[OC学习笔记]类对象的结构

对象的分类

Objective-C中所有对象可以分为3类:实例对象,类对象,元类对象。其中我们开发者常用的继承自NSObject都属于实例对象,实例对象通过isa指针指向的是类对象。类对象通过isa指向的是元类对象。类对象和元类对象拥有同样的结构,都是来自objc_class。关于这部分可以先学习一下我前面的博客:[OC学习笔记]浅学元类

objc_class结构

因为所有的类都是继承于objct_class,在源码中查找objc_class可以找到其结构:

struct objc_class : objc_object {
    
    
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    ...

其中isa指针是继承自objc_object的。isa在之前的内容里面已经讲过,主要是指向类对象或者元类对象的。类结构里面默认一个Class ISA同时包含Class superclasscache_t cacheclass_data_bits_t bitssuperclass是体现出继承关系的。cache是方法缓存。bits是类的其他信息,例如成员变量,方法列表,协议,属性。
注意:元类对象结构也是如此,但元类对象里面没有成员变量,协议,属性这些内容。

isa指向

放出这张经典图片:
请添加图片描述

isa指向做一个简单的总结:

实例对象的isa指针指向对应的类对象,类对象的isa指针指向对应的元类对象,元类对象的isa指向基类的元类对象(根元类)。

可以用lldb调试来验看下。

- (void)test {
    
    
    NSObject *objc = [[NSObject alloc] init];
    // isa指向的就是类对象
    Class objcClass1 = [NSObject class];
    Class objcMetaClass = object_getClass([NSObject class]);
}

请添加图片描述
根据上一篇内容,我们先获取objc的isa指针的值,然后用这个值与运算ISA_MASK,得到的值,正好是类对象的地址。然后用同样的方法获取类对象的值,和ISA_MASK与运算得到元类对象的地址。
可以继续这样再操作一轮,得到的还是元类对象的地址。这是因为该类是NSObjectNSObject元类对象的isa是指向自己的。

define ISA_MASK        0x007ffffffffffff8ULL

0x00007ffffffffff8 转二进制为

0001 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1000

其实就是,000…000…中间44个1…000。其他位抹零,只保留中间44位,即取到shiftcls类信息。

请添加图片描述
可看到NSObjectisa走势为: NSObject →根元类 →根元类自身

superClass指向

再看上面那张图:superClass的指向也在上面那个经典的图中。这里要注意superclassobjc_class才有的,所以实例对象是没有的。类对象的superclass指向父类对象,元类对象的superclass指向父类的元类对象,根元类对象的superclass指针指向根类对象。根类对象的superclassnil

bits

类的结构中还有一个bits,里面也存了很多信息。我们之前知道,已知首地址,可以通过平移方法,得到我们64位下结构体指针类型占8字节,即isa占8字节,superclass同理也是结构体指针类型占8字节,即superclass占8字节。
接下来我们看下cache的大小:
因为cachecache_t类型, 最简单的方法lldb命令 po读一下cache_t请添加图片描述
可以看到,大小为16字节。
这一小节,我们主要来看bits的数据结构 class_data_bits_t

struct class_data_bits_t {
    
    
    friend objc_class;

    // Values are the FAST_ flags above.
    uintptr_t bits;
private:
    bool getBit(uintptr_t bit) const
    {
    
    
        return bits & bit;
    }

    // Atomically set the bits in `set` and clear the bits in `clear`.
    // set and clear must not overlap.
    void setAndClearBits(uintptr_t set, uintptr_t clear)
    {
    
    
        ASSERT((set & clear) == 0);
        uintptr_t newBits, oldBits = LoadExclusive(&bits);
        do {
    
    
            newBits = (oldBits | set) & ~clear;
        } while (slowpath(!StoreReleaseExclusive(&bits, &oldBits, newBits)));
    }

    void setBits(uintptr_t set) {
    
    
        __c11_atomic_fetch_or((_Atomic(uintptr_t) *)&bits, set, __ATOMIC_RELAXED);
    }

    void clearBits(uintptr_t clear) {
    
    
        __c11_atomic_fetch_and((_Atomic(uintptr_t) *)&bits, ~clear, __ATOMIC_RELAXED);
    }

public:

