iOS底层原理

iOS底层原理 06 : isa走位&类继承的经典面试题

2020-09-15  本文已影响0人  smooth_lgh
1. 下面代码打印的结果是什么,并做分析。
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];  
        BOOL re2 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];     
        BOOL re3 = [(id)[NSObject class] isMemberOfClass:[NSObject 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);
    }
    return 0;
}

打印的结果:

re1 :1
re2 :0
re3 :0
re4 :0

为什么是这个结果呢?
** [[NSObject class] isKindOfClass:[NSObject class]],首先前面是在调用isKindOfClass方法,所以找的是+ (BOOL)isKindOfClass:(Class)cls类方法。**
我们查看源码

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

对于re1

1.Class tcls = self->ISA(), NSObject类isa指向根元类根元类 != NSObject类,循环继续
2.接下来tcls = tcls->superclass根元类的父类NSObject类, 此时 tcls == cls ,return YES;

对于re2 **

1.Class tcls = self->ISA(), LGPerson信息类isa指向LGHPerson的元类LGPerson的元类!= LGPerson类,循环继续。
2. tcls = tcls->superclassLGPerson的元类的父类根元类根元类 != LGPerson类,循环继续。
3. tcls = tcls->superclass根元类 的父类NSObject类NSObject类 != LGPerson类,循环继续。
4. tcls = tcls->superclassNSObject的父类nil,循环结束
5. return NO;

同理[(id)[NSObject class] isMemberOfClass:[NSObject class]],我们需要找的是+ (BOOL)isMemberOfClass:(Class)cls类方法。

通过源码得出: +isMemberOfClass是判断当前类的isa所指向的类传入的cls类是否一致

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

对于re3

NSObject类的isa指向根元类根元类 != NSObject类,return NO;

对于re4

LGPerson类的isa指向LGPerson的元类LGPerson的元类 != LGPerson类, return NO;

2. 下面代码打印的结果是什么,并做分析。
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];        
        BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; 
        BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject 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;
}

打印的结果:

re5 :1
re6 :1
re7 :1
re8 :1

首先[(id)[NSObject alloc] isKindOfClass:[NSObject class]],我们应该找的是实例方法-(BOOL)isKindOfClass:(Class)cls
查看源码:
1. 首先是tcls = [self class]cls比较,
2. 接下来tcls->superclass(即父类)cls比较,
3. tcls->superclass->superclass(即父类的父类) 与cls比较
.....for循环
4. 一直到NSObject类cls比较,如果还是不相等,NSObject类->superclassnil,for循环结束,返回NO

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

对于re5

Class tcls = [self class] ,tcls=NSObject类,NSObject类=NSObject类, return YES;

对于re6

Class tcls = [self class] ,tcls=LGPerson类,LGPerson类==LGPerson类,return YES;

对于[(id)[NSObject alloc] isMemberOfClass:[NSObject class]],我们该找的是实例方法-(BOOL)isMemberOfClass:(Class)cls*
查看源码:

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

对于re7

Class tcls = [self class]tcls=NSObject类NSObject类==NSObject类, return YES;

对于re8

Class tcls = [self class] ,tcls=LGPerson类LGPerson类==LGPerson类,return YES;

2.以下代码的输出结果是什么?并分析原因。
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;

@end

@implementation LGPerson

- (void)sayHello{
    NSLog(@"LGPerson say : Hello!!!");
}

+ (void)sayHappy{
    NSLog(@"LGPerson say : Happy!!!");
}
@end
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        Class pClass     = object_getClass(person);
        const char *className = class_getName(pClass);
        Class metaPClass = objc_getMetaClass(className);
        
        Method method1 = class_getClassMethod(pClass, @selector(sayHello));
        Method method2 = class_getClassMethod(metaPClass, @selector(sayHello));
        Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
        Method method4 = class_getClassMethod(metaPClass, @selector(sayHappy));
        LGLog(@"method1=%p",method1);
        LGLog(@"method2=%p",method2);
        LGLog(@"method3=%p",method3);
        LGLog(@"method4=%p",method4);
        
        Method method5 = class_getInstanceMethod(pClass, @selector(sayHello));
        Method method6 = class_getInstanceMethod(metaPClass, @selector(sayHello));
        Method method7 = class_getInstanceMethod(pClass, @selector(sayHappy));
        Method method8 = class_getInstanceMethod(metaPClass, @selector(sayHappy));
        LGLog(@"method5=%p",method5);
        LGLog(@"method6=%p",method6);
        LGLog(@"method7=%p",method7);
        LGLog(@"method8=%p",method8);
    }
    return 0;
}

打印结果:

method1=0x0
method2=0x0
method3=0x100003070
method4=0x100003070
method5=0x1000030d8
method6=0x0
method7=0x0
method8=0x100003070

首先我们知道, 实例方法存储在类的实例方法列表中中,而类方法存储在元类的实例方法列表中。

