美文网首页iOS
iOS底层探索之类的结构(下)

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

作者: 俊而不逊 | 来源:发表于2021-06-23 17:28 被阅读0次
    类的结构探索分析.png

    在之前的几篇博客里面,已经介绍了,类的底层结构,还有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底层探索之类的结构(下)

        本文链接:https://www.haomeiwen.com/subject/ctgbyltx.html