美文网首页底层
iOS-OC底层05:面试题分析

iOS-OC底层05:面试题分析

作者: MonKey_Money | 来源:发表于2020-09-15 16:00 被阅读0次

    实例方法和类方法的分析

    @interface LGPerson : NSObject
    
    @property (nonatomic, copy) NSString *name;
    -(void)myInstanceMethod;
    
    +(void)myClassMethod;
    @end
    @implementation LGPerson
    +(void)myClassMethod {
        
    }
    -(void)myInstanceMethod {
        
    }
    @end
    @interface LGTeacher : LGPerson
    -(void)myTeacherInstanceMethod;
    
    +(void)myTeacherClassMethod;
    @end
    @implementation LGTeacher
    
    -(void)myTeacherInstanceMethod {
        
    }
    
    +(void)myTeacherClassMethod {
        
    }
    @end
    

    我们通过class的c++实现objc_class,在进行内存平移访问,得出LGPerson的方法共4个方法分别如下

    (lldb) p $6.get(0)
    (method_t) $7 = {
      name = "myInstanceMethod"
      types = 0x0000000100000f82 "v16@0:8"
      imp = 0x0000000100000dc0 (KCObjc`-[LGPerson myInstanceMethod])
    }
    (lldb) p $6.get(1)
    (method_t) $8 = {
      name = ".cxx_destruct"
      types = 0x0000000100000f82 "v16@0:8"
      imp = 0x0000000100000e30 (KCObjc`-[LGPerson .cxx_destruct])
    }
    (lldb) p $6.get(2)
    (method_t) $9 = {
      name = "name"
      types = 0x0000000100000f96 "@16@0:8"
      imp = 0x0000000100000dd0 (KCObjc`-[LGPerson name])
    }
    (lldb) p $6.get(3)
    (method_t) $10 = {
      name = "setName:"
      types = 0x0000000100000f9e "v24@0:8@16"
      imp = 0x0000000100000e00 (KCObjc`-[LGPerson setName:])
    }
    

    我们没看到类方法我们猜测类方法可能在元类中,

    (lldb) p  $16.list
    (method_list_t *const) $17 = 0x0000000100002088
    (lldb) p *$17
    (method_list_t) $18 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 1
        first = {
          name = "myClassMethod"
          types = 0x0000000100000f82 "v16@0:8"
          imp = 0x0000000100000db0 (KCObjc`+[LGPerson myClassMethod])
        }
      }
    }
    (lldb) p $18.get(0)
    (method_t) $19 = {
      name = "myClassMethod"
      types = 0x0000000100000f82 "v16@0:8"
      imp = 0x0000000100000db0 (KCObjc`+[LGPerson myClassMethod])
    }
    

    具体寻址过程,可以参考类结构分析

    class_getInstanceMethod

    从源码看class_getInstanceMethod的方法执行过程
    class_getInstanceMethod===>_class_getMethod()===>getMethod_nolock()


    instanceMethod.png
            LogInstanceMethod_classAndMetaClass([LGTeacher class]);
    
    void LogInstanceMethod_classAndMetaClass(Class pClass) {
        const char * className =class_getName(pClass);
       Class metaClass  = objc_getMetaClass(className);
        Method method1 = class_getInstanceMethod(pClass, @selector(myTeacherInstanceMethod));
        Method method2 = class_getInstanceMethod(pClass, @selector(myTeacherClassMethod));
        Method method3 = class_getInstanceMethod(pClass, @selector(myInstanceMethod));
        Method method4 = class_getInstanceMethod(pClass, @selector(myClassMethod));
        
        Method method5 = class_getInstanceMethod(metaClass, @selector(myTeacherInstanceMethod));
        Method method6 = class_getInstanceMethod(metaClass, @selector(myTeacherClassMethod));
        Method method7 = class_getInstanceMethod(metaClass, @selector(myInstanceMethod));
        Method method8 = class_getInstanceMethod(metaClass, @selector(myClassMethod));
        NSLog(@"\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p",method1,method2,method3,method4,method5,method6,method7,method8);
        
    }
    打印结果
    0x100002110
    0x0
    0x1000021e0
    0x0
    0x0
    0x1000020a8
    0x0
    0x100002178
    

    class_getInstanceMethod方法总结

    LGTeacher.class
    @selector(myTeacherInstanceMethod):自己的实例方法,所以有值
    @selector(myTeacherClassMethod):自己的类方法,返回为空
    @selector(myInstanceMethod):父类的实例方法,因为class_getInstanceMethod会遍历LGTeacher的父类,所以有返回值
    @selector(myClassMethod):父类的类方法,所以返回有值
    LGTeacher的元类分析,LGTeacher的元类的实例方法也就是LGTeacher类的类方法
    @selector(myTeacherInstanceMethod):元类的实例方法是本类的类方法,所以返回为空
    @selector(myTeacherClassMethod):本类的类方法就是本元类的实例方法,所以返回有值
    @selector(myInstanceMethod):本类的实例方法,不在本元类的实例方法中,所以为空
    @selector(myClassMethod):通过流程图可知,本类的实例方法还包括父类的实例方法,所以父类的类方法也是本元类的实例方法(会通过继承树寻找)

    class_getClassMethod

    我们先看一下源码

    Method class_getClassMethod(Class cls, SEL sel)
    {
        if (!cls  ||  !sel) return nil;
    
        return class_getInstanceMethod(cls->getMeta(), sel);
    }
    //cls->getMeta()
      Class getMeta() {
            if (isMetaClass()) return (Class)this;
            else return this->ISA();
        }
    
    

    class_getClassMethod实质是让元类调用实例方法。先拿到传来类的元类,如果传来的类本来是元类,直接return
    我们根据class_getInstanceMethod可知,class_getClassMethod是取类方法和父类的类方法,如果cls是元类,也会取到类方法和本类的父类类方法
    验证

    void logClassMethod_classAndMetaClass(Class pClass) {
         const char * className =class_getName(pClass);
        Class metaClass  = objc_getMetaClass(className);
         Method method1 = class_getClassMethod(pClass, @selector(myTeacherInstanceMethod));
         Method method2 = class_getClassMethod(pClass, @selector(myTeacherClassMethod));
         Method method3 = class_getClassMethod(pClass, @selector(myInstanceMethod));
         Method method4 = class_getClassMethod(pClass, @selector(myClassMethod));
         
         Method method5 = class_getClassMethod(metaClass, @selector(myTeacherInstanceMethod));
         Method method6 = class_getClassMethod(metaClass, @selector(myTeacherClassMethod));
         Method method7 = class_getClassMethod(metaClass, @selector(myInstanceMethod));
         Method method8 = class_getClassMethod(metaClass, @selector(myClassMethod));
         NSLog(@"\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p",method1,method2,method3,method4,method5,method6,method7,method8);
    }
    打印日志
    0x0 //类的实例方法为空
    0x1000020b0 //返回类的类方法
    0x0 //父类的实例方法为空
    0x100002180//返回父类的类方法
    0x0 //类的实例方法为空
    0x1000020b0//返回类的类方法
    0x0 //父类的实例方法为空
    0x100002180//返回父类的类方法
    

    *** 类和元类调用class_getClassMethod,效果相同,因为class_getClassMethod的底层是调用元类的class_getInstanceMethod,在从类得到元类中有一个判断如果传入的类为元类直接返回,如果类不是元类,则通过isa取到元类***

    isKindOfClass和isMemberOfClass面试题

    isKindOfClass源码分析,通过探索底层的方法,我们在源码中定位到objc_opt_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);
    }
    

    函数分析,通过isa寻找类或者元类,如果obj是通过类初始化的对象,则cls是本类,如果obj本来是类,则cls是本类的元类。遍历父类和otherClass对比。
    下面一个实例说明

        BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
        BOOL re2 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
           BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];
        BOOL re4 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];       //
        BOOL re5 = [(id)[LGTeacher alloc] isKindOfClass:[LGPerson class]];
           NSLog(@" \nre1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\nre5 :%hhd\n",re1,re2,re3,re4,re5);
    
    打印结果
    re1 :1
     re2 :1
     re3 :0
     re4 :1
    re5 :1
    
    isa流程图.png

    1 . [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
    通过上面源码可知,(id)[NSObject class]在内部实现中被转化成NSObject元类并沿着NSObject元类继承树和NSObject对比,我们知道NSObject的元类的父类是NSObject所以返回为true
    2 . [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
    [NSObject alloc]被转化成NSObject类,NSObject类和[NSObject class]相同所以返回为true

    1. [(id)[LGPerson class] isKindOfClass:[LGPerson class]];
      LGPerson的元类和LGPerson元类的父类是否等于LGPerson,我们从上图中可知不想等
    2. [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; //
      [LGPerson alloc]被转化成LGPerson类和LGPerson类比较相同,所以返回是true
    3. [(id)[LGTeacher alloc] isKindOfClass:[LGPerson class]];
      [LGTeacher alloc]被转化成LGTeacher类,LGTeacher类和LGTeacher类的父类同LGPerson对比,可知LGTeacher的父类就是LGPerson,所以返回是true/

    isMemberOfClass源码分析

    isMemberOfClass底层实现有两个,一个类方法一个实例方法

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

    类方法是元类和cls做对比

    实例方法是通过[self class]取到对象所属的类,然后做对比。
    下面示例

       BOOL re6 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
           BOOL re7 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
          BOOL re8 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];     //
           BOOL re9 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
        BOOL re10 = [(id)[LGTeacher alloc] isMemberOfClass:[LGPerson class]];
     Class metaPerson = objc_getMetaClass("LGPerson");
        BOOL re11 = [(id)[LGPerson class] isMemberOfClass:metaPerson];     //
           NSLog(@" \nre6 :%hhd\n re7 :%hhd\n re8 :%hhd\n re9 :%hhd\nre10 :%hhd\n",re6,re7,re8,re9,re10);
    打印结果
    re6 :0
     re7 :1
     re8 :0
     re9 :1
    re10 :0
    re11 :1
    
    1. [(id)[NSObject class] isMemberOfClass:[NSObject class]]
      会调用底层实现的类方法,取NSObject的元类和NSObject做对比,所以返回是false
    2. [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]
      调用底层的实例方法,拿到[NSObject alloc] 对象的类NSObject,然后和NSObject对比返回是true
    3. [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]
      调用底层的类方法,LGPerson的元类和LGPerson对比返回是false
    4. [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]
      调用底层的实例方法,LGPerson类和LGPerson类做对比返回是true
    5. [(id)[LGTeacher alloc] isMemberOfClass:[LGPerson class]]
      调用底层的实例方法,LGTeacher类和LGPerson对对比,返回是false。
    6. [(id)[LGPerson class] isMemberOfClass:metaPerson]
      调用底层的类方法,LGPerson的元类和LGPerson的元类对比返回时true。

    总结

    isKindOfClass和isMemberOfClass

    两个都分为类调用或者对象调用,类调用是类的元类做判断,对象调用的是类做判断,另外isKindOfClass会沿着继承树和后面的类做对比,而isMemberOfClass只有本类(或者元类)做对比

    class_getInstanceMethod和class_getClassMethod

    元类的实例方法就是类的类方法
    class_getInstanceMethod如果是类调用,类及父类的实例方法方法返回为YES,如果是元类调用的,类的类方法及父类的类方法返回YES。
    class_getClassMethod如果是类调用,类及父类的类方法返回为YES,如果是元类调用,类的类方法和父类的类方返回是YES.因为class_getClassMethod中调用的是class_getInstanceMethod,传入的类是cls->getMeta(),getMeta会取cls的元类,如果cls本来就是元类会直接返回cls。

    相关文章

      网友评论

        本文标题:iOS-OC底层05:面试题分析

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