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

举报
Serendipity·y 发表于 2022/08/30 00:58:26 2022/08/30
【摘要】 一、objc 对象的 isa 的指针指向什么?有什么作用? isa 等价于 is kind of: 实例对象 isa 指向类对象; 类对象指 isa 向元类对象; 元类对象的 is...

一、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

  

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

  • 受限于内存分配的机制,一个 NSObject 对象都会分配 16byte 的内存空间。但是实际上在 64 位下,只使用了 8byte;在 32 位下,只使用了 4byte。
  • 一个 NSObject 实例对象成员变量所占的大小,实际上是 8 字节。
#import <Objc/Runtime>
Class_getInstanceSize([NSObject class])

  
  • 本质是:
size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}

  
  • 获取 ObjC 指针所指向的内存的大小,实际上是 16 字节:
#import <malloc/malloc.h>
malloc_size((__bridge const void *)objc);

  
  • 对象在分配内存空间时,会进行内存对齐,所以在 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
};

  
  • 本质上 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;
    }
};

  
  • 继承自 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;

	// ...

  

六、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;
	
	// ...

  

七、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。

文章来源: blog.csdn.net,作者:╰つ栺尖篴夢ゞ,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/Forever_wj/article/details/126583230

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。