• iOS经典面试题之深入解析objc对象的内存空间、数据结构以及isa指针的理解


    一、objc 对象的 isa 的指针指向什么?有什么作用?

    • isa 等价于 is kind of:
      • 实例对象 isa 指向类对象;
      • 类对象指 isa 向元类对象;
      • 元类对象的 isa 指向元类的基类;
    • isa 有两种类型:
      • 纯指针,指向内存地址;
      • NON_POINTER_ISA,除了内存地址,还存有一些其它信息。
    • isa 指向它的类对象,从而可以找到对象上的方法,对象、类、元类之间的关系,如下所示:

    在这里插入图片描述

    • 说明如下:
      • Root class(class) 其实就是 NSObject,NSObject 是没有超类的,因此 Root class(class) 的 superclass 指向 nil;
      • 每个 Class 都有一个 isa 指针指向唯一的 Meta class;
      • Root class(meta) 的 superclass 指向 Root class(class),也就是 NSObject,形成一个回路;
      • 每个 Meta class 的 isa 指针都指向 Root class(meta)。
    • 在 Runtime 源码查看 isa_t 是共用体,简化结构如下:
    union isa_ t {
    	Class cls;
    	uintptr_ t bits;
    	# if _arm64__ 									  // arm64架构
    	# define ISA_MASK 	     0x80000000ffffffff8ULL   // 用来取出33位内存地址使用(&)操作
    	# define ISA_MAGIC_MASK  0x0000003f000000001ULL
    	# define ISA_MAGIC_VALUE 0x0000001a000000001ULL
    	struct {
    		uintptr_t nonpointer         :1;  // 0:代表普通指针,1:表示优化过的,可以存储更多信息
    		uintptr_t has_assoc          :1;  // 是否设置过关联对象,如果没设置过,释放会更快
    		uintptr t has_cxx_dtor       :1;  // 是否有C++的析构函数
    		uintptr t shiftcls           :33; // MACH_VL_MAX_ADDRESS ox10000000000 内存地址值
    		uintptr_t magic              :6;  // 用于在调试时分解对象是否未完成初始化
    		uintptr_t weakly_referenced  :1;  // 是否有被弱引用指向过
    		uintptr_t deallocating       :1;  // 是否正在释放
    		uintptr_t has_sidetable_rc   :1;  // 引用计数器是否过大无法存储在ISA中,如果为1,那么引用计数会存储在一个叫ideTable的类的
    		uintptr_t extra_rc           :19; // 里面存储的值是引用计数器减
    		define RC_ONE  (1ULL<<45)
    		define RC_HALF (1ULL<<18)
    };
    
    elif _x86_64_     // arm86架构, 模拟器是arm86
    	define ISA_MASK 	   0x00007ffffffffff8ULL
    	define ISA_MAGIC_MASK  0x001f800000008001ULL
    	define ISA_MAGIC_VALUE 0x001d800000008001ULL
    	struct {
    		uintptr_t nonpointer   		: 1;
    		uintptr_t has_assoc.   		: 1;
    		uintptr_t has_cxx_dtor 		: 1;
    		uintptr_t shiftcls     		: 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
    		uintptr_t magic.       		: 6;
    		uintptr_t weakly_referenced : 1;
    		uintptr_t deallocating 		: 1;
    		uintptr_t has_sidetable_rc 	: 1;
    		uintptr_t extra_rc     		: 8;
    		define RC_ONE  (1ULL<<56)
    		define RC_HALF (1ULL<<7)
    	};
    else
    	error unknown architecture for packed isa
    endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    二、一个 NSObject 对象占用多少内存空间?

    • 受限于内存分配的机制,一个 NSObject 对象都会分配 16byte 的内存空间。但是实际上在 64 位下,只使用了 8byte;在 32 位下,只使用了 4byte。
    • 一个 NSObject 实例对象成员变量所占的大小,实际上是 8 字节。
    #import <Objc/Runtime>
    Class_getInstanceSize([NSObject class])
    
    • 1
    • 2
    • 本质是:
    size_t class_getInstanceSize(Class cls)
    {
        if (!cls) return 0;
        return cls->alignedInstanceSize();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 获取 ObjC 指针所指向的内存的大小,实际上是 16 字节:
    #import <malloc/malloc.h>
    malloc_size((__bridge const void *)objc);
    
    • 1
    • 2
    • 对象在分配内存空间时,会进行内存对齐,所以在 iOS 中,分配内存空间都是 16 字节的倍数。

    三、实例对象的数据结构

    • 在文件 objc-private.h 中,如下所示:
    struct objc_object {
    private:
        isa_t isa;
    
    public:
    
        // ISA() assumes this is NOT a tagged pointer object
        Class ISA(bool authenticated = false);
    
        // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
        Class rawISA();
    
        // getIsa() allows this to be a tagged pointer object
        Class getIsa();
        
        uintptr_t isaBits() const;
    
        // initIsa() should be used to init the isa of new objects only.
        // If this object already has an isa, use changeIsa() for correctness.
        // initInstanceIsa(): objects with no custom RR/AWZ
        // initClassIsa(): class objects
        // initProtocolIsa(): protocol objects
        // initIsa(): other objects
        void initIsa(Class cls /*nonpointer=false*/);
        void initClassIsa(Class cls /*nonpointer=maybe*/);
        void initProtocolIsa(Class cls /*nonpointer=maybe*/);
        void initInstanceIsa(Class cls, bool hasCxxDtor);
    
        // changeIsa() should be used to change the isa of existing objects.
        // If this is a new object, use initIsa() for performance.
        Class changeIsa(Class newCls);
    
        bool hasNonpointerIsa();
        bool isTaggedPointer();
        bool isTaggedPointerOrNil();
        bool isBasicTaggedPointer();
        bool isExtTaggedPointer();
        bool isClass();
    
        // object may have associated objects?
        bool hasAssociatedObjects();
        void setHasAssociatedObjects();
    
        // object may be weakly referenced?
        bool isWeaklyReferenced();
        void setWeaklyReferenced_nolock();
    
        // object may have -.cxx_destruct implementation?
        bool hasCxxDtor();
    
        // Optimized calls to retain/release methods
        id retain();
        void release();
        id autorelease();
    
        // Implementations of retain/release methods
        id rootRetain();
        bool rootRelease();
        id rootAutorelease();
        bool rootTryRetain();
        bool rootReleaseShouldDealloc();
        uintptr_t rootRetainCount();
    
        // Implementation of dealloc methods
        bool rootIsDeallocating();
        void clearDeallocating();
        void rootDealloc();
    
    private:
        void initIsa(Class newCls, bool nonpointer, bool hasCxxDtor);
    
        // Slow paths for inline control
        id rootAutorelease2();
        uintptr_t overrelease_error();
    
    #if SUPPORT_NONPOINTER_ISA
        // Controls what parts of root{Retain,Release} to emit/inline
        // - Full means the full (slow) implementation
        // - Fast means the fastpaths only
        // - FastOrMsgSend means the fastpaths but checking whether we should call
        //   -retain/-release or Swift, for the usage of objc_{retain,release}
        enum class RRVariant {
            Full,
            Fast,
            FastOrMsgSend,
        };
    
        // Unified retain count manipulation for nonpointer isa
        inline id rootRetain(bool tryRetain, RRVariant variant);
        inline bool rootRelease(bool performDealloc, RRVariant variant);
        id rootRetain_overflow(bool tryRetain);
        uintptr_t rootRelease_underflow(bool performDealloc);
    
        void clearDeallocating_slow();
    
        // Side table retain count overflow for nonpointer isa
        struct SidetableBorrow { size_t borrowed, remaining; };
    
        void sidetable_lock();
        void sidetable_unlock();
    
        void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced);
        bool sidetable_addExtraRC_nolock(size_t delta_rc);
        SidetableBorrow sidetable_subExtraRC_nolock(size_t delta_rc);
        size_t sidetable_getExtraRC_nolock();
        void sidetable_clearExtraRC_nolock();
    #endif
    
        // Side-table-only retain count
        bool sidetable_isDeallocating();
        void sidetable_clearDeallocating();
    
        bool sidetable_isWeaklyReferenced();
        void sidetable_setWeaklyReferenced_nolock();
    
        id sidetable_retain(bool locked = false);
        id sidetable_retain_slow(SideTable& table);
    
        uintptr_t sidetable_release(bool locked = false, bool performDealloc = true);
        uintptr_t sidetable_release_slow(SideTable& table, bool performDealloc = true);
    
        bool sidetable_tryRetain();
    
        uintptr_t sidetable_retainCount();
    #if DEBUG
        bool sidetable_present();
    #endif
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 本质上 objc_object 的私有属性只有一个 isa 指针,指向类对象的内存地址。

    四、类对象的数据结构

    • 类对象就是 objc_class,如下所示:
    struct objc_class : objc_object {
        Class superclass;
        const char *name;
        uint32_t version;
        uint32_t info;
        uint32_t instance_size;
        struct old_ivar_list *ivars;
        struct old_method_list **methodLists;
        Cache cache;
        struct old_protocol_list *protocols;
        // CLS_EXT only
        const uint8_t *ivar_layout;
        struct old_class_ext *ext;
    
        void setInfo(uint32_t set) {
            OSAtomicOr32Barrier(set, (volatile uint32_t *)&info);
        }
    
        void clearInfo(uint32_t clear) {
            OSAtomicXor32Barrier(clear, (volatile uint32_t *)&info);
        }
    
    
        // set and clear must not overlap
        void changeInfo(uint32_t set, uint32_t clear) {
            ASSERT((set & clear) == 0);
    
            uint32_t oldf, newf;
            do {
                oldf = this->info;
                newf = (oldf | set) & ~clear;
            } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&info));
        }
    
        bool hasCxxCtor() {
            // set_superclass propagates the flag from the superclass.
            return info & CLS_HAS_CXX_STRUCTORS;
        }
    
        bool hasCxxDtor() {
            return hasCxxCtor();  // one bit for both ctor and dtor
        }
    
        // Return YES if the class's ivars are managed by ARC, 
        // or the class is MRC but has ARC-style weak ivars.
        bool hasAutomaticIvars() {
            return info & (CLS_IS_ARC | CLS_HAS_WEAK_WITHOUT_ARC);
        }
    
        // Return YES if the class's ivars are managed by ARC.
        bool isARC() {
            return info & CLS_IS_ARC;
        }
    
        bool hasCustomRR() {
            return true;
        }
    
        bool hasCustomAWZ() {
            return true;
        }
    
        bool forbidsAssociatedObjects() {
            // Old runtime doesn't support forbidding associated objects.
            return false;
        }
        
        bool instancesHaveAssociatedObjects() {
            return info & CLS_INSTANCES_HAVE_ASSOCIATED_OBJECTS;
        }
    
        void setInstancesHaveAssociatedObjects() {
            setInfo(CLS_INSTANCES_HAVE_ASSOCIATED_OBJECTS);
        }
    
        bool shouldGrowCache() {
            return info & CLS_GROW_CACHE;
        }
    
        void setShouldGrowCache(bool grow) {
            if (grow) setInfo(CLS_GROW_CACHE);
            else clearInfo(CLS_GROW_CACHE);
        }
    
        // +initialize bits are stored on the metaclass only
        bool isInitializing() {
            return getMeta()->info & CLS_INITIALIZING;
        }
    
        // +initialize bits are stored on the metaclass only
        void setInitializing() {
            getMeta()->setInfo(CLS_INITIALIZING);
        }
    
        // +initialize bits are stored on the metaclass only
        bool isInitialized() {
            return getMeta()->info & CLS_INITIALIZED;
        }
    
        // +initialize bits are stored on the metaclass only
        void setInitialized() {
            getMeta()->changeInfo(CLS_INITIALIZED, CLS_INITIALIZING);
        }
    
        bool isLoadable() {
            // A class registered for +load is ready for +load to be called
            // if it is connected.
            return isConnected();
        }
    
        IMP getLoadMethod();
    
        bool isFuture();
    
        bool isConnected();
    
        const char *mangledName() { return name; }
        const char *demangledName() { return name; }
        const char *nameForLogging() { return name; }
        
        bool isRootClass() {
            return superclass == nil;
        }
    
        bool isRootMetaclass() {
            return ISA() == (Class)this;
        }
    
        bool isMetaClass() {
            return info & CLS_META;
        }
    
        // NOT identical to this->ISA() when this is a metaclass
        Class getMeta() {
            if (isMetaClass()) return (Class)this;
            else return this->ISA();
        }
    
        // May be unaligned depending on class's ivars.
        uint32_t unalignedInstanceStart() {
            // This is not simply superclass->instance_size.
            // superclass->instance_size is padded to its sizeof() boundary, 
            // which may envelop one of this class's ivars. 
            // That in turn would break ARC-style ivar layouts.
            // Instead, we use the address of this class's first ivar when possible.
            if (!superclass) return 0;
            if (!ivars || ivars->ivar_count == 0) return superclass->instance_size;
            return ivars->ivar_list[0].ivar_offset;
        }
    
        // Class's instance start rounded up to a pointer-size boundary.
        // This is used for ARC layout bitmaps.
        uint32_t alignedInstanceStart() {
            return word_align(unalignedInstanceStart());
        }
    
    
        // May be unaligned depending on class's ivars.
        uint32_t unalignedInstanceSize() {
            return instance_size;
        }
    
        // Class's ivar size rounded up to a pointer-size boundary.
        uint32_t alignedInstanceSize() {
            return word_align(unalignedInstanceSize());
        }
    
        size_t instanceSize(size_t extraBytes) {
            size_t size = alignedInstanceSize() + extraBytes;
            // CF requires all objects be at least 16 bytes.
            if (size < 16) size = 16;
            return size;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 继承自 objc_object 结构体,因此包含 isa 指针:
      • isa:指向元类;
      • superClass:指向父类;
      • Cache:方法的缓存列表;
      • data:顾名思义,就是数据,是一个被封装好的 class_rw_t。

    五、class_rw_t 的理解

    • rw 代表可读可写,ObjC 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t 中:
    // 可读可写
    struct class_rw_t {
    	// Be warned that Symbol ication knows the layout of this structure
    	uint32_t flags;
    	uint32_t version; ,
    	const class_ro_t *ro;        // 指向只读的结构体,存故类初始信息
    
    	/*
    	这三个都是二维数组,是可读可写的,包含了类的初始内容,分类的内容
    	methods 中,存储 methods_list_t --> methods_t
    	二维数组,methods_list_t --> methods_t
    	这三个二维数组中的数据有一部分是从 class_ro_t 中合并过来
    	*/
    	method_array_t methods;      // 方法列表(类对象存放对象方法,元类对象存放实例方法)
    	property_array_t properties; // 属性列表
    	protocol_array_t protocols;  // 协议列表
    	Class firstSubclass;
    	Class nextSiblingClass;
    
    	// ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    六、class_ro_t 的理解

    • 存储了当前类在编译期就已经确定的属性、方法以及遵循的协议:
    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;
    #ifdef __LP64__
        uint32_t reserved;
    #endif
    
        union {
            const uint8_t * ivarLayout;
            Class nonMetaclass;
        };
    
        explicit_atomic<const char *> name;
        // With ptrauth, this is signed if it points to a small list, but
        // may be unsigned if it points to a big list.
        void *baseMethodList;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars;
    
        const uint8_t * weakIvarLayout;
        property_list_t *baseProperties;
    	
    	// ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    七、objc 中向一个 nil 对象发送消息将会发生什么?

    • 如果向一个 nil 对象发送消息,首先在寻找对象的 isa 指针时就是 0 地址返回,所以不会出现任何错误,也不会崩溃。
    • 如果一个方法返回值是一个对象,那么发送给 nil 的消息将返回 0(nil);
    • 如果方法返回值为指针类型,其指针大小为小于或者等于 sizeof(void*) ,float,double,long double 或者 long long 的整型标量,发送给 nil 的消息将返回 0;
    • 如果方法返回值为结构体,发送给 nil 的消息将返回 0,结构体中各个字段的值将都是 0;
    • 如果方法的返回值不是上述提到的几种情况,那么发送给 nil 的消息的返回值将是未定义的。

    八、objc 在向一个对象发送消息时,发生了什么?

    • objc 在向一个对象发送消息时,runtime 会根据对象的 isa 指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果一直到根类还没找到,转向拦截调用,执行消息转发机制,一旦找到 ,就去执行它的实现 IMP。
  • 相关阅读:
    LeetCode 2894. 分类求和并作差【数学,容斥原理】1140
    怎么查找女性人数的不同年龄段的人数
    【linux kernel】linux的platform设备驱动框架分析
    centos设置固定ip
    hadoop 实现数据排序
    一键AI去除视频水印和字幕!
    MacOS 上安装和管理 Node.js
    PDF转HTML转换器哪个好用?快看看这里
    C++ Reference: Standard C++ Library reference: C Library: cfenv: FE_INVALID
    MVC netbeans数据库操作注意事项
  • 原文地址:https://blog.csdn.net/Forever_wj/article/details/126583230