美文网首页V10(逻辑教育)iOS 面试题整理
iOS-底层探索06:isa 经典面试题分析

iOS-底层探索06:isa 经典面试题分析

作者: differ_iOSER | 来源:发表于2020-09-17 10:49 被阅读0次

    iOS 底层探索 文章汇总

    目录

    一、类在内存中存在几份?

    类在内存中存在几份实际上是说类对象在内存中存在几份。由于类的信息在内存中永远只存在一份,所以 类对象只有一份

    二、objc_object 与 对象的关系

    • 所有的对象 都是以 objc_object为模板继承过来的

    • 所有的对象 是 来自 NSObject(OC) ,但是真正到底层的 是一个objc_object(C/C++)的结构体类型

    【总结】objc_object对象的关系 是 继承关系

    三、什么是 属性 & 成员变量 & 实例变量 ?

    • 属性(property):在OC中是通过@property开头定义,属性 =ivar + setter + getter

    • 成员变量(ivar):在OC的类中{}中定义的

    • 实例变量:通过当前对象类型,具备实例化的变量,是一种特殊的成员变量,例如 NSObjectUILabelUIButton

    四、成员变量 和 实例变量什么区别?

    • 实例变量(即成员变量中的对象变量 就是 实例变量):以实例对象实例化来的,是一种特殊的成员变量

    -NSString常量类型, 因为不能添加属性,如果定义在类中的{}中,是成员变量

    • 成员变量中 除去基本数据类型NSString,其他都是 实例变量(即可以添加属性成员变量),实例变量主要是判断是不是对象

    五、元类 中为什么会有 类对象 的 类方法?

    UIView实际上也是一个对象,叫做类对象,那么类对象的类又是什么呢?就是元类。元类也是一个对象,它的类又是什么呢?根元类。根元类也是一个对象,它的类又是什么呢?它自己
    元类对象:OC类方法元类存在的根本原因。因为元类对象中存储着类对象调用的方法也就是类方法。元类的定义和创建都是编译器自动完成的,无需人为干涉,而且大部分时候都是倾向于隐藏的。

    六、实例对象、类对象、元类对象到底有什么区别呢?

    • 实例对象实例对象拷贝了实例所属的类的成员变量,但不拷贝类定义的方法。当调用实例方法时,需要到类的方法列表里面去寻找方法的函数指针。

    • 类对象:是一个功能完整的对象,它没有自己的实例变量。(这里要与类的成员变量区别开来,类的成员变量是属于实例对象的,而不是属于类对象的。类方法才是属于类对象自己的)。类对象中存储着成员变量实例方法列表

    • 元类对象OC类方法元类存在的根本原因。因为元类对象中存储着类对象调用的方法也就是类方法。元类的定义和创建都是编译器自动完成的,无需人为干涉,而且大部分时候都是倾向于隐藏的。

    七、探究如下方法

    1. class_copyMethodList
    2. class_getInstanceMethod
    3. class_getClassMethod
    4. class_getMethodImplementation

    首先定义一个类:NAPerson

    @interface NAPerson : NSObject
    - (void)sayHello;
    + (void)sayHappy;
    @end
    
    @implementation NAPerson
    - (void)sayHello {
        NSLog(@"NAPerson say : Hello!!!");
    }
    + (void)sayHappy {
        NSLog(@"NAPerson say : Happy!!!");
    }
    @end
    

    主方法调用如下:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NAPerson *person = [NAPerson alloc];
            Class pClass     = object_getClass(person);
            naObjc_copyMethodList(pClass);
    
            naInstanceMethod_classToMetaclass(pClass);
            naClassMethod_classToMetaclass(pClass);
            naIMP_classToMetaclass(pClass);
        }
        return 0;
    }
    

    思考以下每个方法的打印信息:

    获取类的方法列表
    void naObjc_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));
            
            NALog(@"Method, name: %@", key);
        }
        free(methods);
    }
    
    获取类的实例方法
    void naInstanceMethod_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));
        
        NALog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
    }
    
    获取类的类方法
    void naClassMethod_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));
        
        NALog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
    }
    
    获取方法的实现
    void naIMP_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));
    
        NALog(@"%s - %p-%p-%p-%p",__func__,imp1,imp2,imp3,imp4);
    }
    
    以下是几个函数调用的打印结果: WX20200916-173423.png
    naObjc_copyMethodList函数 分析

    在这个函数中,主要是获取NAPerson类中的方法列表,从实例方法存储在类中,类方法存储在元类中可以得知,NAPerson的方法列表打印结果只有sayHello方法,如果NAPerson类定义了属性该方法还会返回set/get方法。

    naInstanceMethod_classToMetaclass函数 分析
    • 实例方法存在类对象中、类方法存在元类对象中
    • 实例方法就是类对象的实例方法
    • 类方法就是类元类对象的实例方法
    naInstanceMethod_classToMetaclass函数 分析
    • 获取类方法就是返回元类的实例方
    • 获取元类的类方法就是返回元类自己的实例方法
    Method class_getClassMethod(Class cls, SEL sel)
    {
        if (!cls  ||  !sel) return nil;
    
        return class_getInstanceMethod(cls->getMeta(), sel);
    }
    
    Class getMeta() {
            if (isMetaClass()) return (Class)this;
            else return this->ISA();
        }
    
    naIMP_classToMetaclass函数 分析

    class_getMethodImplementation 主要是返回方法的具体实现,针对这个方法有如下官方说明

    该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函数指针可能是一个指向runtime内部的函数,而不一定是方法的实际实现。如果类实例无法响应selector,则返回的函数指针将是运行时消息转发机制的一部分.

    IMP class_getMethodImplementation(Class cls, SEL sel)
    {
        IMP imp;
    
        if (!cls  ||  !sel) return nil;
    
        //查找方法实现
        imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
    
        //如果没有找到,则进行消息转发
        if (!imp) {
            return _objc_msgForward;
        }
    
        return imp;
    }
    
    lookUpImpOrNil(id obj, SEL sel, Class cls, int behavior = 0)
    {
        return lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL);
    }
    
    OBJC_EXPORT void
    _objc_msgForward(void /* id receiver, SEL sel, ... */ ) 
    
    • 中能找到实例方法的实现、元类中能找到类方法的实现所以imp1imp4能打印出对应方法的实现地址
    • 元类中不能找到实例方法的实现、中也找不到类方法的实现,所以imp2imp3打印出了相同的_objc_msgForward方法实现地址

    八、iskindOfClass & isMemberOfClass 的理解

    对应的代码如下:

    BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
    BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
    BOOL re3 = [(id)[NAPerson class] isKindOfClass:[NAPerson class]];       //
    BOOL re4 = [(id)[NAPerson class] isMemberOfClass:[NAPerson class]];     //
    NSLog(@" 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)[NAPerson alloc] isKindOfClass:[NAPerson class]];       //
    BOOL re8 = [(id)[NAPerson alloc] isMemberOfClass:[NAPerson class]];     //
    NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
    

    相关的源代码如下:

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

    需要注意的是调用的是类方法还是实例方法
    打印结果如下:1000 - 1111
    理解isKindOfClass、isMemberOfClass实现的原理很重要,但更重要是能记住、并能在面试中灵活使用

    在使用断点调试时我们会发现isMemberOfClass类方法和实例方法都会调用,但是isKindOfClass的两个方法都不会调用。通过汇编调试可发现isKindOfClass类方法和实例方法调用的都是objc_opt_isKindOfClass方法:

    WX20200917-104346.png

    如何使用汇编调试

    objc_opt_isKindOfClass方法源码如下:

    // Calls [obj isKindOfClass]
    BOOL
    objc_opt_isKindOfClass(id obj, Class otherClass)
    {
    #if __OBJC2__
        if (slowpath(!obj)) return NO;
        Class cls = obj->getIsa();
        if (fastpath(!cls->hasCustomCore())) {
            for (Class tcls = cls; tcls; tcls = tcls->superclass) {
                if (tcls == otherClass) return YES;
            }
            return NO;
        }
    #endif
        return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
    }
    

    调用objc_opt_isKindOfClass方法主要是因为在llvm中编译时对其进行了优化处理。
    打印结果依然是:1000 - 1111

    九、iOS中内省的几个方法?class方法和objc_getClass方法有什么区别?

    1. 什么是内省?
    • 在计算机科学中,内省是指计算机程序在运行时(Run time)检查对象(Object)类型的一种能力,通常也可以称作运行时类型检查。
    • 不应该将内省和反射混淆。相对于内省,反射更进一步,是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。
    1. iOS 中内省的几个方法?
    • isMemberOfClass://对象是否是某个类型的对象
    • isKindOfClass: //对象是否是某个类型或某个类型子类的对象
    • isSubclassOfClass://某个对象是否是另一个类型的子类
    • isAncestorOfObject://某个类对象是否是另一个类型的父类
    • respondsToSelector://是否能响应某个方法
    • conformsToProtocol://是否遵循某个协议
    1. class方法和object_getClass方法有什么区别?
    • 实例class方法就直接返回object_getClass(self)
    • class方法直接返回self,而object_getClass(类对象),则返回元类
      参考链接





    参考文章
    iOS-底层原理 09:类 & isa 经典面试题分析

    相关文章

      网友评论

        本文标题:iOS-底层探索06:isa 经典面试题分析

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