美文网首页
Objc4-818底层探索(五):类信息内容补充

Objc4-818底层探索(五):类信息内容补充

作者: ShawnAlex | 来源:发表于2021-06-21 18:48 被阅读0次

    建议先看下Objc4-818底层探索(三):isa
    建议先看下Objc4-818底层探索(四):isa与类

    主要针对于类信息, 补充一些知识点:

    知识点1:成员变量与属性

    成员变量

    @interface ViewController (){
    
        NSString *name ; // 成员变量, 实例变量
        int age;   // 成员变量, 基本数据类型变量
        id data;   // 成员变量, 实例变量
    }
    
    • 通常在.h/.m文件以@interface{ } 形式定义的变量
    成员变量的访问权限
    @interface ViewController (){
        
        NSString * A; 
        
    @public
        NSString * B;
        
    @protected
        NSString * C;
        
    @private
        NSString * D;
        
        @package
        NSString * E;
    }
    
    • @public:在任何地方都能直接访问对象的成员变量

    • @private:只能在当前类的对象方法中直接访问, 如果子类要访问需要调用父类的get/set方法

    • @protected:可以在当前类及其子类对象方法中直接访问,变量默认的访问权限就是 protected

    • @package:只能在framework内部的类是@protected的权限,对于外部的类是@private,相当于框架级的保护权限,适合使用在静态库.a中。

    实例变量与基础数据类型变量
    • 如果成员变量是一个类(类的实例化), 则这个变量实例变量, 例如上面例子name, data (id 是 OC特有的类型。从本质上讲, id 等同于 (void *))都是实例变量。而ageint型, 像int, double, short等是基础数据类型变量

    • 实例变量 + 基础数据类型变量 = 成员变量

    属性(属性变量)

    @property (nonatomic, strong) NSString *hobby;  //属性
    
    • 通常在.h/.m文件以@interface{ } 形式定义的变量

    • 编译器会自动为属性生成set, get方法, 以及生成成员变量_documentsDirectory即成员变量名前加下划线形式, 详细看下面的知识点2

    • 属性是用于与其他对象交互的变量, 正因为要与其他对象交互, 就有了属性修饰符或者叫属性特质, 如:nonatomic, readwrite, copy 等等


    知识点2:关于成员变量和属性在底层

    创建一个只有main的项目 (创建main项目可以参考我这一篇章: IOS创建个只有main.m工程) 。里面添加一个对象继承NSObject, 并添加一些的成员变量和属性。

    @interface SATest : NSObject {
        NSString *insStr;
    }
    
    @property (atomic, copy) NSString *atoCopyStr;
    @property (nonatomic, copy) NSString *nonCopyStr;
    @property (atomic) NSString *atoStr;
    @property (nonatomic) NSString *nonStr;
    
    @end
    

    clang一下, 看下前端编译器中底层实现

    clang -rewrite-objc main.m -o main.cpp
    
    clang
    • @property属性在底层取消了属性而是转换成"下划线+成员变量"以及set, get方法的形式

    • @ interface{}成员变量在底层还是以成员变量的存放, 不会有set, get方法

    之前的探索我们还能得到以下结论

    • 通过@interface XXXX {}定义的成员变量,会存储在bits属性中,通过bits → data() → ro() → ivars获取成员变量列表,除了包括成员变量,还包括属性成员变量

    • 通过@property定义的属性,不仅仅在ro() → ivars中以下划线存在, 并且还存储在bits属性中,通过bits --> data() --> properties() --> list获取属性列表,其中只包含property属性


    知识点3: 关于objc_setProperty

    objc_setProperty

    在clang底层中, 会发现属性有些set方法里面有一个objc_setProperty方法, 而且有些方法有, 有些没有, 我们接下来探索下objc_setProperty。818源码查找下objc_setProperty

    objc818下objc_setProperty objc818下objc_setProperty

    可看到除了objc_setProperty以外还有objc_setProperty_atomic, objc_setProperty_nonatomic, objc_setProperty_atomic_copy, objc_setProperty_nonatomic_copy而且他们都直接调用一个reallySetProperty

    static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
    {
        if (offset == 0) {
            object_setClass(self, newValue);
            return;
        }
    
        id oldValue;
        id *slot = (id*) ((char*)self + offset);
    
        if (copy) {
            newValue = [newValue copyWithZone:nil];
        } else if (mutableCopy) {
            newValue = [newValue mutableCopyWithZone:nil];
        } else {
            if (*slot == newValue) return;
            newValue = objc_retain(newValue);
        }
    
        if (!atomic) {
            oldValue = *slot;
            *slot = newValue;
        } else {
            spinlock_t& slotlock = PropertyLocks[slot];
            slotlock.lock();
            oldValue = *slot;
            *slot = newValue;        
            slotlock.unlock();
        }
    
        objc_release(oldValue);
    }
    

    可看到他在底层的操作主要是对新增的retain, 对旧值的release

    reallySetProperty

    objc_setProperty相当于一个承上启下接口, 上层许多个set方法直接对接llvm, 则llvm需要针对每一个set做对应处理, 则会很麻烦。所以苹果设置了一个中间层(接口隔离层)objc_setProperty, 令他处理一部分set方法, 保证无论上层怎么变, 传入llvm的格式不会有变化。主要是达到上下层接口隔离的目的。

    objc_setProperty
    LLVM中的objc_setProperty

    看底层可看出, 有些需要有些需要objc_setProperty, 有些并无调用objc_setProperty, 这块要看底层LLVM, 看下LLVM怎么处理的objc_setProperty。这里我们要用逆推法

    [第一步] 查询objc_setProperty

    因为要查找objc_setProperty, 全局搜索objc_setProperty。发现getSetPropertyFn()方法中调用CGM.CreateRuntimeFunction(FTy, "objc_setProperty");CGM创建runtime函数objc_setProperty

    • Fn: 为function函数的缩写
    objc_setProperty
    [第二步] 查询getSetPropertyFn()

    全局搜索什么方法调用getSetPropertyFn(), 可看到GetPropertySetFunction调用

    getSetPropertyFn()
    [第三步] 查询GetPropertySetFunction

    全局搜索什么方法调用GetPropertySetFunction, 可看到如果case为GetSetPropertySetPropertyAndExpressionGet会调用GetPropertySetFunction

    GetPropertySetFunction()

    -其中UseOptimizedSetter(CGM)这个判断后面标注为// 10.8 and iOS 6.0 code and GC is off即10.8和iOS 6.0代码,GC关闭, 所以新版本直接走GetPropertySetFunction

    [第四步] 查询GetSetProperty

    全局搜索什么方法调用GetSetProperty, 可看见PropertyImplStrategy中有

    GetSetProperty GetSetProperty
    • IsCopy: 如果属性修饰符为copy, 直接会调用objc_setProperty

    • Retain & !IsAtomic: 如果属性修饰符为retain, 并且为非原子性nonatomic, 也会调用objc_setProperty

    [第五步] 验证objc_setProperty
    验证

    可看到 copy或者 retain&nonatomic,会调用objc_setProperty


    知识点4: 关于编码

    编码

    clang命令之后我们会发现会出现很多T, @, v...这些符号, 那这些又是什么呢?

    iOS系统提供了一个叫做@encode指令,可以将具体的类型表示成字符串编码

    1. @encode实际上是编译器指令其中的一种。
    2. @encode能够返回一个Objective-C 类型编码(Objective-C Type Encodings)。
    3. @encode是一种编译器内部表示的字符串,方便识别,类似于 ANSI C 的 typeof 操作。

    具体看下苹果官方定义:

    苹果Type Encodings:
    https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html

    Type Encodings

    苹果Property Type and Functions:
    https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW1

    Property Type and Functions

    当然我们也可以通过命令查看编码

    #pragma mark - 各种类型编码
    void lgTypes(void){
        NSLog(@"char --> %s",@encode(char));
        NSLog(@"int --> %s",@encode(int));
        NSLog(@"short --> %s",@encode(short));
        NSLog(@"long --> %s",@encode(long));
        NSLog(@"long long --> %s",@encode(long long));
        NSLog(@"unsigned char --> %s",@encode(unsigned char));
        NSLog(@"unsigned int --> %s",@encode(unsigned int));
        NSLog(@"unsigned short --> %s",@encode(unsigned short));
        NSLog(@"unsigned long --> %s",@encode(unsigned long long));
        NSLog(@"float --> %s",@encode(float));
        NSLog(@"bool --> %s",@encode(bool));
        NSLog(@"void --> %s",@encode(void));
        NSLog(@"char * --> %s",@encode(char *));
        NSLog(@"id --> %s",@encode(id));
        NSLog(@"Class --> %s",@encode(Class));
        NSLog(@"SEL --> %s",@encode(SEL));
        int array[] = {1,2,3};
        NSLog(@"int[] --> %s",@encode(typeof(array)));
        typedef struct person{
            char *name;
            int age;
        }Person;
        NSLog(@"struct --> %s",@encode(Person));
        
        typedef union union_type{
            char *name;
            int a;
        }Union;
        NSLog(@"union --> %s",@encode(Union));
    
        int a = 2;
        int *b = {&a};
        NSLog(@"int[] --> %s",@encode(typeof(b)));
    }
    
    编码代码打印

    知识点5: 关于类与isa走位图面试题

    先看下isa走位图

    isa走位图

    熟悉走位图之后我们看几个题目

    题目1:

    看下这个例子返回打印结果(回复有或者没有即可)

    //- (void)sayHello;
    //+ (void)sayHappy;
    
    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));
       
        NSLog(@"method1: %p", method1);
        NSLog(@"method2: %p", method2);
        NSLog(@"method3: %p", method3);
        NSLog(@"method4: %p", method4);
        
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            
            SATest *test = [SATest alloc];
            Class pClass = object_getClass(test);
        
            lgInstanceMethod_classToMetaclass(pClass);
            //lgClassMethod_classToMetaclass(pClass);
            //lgIMP_classToMetaclass(pClass);
            
        }
        return 0;
    }
    
    解题思路:

    先看下class_getInstanceMethod源码

    /***********************************************************************
    * class_getInstanceMethod.  Return the instance method for the
    * specified class and selector.
    **********************************************************************/
    Method class_getInstanceMethod(Class cls, SEL sel)
    {
        if (!cls  ||  !sel) return nil;
    
        // This deliberately avoids +initialize because it historically did so.
    
        // This implementation is a bit weird because it's the only place that 
        // wants a Method instead of an IMP.
    
    #warning fixme build and search caches
            
        // Search method lists, try method resolver, etc.
        lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
    
    #warning fixme build and search caches
    
        return _class_getMethod(cls, sel);
    }
    

    就是判断类是否有指定的实例方法, 实例方法存在当前类中, 类方法存在元类中

    // - (void)sayHello; 实例方法
    // + (void)sayHappy; 类方法

    • class_getInstanceMethod(pClass, @selector(sayHello));, 判断当前类是否有实例方法sayHello, 有
    • class_getInstanceMethod(metaClass, @selector(sayHello));, 判断元类是否有实例方法sayHello, 无
    • class_getInstanceMethod(pClass, @selector(sayHappy));, 判断当前类是否有类方法sayHappy, 无
    • class_getInstanceMethod(metaClass, @selector(sayHappy));, 判断元类是否有类方法sayHappy, 有
    结果:
    问题1结果

    题目2:

    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));
        Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
        Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
        
        NSLog(@"method1: %p", method1);
        NSLog(@"method2: %p", method2);
        NSLog(@"method3: %p", method3);
        NSLog(@"method4: %p", method4);
    }
    
    /***********************************************************************
    * class_getClassMethod.  Return the class method for the specified
    * class and selector.
    **********************************************************************/
    Method class_getClassMethod(Class cls, SEL sel)
    {
        if (!cls  ||  !sel) return nil;
    
        return class_getInstanceMethod(cls->getMeta(), sel);
    }
    
        Class getMeta() {
            if (isMetaClassMaybeUnrealized()) return (Class)this;
            else return this->ISA();
        }
    

    那么

    • class_getClassMethod(pClass, @selector(sayHello)), 在当前类中找实例方法sayHello, 无
    • class_getClassMethod(metaClass, @selector(sayHello)), 在元类中找实例方法sayHello, 无
    • class_getClassMethod(pClass, @selector(sayHappy)), 在当前类中找类方法sayHappy, 有
    • class_getClassMethod(metaClass, @selector(sayHappy)), 在元类中找类方法sayHappy, 因为传入的是元类, 这里cls->getMeta()返回元类本身, 所以有
    结果:
    问题2结果

    题目3:

    #import "SATest.h"
    
    void lgKindofDemo(void){
        
        BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
        BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
        BOOL re3 = [(id)[SATest class] isKindOfClass:[SATest class]];
        BOOL re4 = [(id)[SATest class] isMemberOfClass:[SATest class]];
        NSLog(@"\n re1 :%hhd \n re2 :%hhd \n re3 :%hhd \n re4 :%hhd \n",re1,re2,re3,re4);
    
        BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
        BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
        BOOL re7 = [(id)[SATest alloc] isKindOfClass:[SATest class]];
        BOOL re8 = [(id)[SATest alloc] isMemberOfClass:[SATest class]];
        NSLog(@"\n re5 :%hhd \n re6 :%hhd \n re7 :%hhd \n re8 :%hhd \n",re5,re6,re7,re8);
    }
    
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            lgKindofDemo();
            NSLog(@"Hello World");
        
        }
        return 0;
        
    }
    

    解题思路:

    需要先看isKindOfClass, isMemberOfClass底层

    isMemberOfClass

    先看下isMemberOfClass底层实现

    // 类方法
    + (BOOL)isMemberOfClass:(Class)cls {
        // 判断当前元类是否与传入类相等
        return self->ISA() == cls;
    }
    
    // 实例方法方法
    - (BOOL)isMemberOfClass:(Class)cls {
        // 判断当前类是否与传入类相等
        return [self class] == cls;
    }
    
    // 类方法
    + (BOOL)isKindOfClass:(Class)cls {
        // 循环判断
        // 元类 vs 传入类
        // 父元类 vs 传入类
        // 根元类 vs 传入类
        // 根类 vs 传入类
        for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    
    // 实例方法
    - (BOOL)isKindOfClass:(Class)cls {
        // 循环判断
        // 类 vs 传入类
        // 父类 vs 传入类
        // 根类 vs 传入类
        // nil vs 传入类
        for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    
    • [XXX class]: 取得类, 调用类方法
    • [XXX alloc]: 开辟对象, 调用对象/实例方法
    1. [(id)[NSObject class] isKindOfClass:[NSObject class]];: 类方法, 判断NSObject即与传入类NSObject。相等

    2.[(id)[NSObject class] isMemberOfClass:[NSObject class]];: 类方法, 判断NSObject元类根元类与传入类NSObject。不相等

    3.[(id)[SATest class] isKindOfClass:[SATest class]]: 类方法, 依次判断SATest父类, 根类, nil与传入类SATest。不相等

    4.[(id)[SATest class] isMemberOfClass:[SATest class]];: 类方法, 判断SATest元类元类与传入类SATest。不相等

    5.[(id)[NSObject alloc] isKindOfClass:[NSObject class]];: 实例方法, 循环判断NSObject与传入类NSObject。相等

    6.[(id)[NSObject alloc] isMemberOfClass:[NSObject class]];: 实例方法, 判断NSObject与传入类NSObject。相等

    7.[(id)[SATest alloc] isKindOfClass:[SATest class]];: 实例方法, 循环判断SATest与传入类SATest。相等

    1. [(id)[SATest alloc] isMemberOfClass:[SATest class]];: 实例方法, 判断SATest类与传入类SATest。相等

    固结果为 1, 0, 0, 0, 1, 1, 1, 1

    结果:

    问题3结果

    但实际上这么思考, 结果没问题但过程有问题, 因为我们打开终端, 会发现系统实际上没有调用 isKindOfClass, 而是调用objc_opt_isKindOfClass, 系统重定向了isKindOfClass方法。

    objc_opt_isKindOfClass objc_opt_isKindOfClass

    那么我们需要查找objc_opt_isKindOfClass

    // Calls [obj isKindOfClass]
    BOOL
    objc_opt_isKindOfClass(id obj, Class otherClass)
    {
    #if __OBJC2__
        if (slowpath(!obj)) return NO;
    
        // 做了一步obj->getIsa() 获取isa, 即
        // 如果obj 是对象,则isa是类,
        // 如果obj是类,则isa是元类
        Class cls = obj->getIsa();
        // 缓存中是否能查找到当前类的isKindOfClass方法
        // 找到走if 
        if (fastpath(!cls->hasCustomCore())) {
            // 循环判断
            // 如果是对象
            // 当前类 vs 传入类
            // 父类 vs 传入类
            // 根类 vs 传入类
            // nil vs 传入类
    
            // 如果是类
            // 元类 vs 传入类
            // 父元类 vs 传入类
            // 根元类 vs 传入类
            // 根类 vs 传入类
            for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
                if (tcls == otherClass) return YES;
            }
            return NO;
        }
    #endif
         // 没找到走消息转发, 发送`isKindOfClass`
        return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
    }
    
    // class or superclass has default new/self/class/respondsToSelector/isKindOfClass
    // 判断当前类是否有个默认的 isKindOfClass
    #define FAST_CACHE_HAS_DEFAULT_CORE   (1<<15)
        bool hasCustomCore() const {
            return !cache.getBit(FAST_CACHE_HAS_DEFAULT_CORE);
        }
    

    所以回头再看下1, 3, 5, 7

    1. [(id)[NSObject class] isKindOfClass:[NSObject class]];: 判断NSObject即与传入类NSObject。相等

    3.[(id)[SATest class] isKindOfClass:[SATest class]]: 循环判断SATest元类, 根元, 与传入类SATest。不相等

    5.[(id)[NSObject alloc] isKindOfClass:[NSObject class]];: 判断NSObject即与传入类NSObject 。相等

    7.[(id)[SATest alloc] isKindOfClass:[SATest class]];: 判断SATest类即与传入类SATest类。 相等


    知识点6: armv7, arm64, i386 , x86_64解答

    • armv7 | armv7s | arm64是ARM处理器的指令集

    • i386 | x86_64是Mac 处理的指令集。

    下面是指令集在设备的使用

    arm64iPhone5S之后的iPhone系列, iPad Air以及iPad mini2 之后的iPad系统

    armv7siPhone5iPhone5CiPad4(iPad with Retina Display)

    armv7iPhone4iPhone4SiPadiPad2iPad3iPad miniiPod Touch 3GiPod Touch4



    i386: 是针对intel通用的微处理器32位处理器

    x86_64: 是针对x86架构64位处理器

    • 模拟器32位处理器测试要i386的架构

    • 模拟器64位处理器测试要x86_64的架构

    • 真机32位处理器要armv7 或者armv7s

    • 真机64位处理器要arm64架构

    相关文章

      网友评论

          本文标题:Objc4-818底层探索(五):类信息内容补充

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