iOS底层原理08:iOS经典面试题分析

293 阅读9分钟

前言

这篇文章需要涉及到 isasuperclass 的指向流程分析,如果你对此不清晰,可以在iOS底层原理05:isa底层原理探索下这篇文章中了解一下。

1、iskindOfClass & isMemberOfClass面试题

下面代码输出:

//Person 继承 NSObject
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     
BOOL re3 = [(id)[Person class] isKindOfClass:[Person class]];       
BOOL re4 = [(id)[Person class] isMemberOfClass:[Person 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)[Person alloc] isKindOfClass:[Person class]];       
BOOL re8 = [(id)[Person alloc] isMemberOfClass:[Person class]];     
NSLog(@"\n 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->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;
}

isMemberOfClass 源码解析

//类方法
//获取类的元类,与传入的类对比
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}
//实例方法
//获取对象的类,与传入的类对比
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

通过断点调试,遇到了一个isKindOfClass不走源码的流程,而是走到objc_opt_isKindOfClass源码中。为什么会有这个呢,因为在llvm中编译时对其进行了优化处理

查看汇编代码

objc_opt_isKindOfClass源码

BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    if (slowpath(!obj)) return NO;
    //获取isa,
       //如果obj 是对象,则isa是类,
       //如果obj 是类,则isa是元类
    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);
}

代码分析:

  • re1
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];

传入的objNSobjectotherClassNSObjectcls = obj->getIsa()NSobject meta 进入循环

第一次循环:Class tcls = cls即 NSobject meta ,otherClass 为 NSobject ;执行判断条件if (tcls == otherClass) ,不相等;执行 tcls = tcls->superclass ,此时 tcls 指向 NSobject meta的父类 ,即 NSObject。进入第二次循环。

第二次循环:此时 tclsNSobject,otherClass 依然是 NSobject,执行判断条件 if (tcls == otherClass) 相等,return YES。 所以 re1 的结果为 1。

  • re2
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; 

传入的cls 为 NSObject

self->ISA( ) ,self的 isa 指向 NSObject metaNSObject metaNSObject 不相等。

所以 re2 的结果为 0。

  • re3
BOOL re3 = [(id)[Person class] isKindOfClass:[Person class]];       

传入的objPersonotherClassPersoncls = obj->getIsa()Person meta 进入循环

第一次循环:Class tcls = cls即 Person meta ,otherClass 为 Person ;执行判断条件if (tcls == otherClass) ,不相等;执行 tcls = tcls->superclass ,此时 tcls 指向 Person meta 的父类 ,即 NSObject。进入第二次循环。

第二次循环:此时 tclsNSobject meta,otherClass 依然是 Person,执行判断条件 if (tcls == otherClass) 不相等;执行 tcls = tcls->superclass ,此时 tcls 指向 NSobject meta 的父类 ,即 NSObject。进入第三次循环。

第三次循环:此时 tclsNSobject,otherClass 依然是 Person,执行判断条件 if (tcls == otherClass) 不相等,执行 tcls = tcls->superclass ,此时 tcls 指向 NSobject 的父类 ,为nil,不满足for循环条件,结束循环,return NO,即re3 = 0;

  • re4
BOOL re4 = [(id)[Person class] isMemberOfClass:[Person class]]; 

传入cls为 Person

self->ISA(),self的 isa 指向 Person metaPerson metaPerson 不相等。

所以 re4 的结果为 0。

  • re5
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       

传入的objNSobject对象otherClassNSObjectcls = obj->getIsa()NSobject对象的isa指向根类NSobject 进入循环。

Class tcls = cls即NSobject,执行判断条件 if (tcls == otherClass) 相等

所以 re5 的结果为 1。

  • re6
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     

传入的cls 为 NSObject, self 是 NSObject 对象

[self class] 为 NSObject 类 ,[self class] 和 cls相等。

所以 re6 的结果为 1。

  • re7
BOOL re7 = [(id)[Person alloc] isKindOfClass:[Person class]];

传入的objPerson对象otherClassPerson类cls = obj->getIsa()Person对象的isa指向Person类Person 进入循环。

Class tcls = cls即Person,执行判断条件 if (tcls == otherClass) 相等。

r所以 re7 的结果为 1。

  • re8
BOOL re8 = [(id)[Person alloc] isMemberOfClass:[Person class]];     

传入的cls 为 Person类, self 是Person 对象

[self class]Person 类 ,[self class] 和 cls相等。 所以 re8 的结果为 1。

打印结果

2、class_getInstanceMethod面试题

前期准备

  • 先定义一个Person类继承NSobject,Person类有一个实例方法eat()和一个类方法run();再定义一个Teacher类继承Person类,有一个实例方法teach()
@interface Person : NSObject
- (void)eat;
+ (void)run;
@end

@implementation Person
- (void)eat{
    NSLog(@"eat something");
}
+ (void)run{
    NSLog(@"run run");
}
@end

@interface Teacher : Person
-(void)teach;
@end

@implementation Teacher
-(void)teach{
    NSLog(@"teacher teach");
}
@end
  • 下面代码会打印什么