    class_rw_t* data() const {
    
    
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    void setData(class_rw_t *newData)
    {
    
    
        ASSERT(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
        // Set during realization or construction only. No locking needed.
        // Use a store-release fence because there may be concurrent
        // readers of data and data's contents.
        uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
        atomic_thread_fence(memory_order_release);
        bits = newBits;
    }

    // Get the class's ro data, even in the presence of concurrent realization.
    // fixme this isn't really safe without a compiler barrier at least
    // and probably a memory barrier when realizeClass changes the data field
    const class_ro_t *safe_ro() const {
    
    
        class_rw_t *maybe_rw = data();
        if (maybe_rw->flags & RW_REALIZED) {
    
    
            // maybe_rw is rw
            return maybe_rw->ro();
        } else {
    
    
            // maybe_rw is actually ro
            return (class_ro_t *)maybe_rw;
        }
    }

#if SUPPORT_INDEXED_ISA
    void setClassArrayIndex(unsigned Idx) {
    
    
        // 0 is unused as then we can rely on zero-initialisation from calloc.
        ASSERT(Idx > 0);
        data()->index = Idx;
    }
#else
    void setClassArrayIndex(__unused unsigned Idx) {
    
    
    }
#endif

    unsigned classArrayIndex() {
    
    
#if SUPPORT_INDEXED_ISA
        return data()->index;
#else
        return 0;
#endif
    }

    bool isAnySwift() {
    
    
        return isSwiftStable() || isSwiftLegacy();
    }

    bool isSwiftStable() {
    
    
        return getBit(FAST_IS_SWIFT_STABLE);
    }
    void setIsSwiftStable() {
    
    
        setAndClearBits(FAST_IS_SWIFT_STABLE, FAST_IS_SWIFT_LEGACY);
    }

    bool isSwiftLegacy() {
    
    
        return getBit(FAST_IS_SWIFT_LEGACY);
    }
    void setIsSwiftLegacy() {
    
    
        setAndClearBits(FAST_IS_SWIFT_LEGACY, FAST_IS_SWIFT_STABLE);
    }

    // fixme remove this once the Swift runtime uses the stable bits
    bool isSwiftStable_ButAllowLegacyForNow() {
    
    
        return isAnySwift();
    }

    _objc_swiftMetadataInitializer swiftMetadataInitializer() {
    
    
        // This function is called on un-realized classes without
        // holding any locks.
        // Beware of races with other realizers.
        return safe_ro()->swiftMetadataInitializer();
    }
};

其实最最重要的是其中的2个方法。datasafe_ro,两个方法分别返回class_rw_tclass_ro_t

class_rw_t* data() const {
    
    
	return (class_rw_t *)(bits & FAST_DATA_MASK);
}
const class_ro_t *safe_ro() const {
    
    
	class_rw_t *maybe_rw = data();
	if (maybe_rw->flags & RW_REALIZED) {
    
    
		// maybe_rw is rw
		return maybe_rw->ro();
	} else {
    
    
		// maybe_rw is actually ro
		return (class_ro_t *)maybe_rw;
	}
}

仔细观察获取class_ro_t的方法,里面也用到了data()方法,也可以理解为class_ro_t也在class_rw_t之中。

class_rw_t

继续看class_rw_t的数据结构:

struct class_rw_t {
    
    
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;

private:
    using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("class_rw_ext_t")>;

    const ro_or_rw_ext_t get_ro_or_rwe() const {
    
    ...}

    void set_ro_or_rwe(const class_ro_t *ro) {
    
    ...}

    void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
    
    ...}

    class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);

public:
    void setFlags(uint32_t set)
    {
    
    ...}

    void clearFlags(uint32_t clear) 
    {
    
    ...}

    // set and clear must not overlap
    void changeFlags(uint32_t set, uint32_t clear) 
    {
    
    ...}

    class_rw_ext_t *ext() const {
    
    ...}

    class_rw_ext_t *extAllocIfNeeded() {
    
    ...}

    class_rw_ext_t *deepCopy(const class_ro_t *ro) {
    
    ...}

    const class_ro_t *ro() const {
    
    ...}

    void set_ro(const class_ro_t *ro) {
    
    ...}

    const method_array_t methods() const {
    
    
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
    
    
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
        } else {
    
    
            return method_array_t{
    
    v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods};
        }
    }

    const property_array_t properties() const {
    
    
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
    
    
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
        } else {
    
    
            return property_array_t{
    
    v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
        }
    }

