iOS底层探索之类的结构(下)

Posted 卡卡西Sensei

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS底层探索之类的结构(下)相关的知识,希望对你有一定的参考价值。

在之前的几篇博客里面,已经介绍了,类的底层结构,还有isa的走位流程,元类的继承链,对象方法,类方法的存放位置,以及如何通过lldb获取成员和属性、对象方法,类方法。

iOS底层探索之类的结构(上)
iOS底层探索之类的结构(中)

本篇博客主要做一些补充和扩展。

成员变量和属性

开发年限比较久的ios程序员,都知道在iOS5之前,经常看到一个大括号里面定义了成员变量,同时用了@property声明,而且还在@implementation中使用@synthesize方法。

其实,发生这种状况根本原因是苹果将默认编译器从GCC转换为LLVM(low level virtual machine),才不再需要为属性声明实例变量了。

在没有更改之前,属性的正常写法需要成员变量+ @property + @synthesize成员变量三个步骤。 更换为LLVM之后,编译器在编译过程中发现没有新的实例变量后,就会生成一个下划线开头的实例变量。因此现在我们不必在声明一个实例变量。

现在@property声明的属性不仅仅默认给我们生成一个_类型的成员变量,同时也会生成setter/getter方法。
类信息
clang源码

  • 属性 = 带下划线成员变量 + setter + getter ⽅法
  • 实例变量 : 特殊的成员变量 (类的实例化)

从以上clang底层源码也可以验证,@property声明的属性默认会生成一个_类型的成员变量,同时也会生成setter/getter方法。

objc_setProperty

但是我们从上面的底层源码发现了一个问题,有的set方法里面是通过objc_setProperty获取属性,有的是通过内存的平移。这是什么原因了呢?通过对比不同属性设置带有copy和不带有copy的底层源码分析,可以找到原因是copy这个设置的问题!那么为验证这个结论,我们去底层看看!

LLVM

那我们去llvm源码里面看看,objc_setProperty的实现逻辑,你怎么就知道去llvm不是去objc源码看呢?请看下图分析
在这里插入图片描述
首先在底层也就是下层,苹果的源码是固定的,是不会改变的(这里的不变是指不会根据你代码来改变,因为底层逻辑已经写好了)。在上层,我们程序开发人员,会写很多中属性,那么就会有各种各种的奇奇怪怪的代码出现,就有各种的set形式的代码,比如setName、setAge、setXX。。。等等。

这对编译器来说,只是_cmd的方法名字不一样,那就需要一个中间层来把这些奇奇怪怪的,做一下处理,以便于和底层去交互,因为底层逻辑已经封装好,不可能动态的改变的。那我们去研究objc_setProperty就不可能在运行时了,只能在编译时了,所以我们去就llvm里面看看!

那上层和objc_setProperty之间有着怎样的联系呢?

getGetPropertyFn
我们在llvm里面找到了getGetPropertyFn这个函数,是获取创建的CreateRuntimeFunction,里面有objc_setProperty这个参数。那么为什么创建呢?继续往下看看,在哪里调用了
getGetPropertyFn()
既然调用了,肯定是有条件的,是真的是是因为propertycopy吗?我们找到条件,就可以反推知道objc_setProperty的作用了。继续往下看
PropertyImplStrategy
看看上面的代码有没有发现什么?这有个属性imp的策略PropertyImplStrategy,那么再继续往下找
PropertyImplStrategy
看到没有,如果IsCopy为真就Kind = GetSetProperty,这也就验证了博客开篇的猜想,只有属性里面用copy修饰的,才会有objc_setProperty

代码获取类的信息

iOS底层探索之类的结构(上)
iOS底层探索之类的结构(中)
在之前的博客里面,我们已经知道了,可以在控制台通过lldb获取类的信息,那么现在我们通过代码来获取下。

class_copyMethodList

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

class_getInstanceMethod

//获取实例方法
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);
}

class_getClassMethod

//获取类方法
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);
}

class_getMethodImplementation

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

}

代码测试验证
代码测试验证

IMP,SEL

  • SEL : 类成员方法的指针,但不同于C语言中的函数指针,函数指针直接保存了方法的地址,但SEL只是方法编号。
  • IMP:一个函数指针,保存了方法的地址
  • SEL和IMP的关系就:SEL就相当于书本的⽬录标题,IMP就是书本的⻚码,函数就是具体页码对应的内容。

扩展

我们已经学了这么多类的相关的知识了,那么我们看看下面这个面试题

    BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; 
    BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; 
    BOOL re3 = [(id)[JPPerson class] isKindOfClass:[JPPerson class]];    
    BOOL re4 = [(id)[JPPerson class] isMemberOfClass:[JPPerson class]];    
 
    NSLog(@" re1 :%hhd\\n re2 :%hhd\\n re3 :%hhd\\n re4 :%hhd\\n,%hhd,%hhd",re1,re2,re3,re4,re9,re10);

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

代码运行结果
代码运行结果

+ isKindOfClass

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- isKindOfClass

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

+ isMemberOfClass

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- isMemberOfClass

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}
- (Class)class {
    return object_getClass(self);
}

分析

  • re1 传入的是NSObject的类,获取元类NSObject不等,继续寻找获取元类的父类为NSObject与传入的值相等,返回true
  • re2是NSObject类调用类方法isMemberOfClassNSObject类比较,很明显,NSObject的元类NSObject本身并不相等,所以返回false
  • re3 传入的是JPPerson的类,获取元类JPPerson不等,继续寻找获取元类的父类为NSObject的元类,与传入的值依旧不等,继续往上NSObject元类的父类为NSObject依旧不等,再往上就是nil,最后返回false
    -re4JPPerson类调用类方法isMemberOfClassJPPerson类比较,JPPerson的元类JPPerson本身并不相等,所以返回false.
  • re5 传入的是NSObject的实例,获取对象的类,与NSObject相等,返回true
  • re7 传入的是JPPerson的实例,获取对象的类,与JPPerson相等,返回true
    -re8JPPerson的实例调用实例方法isMemberOfClassJPPerson类比较,明显的他们是相同的,所以返回true

更多内容持续更新

🌹 请动动你的小手,点个赞👍🌹

🌹 喜欢的可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我,哈哈😁🌹

🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹

以上是关于iOS底层探索之类的结构(下)的主要内容,如果未能解决你的问题,请参考以下文章

iOS底层原理之类,元类数据结构探索(下)

iOS底层探索之类的结构—cache分析(下)

iOS底层探索之类的结构(上)

iOS底层探索之类的结构—cache分析(上)

iOS底层探索之Runtime:运行时&方法的本质

iOS开发底层之类的底层探究-05