前面我们已经学习了对象相关内容,如alloc的流程,对象的内存分配,以及对象ISA的初始化等;明确了OC层NSObject,与c\c++层中objc_object的对等关系;Class的定义为objc_class *类型。接下来,深入探索类的结构。
一.isa走位
在对象的初始化过程中,学习了对象isa的初始化,并且isa中的shiftcls指向了对象所对应的类。
通过以下案例我们可以再次验证这一点,即对象内存结构的前八个字节是对象的isa,并且指向了p1对象的类LGPeron。见下图:

1.元类
在前面我们已经知道objc_class继承自objc_object,万物接对象,也就是说,类也有一个isa指针,那么思考以下,类的isa指向什么呢?
接着上面的案例,打印LGPerson类中的isa指针所指向的内容,发现也是LGPerson!难道类isa指向自己吗?
不是,我们发现对象isa所指向的LGPerson类的地址为0x0000000100004410,而LGPerson类中isa所指向的LGPerson地址为0x0000000100004438,说明这是两个不同的类!见下图:

这里引入一个元类的概念,元类的定义和创建都是由编译器自动完成!可以理解为,为类的方法找到一个归属!
- 总结
对象isa -> 类,类isa -> 元类。
2.根元类
在上面的案例中,我们通过类对象的isa找到了元类对象,元类既然也是对象,那么它的isa又指向谁呢?
依然是上面的案例,我们打印LGPerson元类isa的指向,结果是NSObject!?见下图:

验证一下:NSObject类的地址和LGPerson元类isa所指向的NSObject是否相同?见下图:

从打印结果发现,并不相同!那么NSObject类的isa又指向什么呢?

从结果中可以发现,NSObject类的isa指向NSObject元类,LGPerson元类isa也指向NSObject元类。因为NSObject是所有类的根类,所以将NSObject元类称为根元类。
那么根元类的isa又指向哪里呢?见下图:

通过打印根元类的内存空间,发现根元类的isa指向了自己。
- 总结
元类isa -> 根元类,NSObject isa -> 根元类,根元类 isa -> 自己。
3.isa走位总结
补充:创建一个LGTeacher类,继承LGPerson类,同样按照上面的分析方式,发现LGPerson类的isa指向的是根元类,LGTeacher元类的isa也指向根元类。
通过上面的isa分析,可以得出isa的走位图:

二.superclass走位
在进行superclass走位分析之前,先要确定superclass指针所在位置,查看objc_class源码实现:
struct objc_class : objc_object {
    // Class ISA;
    Class superclass; // 第二个8字节
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    
    …… 省略
}
复制代码通过上面的源码可以确定,superclass指针在类的第二个8字节中。
1.类superclass
引入一个案例LGTeacher继承自LGPerson,LGPerson继承NSObject,以我们现有的知识储备,很容理解superclass的走位,即:LGTeacher superclass -> LGPerson,LGPerson superclass -> NSObject,NSObject superclass -> nil。
验证一下:

从输出结果看,和我们的设想是一样的。
2.元类superclass
类的superclass我们很熟悉,也很好理解,那么元类的superclass走位是怎样的呢?
同样的思路,首先找到LGTeacher元类,然后根据元类superclass开始分析。

从上面的流程可以发现,LGTeacher元类的superclass指向LGPerson,LGPerson的superclass指向NSObject,NSObject的superclass指向NSObject根类。通过验证可以确定,这里的LGPerson为元类,并且其superclass指向的是NSObject元类。
- 总结
LGTeacher元类 superclass -> LGPerson元类,LGPerson元类 superclass -> NSObject元类,NSObject元类 superclass -> NSObject类。
3.superclass总结
根据上面的分析,可以确定以下superclass走位图:

- isa、- superclass总结,这里使用苹果官方的一走位图,将- isa和- superclass放在一起

补充:类、元类、根元类都是唯一的。
三.类结构分析
在进行类的结构分析前,需要明确的是OC层与c\c++层的对应关系。见下图:

- OC层,万物皆对象,大部分的类均继承自- NSObject。
- 使用clang查看源码,定义的别名NSObject为objc_object类型。
- c\c++层,类的定义为- objc_class,继承自- objc_object,与- OC层一致,万物皆对象。
1.类的结构初步分析
在libObjc.A.dylib库中,全局搜索obj_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;            
    class_data_bits_t bits;   
    
    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    ……省略
}
复制代码类中包含四个重要的属性,Class ISA、Class superclass、cache_t cache、class_data_bits_t bits。
- isa和- superclass前面已经做了分析,这两个指针确定了类之间的关系。
- cache中存储了运行中的一些缓存信息,比如消息缓存。从- objc_class提供的方法可以看出,在获取一些数据时,优先从缓存中获取,如果没有缓存,则从- bits中获取。这样设计的目的是- 保证响应速度。
- bits,类中相关的数据信息都存储在了- bits中。
2.cache_t cache
cache_t源码如下:
struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
    
    ……省略
}
复制代码- 结构体空间大小
解读cache_t源码,提供了两个属性,_bucketsAndMaybeMask和一个联合体,其中_bucketsAndMaybeMask为uintptr_t泛型,占8个字节。联合体中包含一个结构体和一个指针,所以联合体也占用8个字节,所以cache_t一共占用16字节的内存空间。
- 不同架构区分
苹果为不同结构提供了不同的配置模式,其中CACHE_MASK_STORAGE_HIGH_16为ios真机环境。

不同环境的区分,见下图:

3.class_data_bits_t bits
class_data_bits_t是一个结构体,objc_class为其提供了两个重要的方法,data()和setData()。
    // objc_class - 类
    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    
    // class_data_bits_t - rw
    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)));
        uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
        atomic_thread_fence(memory_order_release);
        bits = newBits;
    }
复制代码在类的实现过程中,会创建一个class_rw_t,会将MachO文件中的class_ro_t数据拷贝一份到class_rw_t中,并设置到类中。在class_rw_t中提供了一些方法,可以获取类的属性、协议、分类、方法等。
四.类结构探索
引入一个案例,来探索类的结构,定义了LGPerson类,包括了两个属性,一个“成员变量,一个对象方法和一个类方法,见下面代码:
@interface LGPerson : NSObject{
    NSString *subject;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;
- (void)sayNB;
+ (void)say666;
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *t1 = [[LGPerson alloc] init]; 
    }
    return 0;
}
复制代码设置断点,运行程序,获取LGPerson类的地址,并答应类的内存结构。见下图:

很显然0x00000001000043c8为指向元类的isa,0x0000000100353140为superclass,那么如果要探索bits数据,应该查看那部分地址呢?
从上面的源码结构可以知道,isa占用8字节,superclass占用8字节,cache占用16字节,所以只要类地址平移32字节即可获取bits的首地址!
其中类的首地址为0x1000043f0,那么bits首地址 = 0x1000043f0 + 0x20。
通过上面的分析,将bits首地址强制转换为class_data_bits_t *即可成功获取class_data_bits_t数据结构。见下图:

此时bits不等于空,所以类中已经存储了相关的数据信息。继续探索,我们上面已经说明,class_data_bits_t结构体中提供了data()方法,用于获取class_rw_t,class_rw_t是在类初始化过程中已经被创建了,并且class_rw_t的相关数据来自MachO文件中ro数据!
下面获取class_rw_t内容:

我们已经探索到了类的class_rw_t中,在这里我们可以获取相关的地方、属性等内容。
1.探索方法
查看源码可知,在class_rw_t中提供了const method_array_t methods()方法,在这里寻找突破口。

至此获取到了method_list_t,并且里面有count = 5个方法,只要便利该列表就可以拿到相关方法了。打印输出第一个元素:

很遗憾没有输出结果,为空?查看method_t的源码实现,内部有一个big结构体,该结构体包括了方法编号SEL,方法type encoding,和方法实现。
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;
    };
}
复制代码打印输出big数据,见下图:

成功输出了五个方法!其中name和hobby属性会自动生成get\set方法,但是没有类方法,全是对象方法!!!类+say666()方法在哪里呢?
2.探索属性
和探索方法一样的方式,通过调用class_rw_t中的properties()方法,可以获取属性列表。运行结果如下:

成功找到了name和hobby两个属性,那么成员变量subject放在哪里呢,name和hobby对应的_成员变量又放在那呢?“
3.class_ro_t谜底
走读class_rw_t的源码,并没有发现存储变量的相关属性,也没有获取变量的相关方法,但是在const class_ro_t *ro()方法获取的class_ro_t结构体中有一个属性const ivar_list_t * ivars;,并且是一个常量!说明在编译之初已经确定,运行时也不会修改!
下面从ro()方法中寻找突破口!见下图:

在class_ro_t中,包括方法列表、协议列表、变量列表等,成员变量是否存储在这里呢?进去看看:

完美,成功找到了LGPerson的三个成员变量,subject、_name、_hobby均已找到!!!
4.类方法
类方法呢?在前面的探索中,只找到了5个方法,类方法没有找到,同样在class_ro_t中baseMethods()中也没有找到类方法!类方法放在哪里呢?
跟踪消息慢速方法查找,发现此时的cls已经不是类了,而是元类。

所以类方法存储在元类中!验证一下,找到LGPerson的元类,采用相同的方式进行指针平移,调用bits.data()获取class_rw_t,在methods()中成功获取了类方法+say666(),见下图:

五.ro rw rwe补充
- ro属于- clean memory,在编辑时即确定的内存空间,只读,加载后不会发生改变的内存空间,包括类名称、方法、协议和实例变量的信息;
- rw的数据空间属于- dirty memory,- rw是运行时的结构,可读可写,由于其动态性,可以往类中添加属性、方法、协议。在运行时会发生变更的内存。
- rwe类的额外信息。在- rw中只有- 10%的类真正的更改了他们的方法,并不是每一个类都需要插入数据,进行修改的类很少,避免资源的消耗,所以就有了- rwe。
rw数据什么时候初始化呢?—— realizeClassWithoutSwift,类初始化的时候! 之后的文章中会详细分析!!!
在类初始化流程中,从macho中获取的数据data(),强制装换为class_ro_t,同时初始化class_rw_t的空间,并复制一份ro的数据放入rw中。
rwe是什么呢?与rw、ro有什么关系呢?内部到底是怎么存储的呢?
看源码:
//  class_rw_t 对应 rw
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 *>;
    const ro_or_rw_ext_t get_ro_or_rwe() const {
        return ro_or_rw_ext_t{ro_or_rw_ext};
    }
    void set_ro_or_rwe(const class_ro_t *ro) {
        ro_or_rw_ext_t{ro}.storeAt(ro_or_rw_ext, memory_order_relaxed);
    }
    void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
        // the release barrier is so that the class_rw_ext_t::ro initialization
        // is visible to lockless readers
        rwe->ro = ro;
        ro_or_rw_ext_t{rwe}.storeAt(ro_or_rw_ext, memory_order_release);
    }
    class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
}
//  class_ro_t 对应 ro
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
}
//  class_rw_ext_t 对应 rwe
struct class_rw_ext_t {
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};
复制代码内部实现的关键是:
using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;
复制代码底层定义了一个模板,利用模板机制可以显著减少冗余信息,能大幅度地节约程序代码,进一步提高面向对象程序的可重用性和维护性。
模板中提供了is数据判断、get获取数据、storeAt存储数据等方法。
template <class PT1, class PT2>
class PointerUnion {
    ……省略
public:
   ……省略
    void storeAt(std::atomic<uintptr_t> &raw, std::memory_order order) const {
        raw.store(_value, order);
    }
    template <typename T>
    bool is() const {
        using Ty = typename PointerUnionTypeSelector<PT1, T, IsPT1,
            PointerUnionTypeSelector<PT2, T, IsPT2,
            UNION_DOESNT_CONTAIN_TYPE<T>>>::Return;
        return getTag() == Ty::Num;
    }
    template <typename T> T get() const {
      ASSERT(is<T>() && "Invalid accessor called");
      return reinterpret_cast<T>(getPointer());
    }
    template <typename T> T dyn_cast() const {
      if (is<T>())
        return get<T>();
      return T();
    }
};
复制代码在class_rw_t中,所提供的方法里都有这样的一段代码,重点!!!:
    auto v = get_ro_or_rwe();
    if (v.is<class_rw_ext_t *>()) {
        v.get<class_rw_ext_t *>()->...  // 省略
    } else {
        ...  // 省略
    }
    // get_ro_or_rwe实现
    const ro_or_rw_ext_t get_ro_or_rwe() const {
        return ro_or_rw_ext_t{ro_or_rw_ext};
    }