    const protocol_array_t protocols() const {
    
    
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
    
    
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
        } else {
    
    
            return protocol_array_t{
    
    v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
        }
    }
};

可以看到有三个主要的方法:通过这三个方法分别能获取到类的方法、属性、协议。

属性列表打印

已知首地址以及 ISA占 8字节,superclass占8字节,cache占16字节,所以bits前面总共8+8+16 = 32字节, 可通过首地址平移32字节获取bits信息。

@interface MyPerson : NSObject {
    
    
    NSString *myHobby;
    NSString *myCars;
}

@property (nonatomic, strong) NSString *myName;
@property (nonatomic, assign) int age;

- (void)sayHello;
- (void)sayBye;
+ (void)sayFuckOff;

@end

lldb调试区调试:
请添加图片描述
bits 数据信息在前面已经讲过了,我们从读bits数据信息$2之后开始

  • p $3.properties()获得的属性列表的list结构, 其中list中的ptr就是属性数组的参数指针地址。(p $3.properties()命令中的properties方法是由class_rw_t提供的, 方法中返回的实际类型为property_array_t
  • p *$4.list.ptr读一下指针地址指向内容, 可看到获得属性list信息, count = 2, 也符合我们建的2个属性
  • p $5.get(0)可获取到myName对应属性(property_t) $6 = (name = "myName", attributes = "T@\"NSString\",&,N,V_myName")
  • p $5.get(1)可获取到age属性(property_t) $7 = (name = "age", attributes = "Ti,N,V_age")
  • p $5.get(2)数组越界, 因为我们只建立了2个属性

方法列表打印

先仔细看一下上面的那三个方法,可以看到一个叫class_rw_ext_t的东西,先了解一些东西:

struct class_rw_ext_t {
    
    
    DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
    class_ro_t_authed_ptr<const class_ro_t> ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};

struct property_t {
    
    
    const char *name;
    const char *attributes;
};

struct method_t {
    
    
    static const uint32_t smallMethodListFlag = 0x80000000;

    method_t(const method_t &other) = delete;

    // The representation of a "big" method. This is the traditional
    // representation of three pointers storing the selector, types
    // and implementation.
    struct big {
    
    
        SEL name;
        const char *types;
        MethodListIMP imp;
    };

private:
    ...
};

想必与属性列表不同,方法列表的相关内容nametypesimp储存在struct big里面(818新改动),所以获取方法列表里面的信息也要稍微变一下。看别人是这样取的:
请添加图片描述
人家使用p $5.get(0).big()可以取到,但我就不行,反而能用small()取到。通过观察,我只好换用getDescription()获取:
请添加图片描述
只获取了实例方法,没法获取类方法。或者我们直接上代码获取,哎,这里真的没太理解啊。代码获取:

Class metaClass = objc_getClass("MyPerson");
unsigned int count = 0;
Method *methods = class_copyMethodList(metaClass, &count);
NSLog(@"%u", count);
for (unsigned int i = 0; i < count; i++) {
    
    
	Method const method = methods[i];
	//获取方法名
	NSString *key = NSStringFromSelector(method_getName(method));
	NSLog(@"Method, name: %@", key);
}
metaClass = objc_getMetaClass("MyPerson");
count = 0;
methods = class_copyMethodList(metaClass, &count);
NSLog(@"%u", count);
for (unsigned int i = 0; i < count; i++) {
    
    
	Method const method = methods[i];
	//获取方法名
	NSString *key = NSStringFromSelector(method_getName(method));
	NSLog(@"Method, name: %@", key);
}
free(methods);

输出:
请添加图片描述
可以看出来,本类有6个方法(实例方法),元类有一个方法(类方法)。

class_ro_t

其实,我们刚刚也可以发现,成员变量以及类方法并没有在属性列表、方法列表里面。回过头我们再看struct class_rw_t,看到里面有这么一个方法:

const class_ro_t *ro() const {
    
    
	auto v = get_ro_or_rwe();
	if (slowpath(v.is<class_rw_ext_t *>())) {
    
    
		return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
	}
	return v.get<const class_ro_t *>(&ro_or_rw_ext);
}

看下ro底层:

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;
    WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
    _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];

    _objc_swiftMetadataInitializer swiftMetadataInitializer() const {
    
    ...}

    const char *getName() const {
    
    ...}
    class_ro_t *duplicate() const {
    
    ...}

    Class getNonMetaclass() const {
    
    ...}

    const uint8_t *getIvarLayout() const {
    
    ...}
};

