iOS底层之类的结构分析

5,863 阅读8分钟

isa流程分析

首先来看一张比较熟悉的isa的流程走位图

isa流程图.png

isa指向链

  • 实例对象instanceisa指向类class;
  • 类对象class也有isa指向的是元类meta
  • 元类mata中也有isa指向的是根元类root meta

isa走位图(1).png

类的继承链

  • 子类继承与父类父类继承与根类根类指向的是nil
  • 元类中也存在继承,子类的元类继承与父类的元类父类的元类继承与根元类根元类又继承与根类;

类的继承链.png

isa流程实例验证

补充:在获取到对象的isa值后,可以通过&(与)一个掩码ISA_MASK 0x007ffffffffffff8ULL来获取到对象关联的类地址。

image.png

isa指向链验证

定义一个LGPerson继承NSObject,同时定义LGTeacher继承LGPerson

LGPerson *p = [LGPerson alloc];
NSLog(@"%@",p);
LGTeacher *t = [LGTeacher alloc];
NSLog(@"%@",t);

LLDB调试对象t,通过tisa找到关联类LGTeacher的地址为0x0000000100008310

image.png 接着我们按同样的方式输出,找到LGTeacher类对象的isa关联类地址为0x00000001000082e8image.png 往下找,找到LGTeacher元类对象的isa关联类地址为0x00007ff85e5f2220

image.png 按同样的方式,找到NSObject类对象的isa关联地址为0x00007ff85e5f2220,和LGTeacher元类对象的isa关联类地址一致,可以验证元类isa指向根元类

image.png 根元类isa指向自己。 image.png

类的继承链验证

同样按照LGPerson继承NSObjectLGTeacher继承LGPerson的定义。

Class tClass = LGTeacher.class;
Class pClass = class_getSuperclass(tClass);
Class nClass = class_getSuperclass(pClass);
Class rClass = class_getSuperclass(nClass);
NSLog(@"\n tClass-%@ \n pClass-%@ \n nClass-%@ \n rClass-%@ \n", tClass, pClass, nClass, rClass);

image.png

通过上面代码输出打印,很明显看出subClass->superClass->NSObject->nil的继承链关系。

LGTeacher * teacher = [LGTeacher alloc];
Class tClass = object_getClass(teacher);
Class mtClass = object_getClass(tClass);
Class mtSuperClass = class_getSuperclass(mtClass);
NSLog(@"\n teacher %p 实例对象 -- %p 类 -- %p 元类 -- %p 元类父类", teacher, tClass, mtClass, mtSuperClass);

LGPerson * person = [LGPerson alloc];
Class pClass = object_getClass(person);
Class mpClass = object_getClass(pClass);
Class mpSuperClass = class_getSuperclass(mpClass);
NSLog(@"\n person %p 实例对象 -- %p 类 -- %p 元类 -- %p 元类父类", person, pClass, mpClass, mpSuperClass);

NSObject * obj = [NSObject alloc];
Class objClass = object_getClass(obj);
Class mobjClass = object_getClass(objClass);
Class mobjSuperClass = class_getSuperclass(mobjClass);
NSLog(@"\n NSObject %p 实例对象 -- %p 类 -- %p 元类 -- %p 元类父类", obj, objClass, mobjClass, mobjSuperClass);

teacher 0x600000201ba0 实例对象 -- 0x100008310 类 -- 0x1000082e8 元类 -- 0x100008338 元类父类

person 0x600000201bc0 实例对象 -- 0x100008360 类 -- 0x100008338 元类 -- 0x7ff85e5f2220 元类父类

NSObject 0x600000004030 实例对象 -- 0x7ff85e5f2270 类 -- 0x7ff85e5f2220 元类 -- 0x7ff85e5f2270 元类父类

通过输出信息我们可以看出

  • teacher的元类父类地址0x100008338 == person的元类地址0x100008338;
  • person的元类父类地址0x7ff85e5f2220 == NSObject的元类地址0x7ff85e5f2220;
  • NSObject的元类父类地址0x7ff85e5f2270 == NSObject的类地址0x7ff85e5f2270;

综合,我们可以得出元类的继承链关系为Sub Meta->Super Meta->Root Meta-> NSObject

类的结构

前面对象的本质中,我们知道Class的定义是objc_class结构体类型,那么就看一下objc_class的结构体的结构是怎样定义的。

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

struct objc_class : objc_object {
// 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
... 其他代码
}

这里objc_class的继承与objc_objectobjc_object的结构中只有一个isa指针, 因此,objc_class的成员组成为:

  1. objc_object继承的指针isa;
  2. superclass: Class类型指向父类的指针
  3. cache:缓存相关的东西
  4. bits:数据

类的结构.png

cache结构

cache_t也是一个结构体定义

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8字节
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask; // uint32_t 4字节
#if __LP64__
            uint16_t                   _flags;     // 2字节
#endif
            uint16_t                   _occupied;  // 2字节
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;  // 8字节
    };
 }

分析整个cache_t的结构,发现cache_t的内存总共为16字节。 关于cache_t的探究专门写了一篇文章。

bits探究

bits是今天的探究重点,bits到底存储了哪些数据,在objc_class里有一段源码是data操作。

class_rw_t *data() const {
    return bits.data();
}
void setData(class_rw_t *newData) {
    bits.setData(newData);
}

这里data的类型是class_rw_t,在class_rw_t的源码中,我们可以看到几个方法:

image.png

ro:成员变量、methods:方法、properties:属性、protocols协议 ,我们在类中定义的方法,属性等就行通过调取class_rw_t结构体中的方法获取的。

实例探究

