iOS底层学习-类的原理分析(下)

199 阅读6分钟

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

首先看下苹果WWDC2020关于runtime优化视频讲解

一、类内存的ro数据

上一篇章分析到类的属性和方法,那么成员变量呢? 通过打印class_rw_tproperties()发现只有类的属性但没有成员变量,我们通过WWDC2020关于runtime优化的视频讲解中了解到成员变量ro中。而class_rw_t结构体中可以看到有roget方法

打印class_rw_t中的ro,得到了class_ro_tclass_ro_t中可以看到有个ivars属性,打印出来得到一个ivar_list_t 接着通过get方法就可以发现所有成员变量

二、成员变量和属性以及编码

1、成员变量和属性

上一节找到了成员变量,成员变量和属性在底层中是怎么展现的呢?

通过clang命令编译成底层C++文件 可以看到在底层C++中没有属性,而全在LRPerson_IMPL结构体中,并且属性都被转换成带_形式。 与此同时OC中的属性在底层C++中会生成getset方法,而成员变量不会

2、编码

在C++源码中,可以看到这些符号,这些符号是什么?代表什么意思呢?

这些都是编码,可以通过command + shift + 0,搜索ivar_getTypeEncoding,点击Type Encodings查看详细内存。附上Type Encodings 地址

"setName:", "v24@0:8@16"为例
1、v函数返回值, 表示void
2、24函数参数总共所占内存大小
3、@第一个参数(隐藏参数)即self
4、0从0号位置开始
5、:第二个参数(隐藏参数)SEL
6、8从8号位置开始
7、@第三个参数
8、16从16号位置开始

3、补充

实例变量:特殊的成员变量 (对象类型声明,类的实例化),非基本数据类

三、settergetter方法的底层原理

1、setter方法

在刚才编译的底层C++文件中,可以看到OC中属性的getset方法。但是仔细观察发现nickNameset方法是通过objc_setProperty实现的,而nameanameset方法是通过内存平移赋值实现的

llvm源码中查找objc_setProperty,发现在getSetPropertyFn中创建 继续找getSetPropertyFn 继续找GetPropertySetFunction 发现GetPropertySetFunction的调用是在一个switchcase中,查看下PropertyImplStrategy类型 PropertyImplStrategy的实现 到此可以看到IsCopy的判断,如果是IsAtomic则赋值为GetSetProperty否则为SetPropertyAndExpressionGet,所以copy是影响使用objc_setProperty的重要因素,通过下面代码验证一下

@property (nonatomic, copy) NSString *nickName;
@property (atomic, copy) NSString *nickName1;
@property (nonatomic) NSString *nickName2;
@property (atomic) NSString *nickName3;

clang命令编译成C++源码,可以发现用copy修饰的属性底层通过objc_setProperty实现set方法,其他则通过内存平移赋值的方式

objc源码中看下objc_setProperty函数的实现 可以看到底层会调用copyWithZone。所以copy修饰的属性,是会复制内存的,其他修饰符通过内存平移则不会

2、getter方法

在探究set方法时,我们可以看到有些get方法通过objc_getProperty实现,而有些是直接return一个内存平移后的值 llvm中继续探索objc_getPropertyPropertyImplStrategy类型为GetSetProperty 最终影响getter方法的决定性因素还是这段代码 这段代码的注释也写的很清楚

  • 如果是copy修饰的属性,用setProperty
  • 如果属性还用atomic修饰,那么就用getProperty

四、类方法存储的API方式解析

一个类中有以下两个方法

- (void)sayHello;
+ (void)sayHappy;

1、class_getInstanceMethod

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

    NSLog(@"%p - %p - %p - %p",method1,method2,method3,method4);
}

输出结果:

0x1000081c0 - 0x0 - 0x0 - 0x100008158

结果分析:

  • sayHello对象方法存在类中
  • sayHappy类方法存在元类中

2、class_getClassMethod

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

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));

    NSLog(@"%p - %p - %p - %p",method1,method2,method3,method4);
}

输出结果:

0x0 - 0x0 - 0x100008158 - 0x100008158

class_getClassMethod是获取类方法,在底层中就是获取元类的对象方法 method4传入的是元类,底层代码中会判断若已是元类直接返回,否则返回元类 对象是类的实例,类是元类的实例,在底层中没有类方法这一说,只有对象方法

3、class_getMethodImplementation

void lrIMP_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));
    
    IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy)); 
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));

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

输出结果:

0x100003ae0 - 0x7fff6e3b7580 - 0x7fff6e3b7580 - 0x100003b20

根据class_getInstanceMethod的分析,这里imp2 imp3应该没有实现,但是输出结果却有值。这里涉及到方法查找流程,即使找不到也会返回一个_objc_msgForward。关于消息转发机制后续再详细介绍

4、isKindOfClassisMemberOfClass

void kindOfTest(void) {
    BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
    BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
    BOOL re3 = [(id)[LRPerson class] isKindOfClass:[LRPerson class]];
    BOOL re4 = [(id)[LRPerson class] isMemberOfClass:[LRPerson class]];
    NSLog(@" re1 :%d\n re2 :%d\n re3 :%d\n re4 :%d\n",re1,re2,re3,re4);

    BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
    BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
    BOOL re7 = [(id)[LRPerson alloc] isKindOfClass:[LRPerson class]];
    BOOL re8 = [(id)[LRPerson alloc] isMemberOfClass:[LRPerson class]];
    NSLog(@" re5 :%d\n re6 :%d\n re7 :%d\n re8 :%d\n",re5,re6,re7,re8);
}

输出结果:

 re1 :1
 re2 :0
 re3 :0
 re4 :0
 
 re5 :1
 re6 :1
 re7 :1
 re8 :1

根据isKindOfClassisMemberOfClass的源码分析:

  • + (BOOL)isKindOfClass:(Class)cls类方法。获取类的元类,判断元类和参数是否相同,不同则循环取父类判断。
    re1NSObject是根类,而根元类的父类就是根类和参数相同,返回YES
    re3中从LRPerson元类找到父类NSObject再到nil,都与参数LRPerson类不同。所以返回NO

  • - (BOOL)isKindOfClass:(Class)cls对象方法。获取当前对象的类,判断类和参数是否相同,不同则循环取父类判断。
    re5NSObject对象的类和参数NSObject类相同,返回YES
    re7LRPerson对象的类和参数LRPerson类相同,返回YES

  • + (BOOL)isMemberOfClass:(Class)cls类方法。获取类的元类,判断元类和参数是否相同
    re2NSObject元类和NSObject类不相同,返回NO
    re4LRPerson元类和LRPerson类不相同,返回NO

  • - (BOOL)isMemberOfClass:(Class)cls对象方法。获取对象的类,判断类和参数是否相同
    re6NSObject类和NSObject类相同,返回YES
    re8LRPerson类和LRPerson类相同,返回YES

通过汇编还可以发现isKindOfClassisMemberOfClass在底层中走的是objc_opt_isKindOfClass