可以看到有方法、属性、协议和成员变量。但方法、属性、协议的命名都是base开头的。

探索成员变量

可看到const ivar_list_t * ivars;,有一个ivars属性(ivars: 实例变量),我们仿照下上面也读一下ro
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • p $3.ro()获得的ro的里面的信息
  • p $5.ivars获得的ivars_list_t即成员变量的列表

接下来我们仿照属性列表去读取,发现实例变量储存在ivars_list_t里面,同时也会发现还有属性的成员变量。

小结:

  • 通过XXXX {}定义的成员变量,会存储在类的bits属性中,通过bits --> data() -->ro() --> ivars获取成员变量列表,除了包括成员变量,还包括属性的成员变量。
  • 通过@property定义的属性,存储在bits属性中,通过bits --> data() --> properties() --> list获取属性列表,其中只包含property属性。

探索类方法

所谓的对象/实例方法类方法其实是OC上层或者说苹果官方人为加入的概念, 其底层是都是函数,不区分+-。但实例方法与类方法还是有必要区分的,则苹果将实例方法存在类里面,而类方法存在元类里面。一方面避免对象存储太大会发生混乱,一方面也是为了有个调用区分。
所以说类方法要在元类中查找。
请添加图片描述请添加图片描述

  • x/4gx MyPerson.class以4片段16进账形式打印MyPerson类的内存段,这里留意,我们要取的是元类中的类信息,所以要用类去打印。得到0x0000000100008360isa
  • 0x0000000100008360 & 0x007ffffffffff8ULL即:isa & 掩码,得到类信息0x0000000100008360
  • p (class_data_bits_t *)0x0000000100008380,字节平移32位,得到bits,并转换class_data_bits_t *,这里要留意下千万别忘平移,不然获取的是系统给定isa类方法,几十W条。
  • p $2->data()读取bits里面的data
  • p $4.methods()读取方法列表
  • p *$5.list.ptr获取方法列表里面的信息
  • p $6.get(0).getDescription()->name获取方法列表里面第一条数据, 可看到有(SEL) $8 = "sayFuckOff"

综上

  • 实例方法: 存在对应类的bits中
  • 类方法: 存在对应元类的bits中

ro 和 rw的区别

从生成时机的角度来说,ro编译阶段生成,rw运行的时候生成。从存储的内容角度来讲,ro中有方法、属性、协议和成员变量,而rw中并没有成员变量。rw中的方法属性协议的取值方法中,也是通过取ro或者rwe中的值来获得。ro中的方法、属性、协议都是base,也就是只有本类中的方法属性和协议。

class_rw_ext_t

在2020的WWDC有个视频:Advancements in the Objective-C runtime,其中一部分是和这里有关的,我们看截图:
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
rw_ext_t生成条件:

  • 使用分类的类。
  • 使用Runtime API动态修改类的结构的时候。

在遇到以上2种情况的时候,类的结构(属性、协议、方法)发生改变,原有的ro(Claer Memory,便宜)已经不能继续记录类的属性、协议、方法信息了,于是系统重新生成可读可写的内存结构rw_ext(Dirty Memory, 比较贵),来存放新的类结构。
然后在取方法、属性、列表的时候,在方法实现中来做区分,如果有rw_ext的类,其列表就错那个rw_ext中获得,如果没有,从ro中读取。
请添加图片描述

思考

元类里面存放的内容只有类方法,那么为什么不把类方法存放在类对象中?或者说设计元类的目的是什么呢?

  1. 单一职责设计原理。
    实例对象存储成员变量的值,类对象存放,实例方法、协议、成员变量、属性,元类对象存放类方法,各司其职,互不影响。
  2. 复用msgSend消息发送机制。
    类方法、实例方法是在上层的定义,在底层并不区分类方法实例方法,但在runtime这一层,需要承接上层类方法和实例方法,对接到底层方法调用。使用了msgSend,如果msgSend的时候需要再区分类对象,实例对象,会在内部增加判读逻辑,从而降低了效率,有了元类的存在,问题迎刃而解。

猜你喜欢

转载自blog.csdn.net/weixin_52192405/article/details/125077225