OC底层原理(5)--类的原理分析下

80 阅读4分钟

WWDC关于runtime的优化

Clean Memory 和 Dirty Memory

在iOS里,内存分为两种:
clean Memory 指的是加载后不会发生变化的内存
dirty Memory 指的是进程运行时会发生改变的内存

dirty Memory比clean Memory更昂贵,因为前者一经使用就必须一直存在。
clean Memory可以进行移除,从而节省更多内存空间。因为如果你需要 clean memory ,系统可以从磁盘中重新加载。

macOS可以选择 swap dirty memory,但因为iOS 不使用swap,所以 dirty memory 在iOS中代价很大。dirty memory 是这个类数据被分成两部分的原因。可以保持 clean memory的数据越多越好。通过分离出那些永远不会更改的数据,可以把大部分的类数据存储为 clean memory.

因为clean Memory和dirty Memory的存在,所以类的数据被分成两部分:

一经编译,就不可修改的数据。(ro)

运行过程中可修改的数据。(rw)
ro:数据是只读的,所以它属于clean Memory。
(它是从沙盒读取,ro的数据在编译的时候就已经确定了。)

rw:数据是可读可写的,所以它属于dirty Memory。
(rw的数据存放的是运行时动态修改的数据)
rwe:将ro里面的数据拷贝一份,并且新增的数据也加到rwe里面. 前面我们提到了rw存在的那片内存属于dirty Memory,而dirty Memory在iOS里的代价很大,我们应该尽可能减少rw里的数据存在。

扩展class_rw_t、class_rw_ext_t、class_ro_t

class_rw_ext_t出现前后的class_rw_t对比

image.png

将需要动态修改的内容拆分出来,那么 class_rw_t 的大小就变为之前的一半。

类的结构

image.png

三者之间的关系

image.png

rw里面并不存储属性、协议、方法等变量,只提供了获取这些变量的方法。 假如有的类在运行时改变属性、方法、协议,那么就会去ro里面读取相应的数据,否则就去rwe里面读取数据(rwe会拷贝一份ro的变量数据,而且还包含扩展的变量数据)。

setter方法的底层原理

Person *p = [[Person alloc] init];
p.name = @"jim";

上面的p.name实际上调用了setName进行赋值,而setNamellvm期间,变成set_property方法的调用。之所以会去查看llvm的源码,而不是objc的源码,是因为objc底层是看不到setNameset_property的关联的。

通过Clang来探索一下属性的cpp代码

源码:

@property (nonatomic, copy) NSString *nickName;
@property (atomic, copy) NSString *acnickName;
@property (nonatomic) NSString *nnickName;
@property (atomic) NSString *anickName;
@property (nonatomic, strong) NSString *name;
@property (atomic, strong) NSString *aname;

cpp代码

static NSString * _I_LGPerson_nickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nickName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_LGPerson_setNickName_(LGPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nickName), (id)nickName, 0, 1); }

extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);

static NSString * _I_LGPerson_acnickName(LGPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acnickName), 1); }
static void _I_LGPerson_setAcnickName_(LGPerson * self, SEL _cmd, NSString *acnickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acnickName), (id)acnickName, 1, 1); }

static NSString * _I_LGPerson_nnickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nnickName)); }
static void _I_LGPerson_setNnickName_(LGPerson * self, SEL _cmd, NSString *nnickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nnickName)) = nnickName; }

static NSString * _I_LGPerson_anickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_anickName)); }
static void _I_LGPerson_setAnickName_(LGPerson * self, SEL _cmd, NSString *anickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_anickName)) = anickName; }

static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)) = name; }

static NSString * _I_LGPerson_aname(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_aname)); }
static void _I_LGPerson_setAname_(LGPerson * self, SEL _cmd, NSString *aname) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_aname)) = aname; }

通过clang可以得出以下结论:

getter 方法底层是通过内存平移获取值;

setter 方法底层是通过内存平移set_property方法实现,而当属性用copy修饰的时候才会走set_property方法,否则通过内存平移赋值。

@16@0:8 是类型编码,方法的签名时候使用。

查看类型编码的官网地址:类型编码

image.png

类方法的存储

LGPerson中定义一个类方法+(void)sayNB;

lldb调试

查找它存在的位置:

image.png

API方式解析

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));
        
        LGLog(@"Method, name: %@", key);
    }
    free(methods);
}

void lgInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    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));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

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

void lgIMP_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    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));

    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
    NSLog(@"%s",__func__);

}

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        // LGTeacher *teacher = [LGTeacher alloc];
        LGPerson *person = [LGPerson alloc];
        Class pClass     = object_getClass(person);
        lgObjc_copyMethodList(pClass);
        const char *className = class_getName(pClass);
        Class metaClass = objc_getMetaClass(className);
        NSLog(@"*************");
        lgObjc_copyMethodList(metaClass);

        lgInstanceMethod_classToMetaclass(pClass);
        lgClassMethod_classToMetaclass(pClass);
        lgIMP_classToMetaclass(pClass);
    }
    return 0;
}

打印如下:

Method, name: sayHello
Method, name: name
Method, name: .cxx_destruct
Method, name: setName:
Method, name: obj
Method, name: setObj:

*************

Method, name: sayHappy
lgInstanceMethod_classToMetaclass - 0x1000081b8-0x0-0x0-0x100008150
lgClassMethod_classToMetaclass-0x0-0x0-0x100008150-0x100008150

[74118:14706714] 0x100003b30-0x7ff800ee11c0-0x7ff800ee11c0-0x100003b70

[74118:14706714] lgIMP_classToMetaclass

得出以下结论:

在object底层没有类方法,只有对象方法。

对象方法存储在类里面;

类方法存储在元类当中,在元类中表现为对象方法;

引申 - 元类

为什么要有元类?

是为了复用消息机制objc_msgsend,判断是消息接受者是类对象还是实例对象,方法是类方法还是实例方法,减少objc_msgsend的复杂度。