复制代码可以理解为调用get_ro_or_rwe(),获取模板ro_or_rw_ext_t;再调用模板的is<class_rw_ext_t *>()方法,判断是否存在rwe,即是否存在class_rw_ext_t数据空间;如果存在,则调用get<class_rw_ext_t *>()方法从rwe的数据空间中获取对应的数据。
那么rwe什么时候被创建的呢?动态向本类中添加方法、协议、分类信息的时候,会调用extAllocIfNeeded方法来初始化rwe。
class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>();
        } else {
            return extAlloc(v.get<const class_ro_t *>());
        }
    }
class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
    runtimeLock.assertLocked();
    
    auto rwe = objc::zalloc<class_rw_ext_t>();
    
    rwe->version = (ro->flags & RO_META) ? 7 : 0;
    
    // 初始化,会优先将ro中的baseMethodList放入class_rw_ext_t->methods,所以对于运行时rwe附加信息的方法列表,一定会存在全部的方法列表。
    method_list_t *list = ro->baseMethods();
    if (list) {
        if (deepCopy) list = list->duplicate();
        rwe->methods.attachLists(&list, 1);
    }
    
    // See comments in objc_duplicateClass
    // property lists and protocol lists historically
    // have not been deep-copied
    //
    // This is probably wrong and ought to be fixed some day
    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }
    
    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }
    
    set_ro_or_rwe(rwe, ro);
    return rwe;
}
复制代码通过源码可以发现,在创建rwe,也就是创建class_rw_ext_t时,会将ro优添加到class_rw_ext_t数据结构中。
那么ro的数据哪来的呢?看设置ro的源码实现:
void set_ro(const class_ro_t *ro) {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            v.get<class_rw_ext_t *>()->ro = ro;
        } else {
            set_ro_or_rwe(ro);
        }
    }
 void set_ro_or_rwe(const class_ro_t *ro) {
        ro_or_rw_ext_t{ro}.storeAt(ro_or_rw_ext, memory_order_relaxed);
    }
 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;
        }
        return v.get<const class_ro_t *>();
    }
复制代码同样依然会调用get_ro_or_rwe(),获取模板ro_or_rw_ext_t;再调用模板的is<class_rw_ext_t *>()方法,判断是否存在rwe,即是否存在class_rw_ext_t数据空间;如果不存在rwe,则存储在rw中,即拷贝一份到rw中。
而在获取ro数据时,如果rwe已经存在,则直接返回rwe中的ro,如果rwe不存在,直接返回rw中拷贝的ro。
总结:
在类实现过程中,会初始化rw(class_rw_t),从可执行文件macho中读取类的ro(class_ro_t)数据,并将ro拷贝至rw,此时rwe并没有初始化。在运行时,需要动态向类中添加方法、协议,会创建rwe空间,并将ro中的数据优先attach到rwe数据结构中。在读取数据时,会优先返回rwe中的数据,如果rwe没有初始化,则返回ro中的数据。
底层定义了一个模板,利用模板机制可以显著减少冗余信息,能大幅度地节约程序代码,进一步提高面向对象程序的可重用性和维护性。
























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)