下面我们通过实例,来验证一下类的结构是否如上面分析的一致。

我们自定义一个类LGPerson继承自NSObject,定义一些属性和实力方法以及类方法。

image.png LLDB调试输出

image.png

这里我们可以看出第一个地址0x0000000100008228是类的第一个成员isa,第二个地址0x0000000100816140是类的第二个成员superclass

前面我们知道isa指针8字节,superclass指针8字节,cache结构体16字节,通过LGPerson的首地址便宜8 + 8 + 16 = 32字节我们就可以得到bits的内存地址。

image.png 这里用LGPerson的首地址0x100008250加上32字节,得到地址0x0000000100008270,然后转换一下指针类型,得到$9class_data_bits_t类型,调用data方法就得到$10地址为class_rw_t类型,下面就来探究class_rw_t中的属性、方法、协议。

注意

在转换class_data_bits_t这一步,需要在源码环境调试,关于源码的下载和编译,介绍一个靓仔,# iOS 全网最新objc4 可调式/编译源码还有编译好的源码的下载地址

属性properties

调用class_rw_tproperties()方法,得到property_array_t类型的数组,继承与list_array_tt,找到list下的ptrimage.png

image.png ptrproperty_list_t类型,是继承与entsize_list_tt

image.png entsize_list_tt部分源码

struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;  // 数量

    uint32_t entsize() const {
        return entsizeAndFlags & ~FlagMask;
    }
    uint32_t flags() const {
        return entsizeAndFlags & FlagMask;
    }
    Element& getOrEnd(uint32_t i) const {
        ASSERT(i <= count);
        return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
    }
    Element& get(uint32_t i) const { // 获取元素方法
        ASSERT(i < count);
        return getOrEnd(i);
    }
    // ... 省略
};

通过get方法,获取元素,可以看到下面的结果,LGPerson中的属性nameageproperties()里,而成员变量hobby不在这里。

image.png

方法methods

调用class_rw_tmethods()方法,得到method_array_t类型的数组,继承与list_array_tt,同样找到list下的ptr

image.png 这里看到ptrmethod_list_t类型,同样继承与entsize_list_tt,其中有count为6,输出查看

image.png

这里的元素为method_t类型,method_t为结构体类型,其中的一个成员变量为big的结构体,里面是方法名称等信息。

image.png 我们在调用method_tbig方法,查看输出

image.png 这里看到6个方法分别是

  • 实例方法:sayHello
  • 属性name的get方法;
  • C++析构函数:.cxx_destruct
  • 属性nameset方法;
  • 属性ageget方法;
  • 属性ageget方法;

这里的6个方法都是实力对象方法,并没有我们定义的类方法sayWorld

协议protocols

我们自定义一个协议LGPersonDelegate,让LGPerson遵守并实现协议方法

image.png 调用class_rw_tprotocols()方法,得到protocol_array_t类型的数组,继承与list_array_tt,同样找到list下的ptr

image.png 这里看到ptrprotocol_list_t类型,不是继承自entsize_list_tt,那我们看一下protocol_list_t的定义

image.png 看到protocol_list_t的定义,我们知道count值为1,说明是有值,但是其成员是protocol_ref_tuintptr_t类型,那怎么输出查看这个count中的1到底是什么呢 image.png 查看protocol_ref_t的定义,通过注释信息,我们可以看到protocol_ref_t未映射到protocol_t类型,那我们就找protocol_t的定义

image.png 这里看到protocol_t中有mangledName以及instanceMethods等,只要得到protocol_t就可以输出我们想要的名称方法等信息,怎么才能从protocol_ref_t映射到protocol_t呢,全局找一下吧

image.png 一不小心找到了😁,这里我们看到,protocol_ref_t是可以强转protocol_t的,那我们就试试

image.png 强转成功,调用demangledName方法,我们就得到了LGPersonDelegate,那我们再找一下协议方法

image.png 按照method输出的经验,成功找到协议方法personDelegateMethod

ro

调用class_rw_tro方法,得到class_ro_t的结构体

image.png 查看class_ro_t的内容,看到这里有成员ivars,这引起了我们的注意

image.png 接着查看ivars,是ivar_list_t类型的结构体,也是继承entsize_list_tt,那么我们就可以调用get方法查看成员。

image.png

image.png

调用get方法查看输出,这里就可以看到3个成员变量,自定义hobby和系统帮我们定义的属性生成带_的成员变量。

image.png

类方法

在上面的methods方法中,我们并没有得到定义的类方法sayWorld,不由得我陷入了沉思,我们定义的类结构中,没有我们定义的类方法,那类方法会存在哪里呢?

这里我们换个思考角度想一下,在methods中全都是实例方法,也就是对象方法对象方法是存在类中;类方法类对象在调用,那类方法是不是存在元类中呢?不得而知,那么我们就去验证一下。

image.png

首先获取到LGPerson的元类地址,然后按照methods方面的方法步骤,我们成功输出sayWorld方法,也验证了上面的猜想。

这里我们不由的想,OC的底层是CC++实现的,在C语言C++中,不存在对象方法类方法的区分,有的都是函数实现,在OC的设计中,一个可以new出无数个对象,因此把方法存在中,而不是动态创建的对象中,是合理的。

因为OC对象方法类方法的定义是-+的区分,那么方法名称就会有重名的存在,因此才会引入元类的概念,元类的存在就是解决类方法的问题。

类的结构总结

类的结构总结用一张流程图

objc_class.png 其中protocol_ref_t是一个无符号长整型,可以强转为protocol_t,这是后面才找到的。

以上就是对于OC中类的结构分析,如有疑问或错误之处,请评论区留言或私信我,非常感谢!!!