iOS面试 - 方法归属&isKindOfClass

iOS开发底层探究之路

本篇文章将从几个面试题出发,探究方法的归属以及isa与Superclass。

objc_object 与对象的关系,objc_object 与 NSObject的关系

  • 所有对象在底层都是以objc_object为模版继承来的。
  • 所有对象都是继承自NSObject(根类),而在底层中NSObject 是一个objc_objectC/C++)结构体。
    所以结论:objc_object对象之间是继承关系。

属性、成员变量、实例变量

  • 属性:带下划线的成员变量 + setter + getter,以@property 开头定义的变量
  • 成员变量:定义在类.h文件的{}中的,不带下划线的变量
  • 实例变量:特殊的成员变量,经过实例化的成员变量对象,例如UIButtonUILabel
  • 成员变量中除去基本数据类型及{}中定义的NSString类型变量,剩下的都是实例化后的实例变量,实例变量可理解为是拥有属性对象

方法的归属分析

下面通过一个例子来探究实例方法及类方法的归属问题:

#import 
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end
#import "LGPerson.h"
@implementation LGPerson
- (void)sayHello{
    NSLog(@"LGPerson say : Hello!!!");
}
+ (void)sayHappy{
    NSLog(@"LGPerson say : Happy!!!");
}
@end

定义继承于NSObject的LGPerson类,并添加一个实例方法sayHello,一个类方法sayHappy,下面通过几个打印情况来分析:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        // 0x0000000100000000
        // LGTeacher *teacher = [LGTeacher alloc];

        LGPerson *person = [LGPerson alloc];
        Class pClass     = object_getClass(person);
        lgObjc_copyMethodList(pClass);

        lgInstanceMethod_classToMetaclass(pClass);
        lgClassMethod_classToMetaclass(pClass);
        lgIMP_classToMetaclass(pClass);
        NSLog(@"Hello, World!");
    }
    return 0;
}

方法从上而下的定义如下面:

#ifdef DEBUG
#define LGLog(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#define LGLog(format, ...);
#endif

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

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

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));
    // 元类 为什么有 sayHappy 类方法 0 1
    //
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

void lgIMP_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    // - (void)sayHello;
    // + (void)sayHappy;
    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));

    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
    NSLog(@"%s",__func__);
}

下面我们先给出打印结果,然后逐个分析:


方法归属结果打印

lgObjc_copyMethodList

object_getClass 获取当前实例对象person类对象
此方法参数为Class类型的pClass,一个类对象class_copyMethodList 获取当前类对象中的方法列表,遍历此类对象所有的实例方法,根据打印情况只打印sayHello实例方法,可以验证类对象里面存放实例方法,类方法不存放在类对象中。

lgInstanceMethod_classToMetaclass

方法中的metaClass为当前方法参数类对象pClass的元类,由objc_getMetaClass 根据类获取到元类,class_getInstanceMethod 获取实例方法的源码分析后可知,在传入类及传入类的父类一级一级找下去直到找到返回,没找到返回null,下面分别分析几个打印情况:

  • method1:0x1000031b0

因为sayHello是实例方法,当前pClass刚好也是存放sayHelloLGPerson 类,所以能找到方法并打印

  • method2:0x0

因为metaClass为元类,元类LGPerson)--->根元类NSObject)--->NSObject--->nil,发现找不到当前的sayHello方法,说明返回null,打印为0x0

  • method3:0x0

当前需要找方法sayHappyLGPerson)--->根类NSObject)--->nil,没找到sayHappy方法,所以返回null,打印0x0

  • method4:0x100003148

寻找类方法sayHappymetaClass为当前类的元类,类方法存在类的元类中,所以此时在metaClass 中就找到sayHppy方法,即可返回并打印0x100003148

lgClassMethod_classToMetaclass

跟上面一样,方法参数pClass类对象,metaClass为当前类的元类class_getClassMethod 获取类方法源代码如下:

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

class_getClassMethod 源码可见,获取类的类方法就是去获取当前类的元类的实例方法getMeta()就是返回当前类的元类(如果getMeta()方法当前调用者为元类时,直接返回自己,不是的话就返回当前调用者的元类),下面分别分析打印情况:

  • method1:0x0

sayhello实例方法,class_getClassMethod当前传入为类,不是元类,拿到元类,然后class_getInstanceMethod 方法寻找,元类 ---> 根元类--->NSObject ---> nil,没找到sayHello,返回null,打印0x0

  • method2:0x0