查看class_getClassMethod的源代码.
从源码得出:
1. 首先它会去找cls的元组实例方法列表,结合getMeta()方法,如果本身是元组类型,直接去找自身的实例方法列表,
2. 然后cls->getMeta()->superclass,去cls的元组的父类里面找,
..... for循环..... ,
3. 一直找到根元类,然后是NSObject类,最后是nil,结束循环。

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    return class_getInstanceMethod(cls->getMeta(), sel);
}
 
// 如果是元组类型,直接返回,否则返回当前class的isa指向的类
Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }
Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
    return _class_getMethod(cls, sel);
}
static Method _class_getMethod(Class cls, SEL sel)
{
    mutex_locker_t lock(runtimeLock);
    return getMethod_nolock(cls, sel);
}
getMethod_nolock(Class cls, SEL sel)
{
    method_t *m = nil;
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());
    while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
        cls = cls->superclass;
    }
    return m;
}

class_getClassMethod源码大致流程图:

class_getClassMethod源码大致流程图.png

sayHello实例方法,所以存在于LGPerson类的实例方法列表
1. method1,所以在LGPerson元类的methods中获取不到,返回0x00
2. method2, sayHello是实例方法,所以在LGPerson元类的methods中就更加获取不到,返回0x00

sayHappy类方法,所以存在于LGPerson元类的实例方法列表中.
1. method3, 会去找LGPerson元类的实例方法列表中寻找,恰好就存在sayHappy,所以method3有值
2. method4, 也是会在会在找LGPerson元类的实例方法列表中寻找,所以method4有值

接下来我们看class_getInstanceMethod的源码.
从源码得出:先从cls类的methods列表中查找是否存在sel,然后从cls类的父类的methods列表查找.... 直到从NSObject类的methods列表查找

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
    return _class_getMethod(cls, sel);
}

static Method _class_getMethod(Class cls, SEL sel)
{
    mutex_locker_t lock(runtimeLock);
    return getMethod_nolock(cls, sel);
}
static method_t *
getMethod_nolock(Class cls, SEL sel)
{
    method_t *m = nil;
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());
    while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
        cls = cls->superclass;
    }
    return m;
}
class_getInstanceMethod的源码流程图.png

sayHello是实例方法,存在于LGPerson类的methods列表中。
所以 method05有值,method6是0x00。

sayHappy是类方法,存在于LGPerson元类的methods列表中.
所以 method07是0x00,method8有值

【拓展】3.以下代码输出的结果是什么?
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;

@end

@implementation LGPerson

- (void)sayHello{
    NSLog(@"LGPerson say : Hello!!!");
}

+ (void)sayHappy{
    NSLog(@"LGPerson say : Happy!!!");
}
@end

@interface LGTeacher : LGPerson
- (void)say666;
@end

@implementation LGTeacher
- (void)say666{
    
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGTeacher *teacher = [LGTeacher alloc];
        Class pClass     = object_getClass(teacher);
        const char *className = class_getName(pClass);
        Class metaPClass = objc_getMetaClass(className);
        
        Method method1 = class_getClassMethod(pClass, @selector(sayHello));
        Method method2 = class_getClassMethod(metaPClass, @selector(sayHello));
        
        Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
        Method method4 = class_getClassMethod(metaPClass, @selector(sayHappy));
        
        Method method5 = class_getClassMethod(pClass, @selector(say666));
        Method method6 = class_getClassMethod(metaPClass, @selector(say666));
        LGLog(@"method1=%p",method1);
        LGLog(@"method2=%p",method2);
        LGLog(@"method3=%p",method3);
        LGLog(@"method4=%p",method4);
        LGLog(@"method5=%p",method5);
        LGLog(@"method6=%p",method6);
        
        Method method7 = class_getInstanceMethod(pClass, @selector(sayHello));
        Method method8 = class_getInstanceMethod(metaPClass, @selector(sayHello));
        
        Method method9 = class_getInstanceMethod(pClass, @selector(sayHappy));
        Method method10 = class_getInstanceMethod(metaPClass, @selector(sayHappy));
        
        Method method11 = class_getInstanceMethod(pClass, @selector(say666));
        Method method12 = class_getInstanceMethod(metaPClass, @selector(say666));
        
        LGLog(@"method5=%p",method7);
        LGLog(@"method6=%p",method8);
        LGLog(@"method7=%p",method9);
        LGLog(@"method8=%p",method10);
        LGLog(@"method7=%p",method11);
        LGLog(@"method8=%p",method12);
    }
     return 0;
}

首先sayHello是实例方法,存在于LGPerson类的methods列表中。

对于method1,class_getClassMethod(LGTeacher, @selector(sayHello)) ,
1. 首先去LGTeacher的元类里面找,没有找到,
2. 接下来去LGTeacher的元类的superclass,即LGPerson的元类里面去找,没有找到,
3. 之后去根元类NSObject类寻找,更是不会找到,返回0x00