void myInstanceMethod_classToMetaclass(Class pClass){

    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(eat));
    Method method2 = class_getInstanceMethod(metaClass, @selector(eat));

    Method method3 = class_getInstanceMethod(pClass, @selector(run));
    Method method4 = class_getInstanceMethod(metaClass, @selector(run));
    
    MyNSLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        Person *person = [Person alloc];
        Class pClass   = object_getClass(person);
        myInstanceMethod_classToMetaclass(pClass);
    }
    return 0;
}

class_getInstanceMethod分析

  • 分析之前,我们先看看class_getInstanceMethod是什么?我们先看看苹果官方的说明,键盘上command + shift + 0搜索class_getInstanceMethod

  • 大概的意思是:如果在传入的类或者类的父类中没有找到指定的实例方法,则返回NULL

  • 从代码中我们可以看出,传入的pClass是Person类,通过objc_getMetaClass获取的metaClass是Person元类。

###代码分析:

  • method1

传入的pClass是Person类,需要去获取SEL = eat的实例方法。

在Person类中查找,从上面的代码可以得知,是有这个实例方法的,所以会返回查找到的实例方法,所以method1会有地址输出。

  • method2

传入的pClass是Person元类,需要去获取SEL = eat的实例方法。

在Person元类中查找,元类中没有这个实例方法的,所以会输出地址0x0。

  • method3

传入的pClass是Person类,需要去获取SEL = run的实例方法。

在Person类中没有这个实例方法的,所以会输出地址0x0。

  • method4

传入的pClass是Person元类,需要去获取SEL = run的实例方法。

在Person元类中有这个实例方法的,主要是因为类对象的类方法存储在元类中,类方法在元类中是实例方法,所以method4会有地址输出。

  • 输出结果:
myInstanceMethod_classToMetaclass - 0x1000031f0-0x0-0x0-0x100003188

3、class_getClassMethod面试题

下面代码会打印什么呢?

void myClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(eat));
    Method method2 = class_getClassMethod(metaClass, @selector(eat));

    Method method3 = class_getClassMethod(pClass, @selector(run));
    Method method4 = class_getClassMethod(metaClass, @selector(run));
    
    MyNSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        Person *person = [Person alloc];
        Class pClass   = object_getClass(person);
        myClassMethod_classToMetaclass(pClass);
    }
    return 0;
}

class_getClassMethod分析

  • 首先先了解class_getClassMethod方法,主要是用于获取类方法,苹果官方有如下说明
  • 大概的意思是:如果在传入的类或者类的父类中没有找到指定的类方法,则返回NULL
  • 再来看看源码:
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源码中,如果判断出cls元类,那么就不会再继续往下递归查找,会直接返回this,其目的是为了防止元类的无限递归查找

###代码分析:

  • method1

pClass 是 Person类seleat

Person类不是元类,返回Person的元类,然后在元类中查找 eat实例方法。查找顺序如下:元类 --> 根元类 --> 根类 --> nil,最后返回NULL,地址0x0。

  • method2

pClass 是 Person元类seleat

此时是元类,然后在元类中查找 eat实例方法,并没有实例方法,最后返回NULL,地址0x0。

  • method3

pClass 是 Person类selrun

Person类不是元类,返回Person的元类,然后在元类中查找 run实例方法,发现有这个实例方法,直接返回找到的实例方法,地址0x100003190。

  • method4

pClass 是 Person元类selrun

Person元类元类,直接返回元类,然后在元类中查找 run实例方法,发现有这个实例方法,直接返回找到的实例方法,地址0x100003190。

4、class_getMethodImplementation面试题

  • 先来看看下面代码
void myIMP_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    IMP imp1 = class_getMethodImplementation(pClass, @selector(eat));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(eat));

    IMP imp3 = class_getMethodImplementation(pClass, @selector(run));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(run));

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

class_getMethodImplementation分析

  • class_getMethodImplementation 主要是返回方法的具体实现,苹果官方有如下说明 大概意思是:返回的函数指针可能是运行时内部的函数,而不是实际的方法实现。 例如,如果类的实例不响应selector,则返回的函数指针将成为运行时消息转发机制的一部分。

  • 再来看看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;
}

代码分析:

  • imp1 函数指针地址:0x100001b50

pClass 是 Person类,seleat

Person.m文件,可以得出Person类中可以查找到eat的具体实现,所以返回一个imp函数指针的地址。

  • imp2 函数指针地址:0x1002c3800

pClass 是 Person元类seleat

根据类方法存储在元类中可知,eat是一个实例方法,并不存储在元类中,也没有其任何实现,所以进行了消息转发

  • imp3 函数指针地址:0x1002c3800

pClass 是 Person类selrun

在Person.m文件,run是一个类方法,并不存储在类中,也没有其任何实现,所以进行了消息转发

  • imp4 函数指针地址:0x100001b20

pClass 是 Person元类selrun

根据类方法存储在元类中可知,可以在元类中查找到run的具体实现,所以返回一个imp函数指针的地址

总结

class_getInstanceMethod:获取实例方法,如果指定的或其父类不包含带有指定SEL的实例方法,则为NULL

class_getClassMethod:获取类方法,如果指定的或其父类不包含具有指定SEL的类方法,则为NULL。

class_getMethodImplementation:获取方法的具体实现,如果未查找到,则进行消息转发