class_getClassMethod当前传入为metaClass元类,直接进行class_getInstanceMethod 方法查找当前元类--->根元类---> NSObject--->nil,没找到实例方法sayHello方法,所以返回null,打印0x0

  • method3:0x100003148

当前查找方法为类方法sayHappy,在pClass类中寻找,所以class_getClassMethod 中要拿到元类,然后进行查找,发现在元类中找到类方法sayHappy,所以能打印方法地址0x100003148

  • method4: 0x100003148

class_getClassMethod 传入即为类的元类,所以直接进行class_getInstanceMethod 方法查找,在当前元类找到了sayHappy类方法,所以返回找到的方法

lgIMP_classToMetaclass

class_getMethodImplementation 返回方法的具体实现:

IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;

    if (!cls  ||  !sel) return nil;

    imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward;
    }

    return imp;
}

如果在当前类对象里没找到对应的方法实现,就会触发底层的消息转发,所以打印结果是四个都有值,而不是0x0

  • method1:0x100001d10

sayHello 实例方法,在当前pClass类中能找到当前方法,所以method1返回的是sayHello方法地址指针

  • method2:0x7fff6cd17580

metaClass 元类中寻找实例方法sayHello,是找不到的,所以进行了底层的消息转发,返回的不是sayHello的实现地址

  • method3:0x7fff6cd17580

sayHappy 类方法,因为类方法是存储在元类里的,所以在类pClass中找不到sayHappy方法实现地址,所以触发了底层消息转发

  • method4:0x100001d40

metaClass元类 中找到了sayHappy方法实现,所以返回的就是真正的sayHappy实现地址

isKindOfClass & isMemberOfClass

同样,利用上面的类LGPerson进行下面探究:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
        BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
        BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];       //
        BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson 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)[LGPerson alloc] isKindOfClass:[LGPerson class]];       //
        BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];     //
        NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
    }
    return 0;
}
isKind&isMember结果

首先理清一下isKindOfClass 方法及isMemberOfClass 方法:

  • isKindOfClass方法:
     + (BOOL)isKindOfClass:(Class)cls {
    // 类 vs 元类
    // 根元类 vs NSObject
    // NSObject vs NSObject
    // LGPerson vs 元类 (根元类) (NSObject)
    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;
    }
    
    源码所示,类方法+isKindOfClass 比较线路:元类 ---> 根元类 ---> NSObject ---> nil 与当前cls类比较。实例方法-isKindOfClass比较路线:对象的类 ---> 父类 ---> 根类 ---> nilcls比较。

注意(有坑!!!)


你以为就是这样了?当然不是,isKindOfClass 方法在llvm编译器在编译期做了编译优化,isKindOfClass 在底层走的方法是objc_opt_isKindOfClass ,打开源码,搜索objc_opt_isKindOfClass 打断点调试,发现调用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);
}

可见,无论是实例方法还是类方法调用,比较的对象链:元类 ---> 根元类 ---> NSObject ---> nil ,也就是说isKindOfClass 就是遵循isa走向的规则


  • isMemberOfClass方法:
    + (BOOL)isMemberOfClass:(Class)cls {
        return self->ISA() == cls;
    }
    
     - (BOOL)isMemberOfClass:(Class)cls {
         return [self class] == cls;
     }
    
    类方法+isMemberOfClass 判断的是当前是否是元类,实例方法-isMemberOfClass 判断是否是当前对象的类

类方法re1re2re3re4
实例方法re5re6re7re8

  • re1: 1

首先拿到前者NSObject 的元类(根元类),与后者NSObject类比较,不相等,找根元类的父类,也就是NSObject类,比较结果为相等,所以返回YES,打印1

  • re2: 0

NSObject 元类与NSObject类当然不相等,所以返回NO

  • re3: 0

LGPerson 类 与 LGPerson 元类不想等,与 LGPerson 根元类不想等,与 NSObject类不想等,与 nil不想等,所以返回NO

  • re4: 0

LGPerson 类与LGPerson 元类不相等,返回NO

  • re5: 1

NSObject实例对象的类NSObject 当然与NSObject 类相等,返回YES

  • re6: 1

NSObject实例对象的类NSObject 当然与NSObject 类相等

  • re7: 1

LGPerson 实例对象的类LGPersonLGPerson 类相等

  • re8: 1

LGPerson 实例对象的类LGPersonLGPerson 类相等

你可能感兴趣的:(iOS面试 - 方法归属&isKindOfClass)