对于method2 ,class_getClassMethod(LGTeacher的元类, @selector(sayHello)),
1. 因为传入的是LGTeacher的元类,所以直接到LGTeacher的元类里面找,没有找到,
2. 接下来去LGTeacher的元类的superclass,即LGPerson的元类里面去找,没有找到,
3. 之后去根元类NSObject类寻找,更是不会找到,返回0x00。

首先sayHappy是类方法,存在于LGPerson元类的methods列表中。

对于method3 ,class_getClassMethod(LGTeacher, @selector(sayHappy)),
1. 首先去LGTeacher的元类里面实例方法列表中找,没有找到,
2. 接下来去LGTeacher的元类的superclass,即LGPerson的元类里面的实例方法列表中寻找,可以找到,所以method3有值

对于method4来说 ,
1. 首先去LGTeacher的元类里面实例方法列表中找,没有找到,
2. 接下来去LGTeacher的元类的superclass,即LGPerson的元类里面的实例方法列表中寻找,可以找到,所以method4有值

首先say666是实例方法,存在于LGTeacher类的实例方法列表中

所以method5 和 method6都为 0x00.

首先sayHello是实例方法,存在于LGPerson类的实例方法列表

对于method7 来说,
1. 去LGTeacher类的实例方法列表中找,没有找到,
2. 然后去LGPerson类的实例方法列表中找,找到了,method7有值。

对于method8 ,
1. 去LGTeacher元类的实例方法列表中找,没有找到,
2. 然后去LGPerson元类的实例方法列表中找,没有找到,
3. 最后去根元类和NSObject类的实例方法列表寻找,没有找到,method8=0x00

首先sayHappy是类方法,存在于LGPerson元类的实例方法列表中。

对与method9,
1. 去LGTeacher类的实例方法列表中找,没有找到,
2. 然后去LGPerson类的实例方法列表中找,没有找到了,3. 最后去NSObject类的实例方法列表寻找,没有找到,method9=0x00

对与method10,
1. 去LGTeacher元类的实例方法列表中找,没有找到,
2. 然后去LGPerson元类的实例方法列表中找,找到了,method10有值

首先say666是实例方法,存在于LGTeacher类的实例方法列表

所以method11有值,method12=0x00

**打印结果: **

method1=0x0
method2=0x0
method3=0x100003070
method4=0x100003070
method5=0x0
method6=0x0

method7=0x1000030d8
method8=0x0
method9=0x0
method10=0x100003070
method11=0x100003188
method12=0x0
4.下面的代码输出什么?
@implementation Son : Father
- (id)init {
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}

结果: Son / Son

分析:

对于上面的答案,第一个的结果应该是我们的预期结果,但是第二个结果却让我们很费解了。

那我们利用前面文章讲过的知识点来分析一下整个的流程。

因为,Son 及 Father 都没有实现 -(Class)calss 方法,所以这里所有的调用最终都会找到基类 NSObject 中,并且在其中找到 -(Class)calss 方法。那我们需要了解的就是在 NSObject 中这个方法的实现了。

在 NSObject.mm 中可以找到 -(Class)class 的实现:

- (Class)class {
    return object_getClass(self);
}

在 objc_class.mm 中找到 object_getClass 的实现:

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

ps:上面的方法定义可以去可编译调试的objc4源码中下载源码哦。

可以看到,最终这个方法返回的是,调用这个方法的 objc 的 isa 指针。那我们只需要知道在题干中的代码里面最终是谁在调用 -(Class)class 方法就可以找到答案了。

接下来,我们利用 clang -rewrite-objc 命令,将题干的代码转化为如下代码:

NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_cgm28r0d0bz94xnnrr606rf40000gn_T_Car_3f2069_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_cgm28r0d0bz94xnnrr606rf40000gn_T_Car_3f2069_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Car"))}, sel_registerName("class"))));

从上方可以得出,调用 Father class 的时候,本质是在调用

objc_msgSendSuper(struct objc_super *super, SEL op, ...)

struct objc_super 的定义如下:

struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};

从定义可以得知:当利用 super 调用方法时,只要编译器看到super这个标志,就会让当前对象去调用父类方法,本质还是当前对象在调用,是去父类找实现,super 仅仅是一个编译指示器。但是消息的接收者 receiver 依然是self。最终在 NSObject 获取 isa 指针的时候,获取到的依旧是 self 的 isa,所以,我们得到的结果是:Son。

5. 看看下方的代码会输出什么?
@interface Father : NSObject
@end

@implementation Father

- (Class)class {
    return [Father class];
}

@end

---

@interface Son : Father
@end

@implementation Son

- (id)init {
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}

@end

int main(int argc, const char * argv[]) {
    Son *foo = [[Son alloc]init];
    return 0;
}

---输出:---
Father
Father

更多的面试题和答案:https://github.com/iOSputao/iOS-

上一篇下一篇

猜你喜欢

热点阅读