OC底层原理探索之类的原理分析下

186 阅读4分钟

类的结构

WWDC2020视频地址-> WWDC类的结构 image.png cleanMemory:加载之后不会变化的内存,class_ro_t就是,因为它是只读的 dirtyMemory:运行时会发生改变的内存,类结构一经使用就变成了dirtyMemory,以为运行时会向它写入新的数据。例如创建一个新的方法缓存并从类中指向它。dirtyMemory在iOS中成本很高 ,dirtyMemory是这个类数据被分成两部分的原因, class_ro_t:类加载过程中并不会改变的信息 class_rw_t存储了只有在运行时才会生成的新信息,同时我们发现大约只有10%的类去真正改变了它们,全量追踪的话内存开销比较大,所以可以拆掉最大可能会变动的部分做成class_rw_ext_tdemangled nameswift并不会使用,而方法属性协议大部分类并不会去动态添加。 class_rw_ext_t:category被加载时可以向类中添加新的方法

成员变量和属性(class_rw_t/class_ro_t)

打开源码,我们在上一篇的Person类中新增一个ivar,注意新增方法的不同 image.png 上一篇我们打印了当前Person类的内存情况,发现这样添加的subject并不在当前的property_list中,那么这个在哪里呢?我们在源码中定位到struct class_rw_t中发现里面有一个获取ro的方法class_ro_t *ro(),这里面是类在运行中不可改变的内容也就是上面说的干净内存,我们来验证下,看是否在这里面 image.png 我们找到了一个ivars,打印输出 image.png 果然,subject在class_ro_t的ivars里面,由此我们可以得出一个结论,由property生成的是属性,subject这种是成员变量,属性和成员变量的存储地方不同。成员变量存储在ro里,属性存储在rw里面的property_list中,并且属性会自动在ro里面生成带下划线_的成员变量。 或者我们生成.cpp文件验证下,新建一个工程,在main中创建一个Person,果然在IMPL里面会自动生成一个_name的成员变量

struct Person_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *hobby;
	NSString *_name;
};

在这个下面就是他们的set和get方法

static void _I_Person_setNickName_(Person * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _nickName), (id)nickName, 0, 1); }
static void _I_Person_setNnickName_(Person * self, SEL _cmd, NSString *nnickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_nnickName)) = nnickName; }

经过仔细对比,我们猜测,使用copy修饰的属性set方法使用的是objc_setProperty,而非copy修饰的属性set方法使用的是内存平移(char *)self + OBJC_IVAR_$_Person$_nnickName 当然啦,runtime也直接提供了获取属性名和实例变量名的方法:

void lgObjc_copyIvar_copyProperies(Class pClass){
    
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Ivar const ivar = ivars[i];
        //获取实例变量名
        NSString *ivarName = [NSString stringWithUTF8String: ivar_getName(ivar)];
        NSLog(@"class_copyIvarList:%@",ivarName);
    }
    free(ivars);

    unsigned int pCount = 0;
    objc_property_t *properties = class_copyPropertyList(pClass, &pCount);
    for (unsigned int i=0; i < pCount; i++) {
        objc_property_t const property = properties[i];
        //获取属性名
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
        //获取属性值
        NSLog(@"class_copyProperiesList:%@",propertyName);
    }
    free(properties);
}

类方法和对象方法

我们在类中添加实例方法和类方法,这两种方法是可以同名的,但是OC的底层是c/c++是没有类方法和实例方法的,统一成为函数,如果我们的类方法和实例方法都放到类里面,那么两个方法是无法区分的。所以这里出现了元类。我们继续验证下 image.png 步骤同上篇一样这里不赘述,最终拿到了Person的类方法,验证了类方法存在于元类中。 image.png 同样的runtime也直接提供了获取类方法和对象方法的函数

void lgObjc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method *methods = class_copyMethodList(pClass, &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);
}

或者可以查询在当前的类/元类中是否有这个方法,下面就从各方面辩证输出打印

void lgInstanceMethod_classToMetaclass(Class pClass){
    // 实例方法、对象方法
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    // - (void)sayHello;
    // + (void)sayHappy;
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello)); //1
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello)); // 0

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy)); // 0
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy)); //1
    
    LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
void lgClassMethod_classToMetaclass(Class pClass){
    // 类方法
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello)); //0
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));//0

	//    - (void)sayHello;
	//    + (void)sayHappy;
    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));//1
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));// 1
    
    LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

经常面试的时候会问到,为什么元类中有sayHappy这个类方法?元类中有sayHappy这个对象方法毋庸置疑,但是为什么也会有这个类方法呢,我们看下底层的源码

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

得到:元类的类方法也就等于元类的实例方法。也就是说在底层来说,是没有类方法这个概念的,只有对象方法

IMP

我们从方法的实现也来验证下

void lgIMP_classToMetaclass(Class pClass){
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
	//    - (void)sayHello;
	//    + (void)sayHappy;
    IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));// 0
    // sel -> imp 方法的查找流程 imp_farw
    IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy)); // 0
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));
    // 0x100003ac0-0x7fff201f25c0-0x7fff201f25c0-0x100003b00
    NSLog(@"%s-%p-%p-%p-%p",__func__,imp1,imp2,imp3,imp4);
}

1,4存在是没有疑问的,既然类方法存在于元类中,那么为什么在元类中会有sayHello的imp,Person中会有sayHappy的imp呢?查看源码sel查找imp的过程,我们发现如果没有imp回返回一个_objc_msgForward

__attribute__((flatten))
IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;
	 	...
    if (!imp) {
        return _objc_msgForward;
    }
    return imp;
}

补充扩展

在.cpp文件中,我们能看到这些编码,他们代表的分别是什么呢?runtime中提供了一个方法ivar_getTypeEncoding command + shift + 0搜索document

{(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_Person_nickName},
{(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_Person_setNickName_},
{(struct objc_selector *)"acnickName", "@16@0:8", (void *)_I_Person_acnickName},

image.png "@16@0:8" @-> 返回类型 id,上面是个nickName的get方法,有返回值,所以为@ 16-> 参数所占用的内存 @-> 隐藏参数id get方法中有一个隐藏参数id self 0-> 从0号位置开始 :-> sel 8-> 从8号位置开始

objc_setProperty

查看LLVM源码,定位到propertyImplStrategy,因为这个set方法需要在编译时就完成,所以需要查看LLVM

if (IsCopy) {
    Kind = GetSetProperty
    return;
}