有关[self class]和[super class]的面试题

示例

@interface Person : NSObject
@end

@implementation Person

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

打印结果都:Person


image.png

解答

首先看看class的实现

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

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}
  • 返回的是调用者的isa

然后我们看看selfsuper分别调用class方法有什么区别。
通过终端命令查看c++代码(注意命令行要进入文件Person.m所在的路径)

xcrun -sdk iphonesimulator clang -rewrite-objc Person.m

打开生成的Person.cpp文件,在里面查找init的实现

static instancetype _I_Person_init(Person * self, SEL _cmd) {
    self = ((Person *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("init"));
    if (self) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_60_8_r2m72n2795wn0bkj4xfvwh0000gn_T_Person_257638_mi_0,NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_60_8_r2m72n2795wn0bkj4xfvwh0000gn_T_Person_257638_mi_1,NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("class"))));
    }
    return self;
}
  • self调用class,转换为c++中是通过objc_msgSend发送class消息。
    • ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))
  • super调用class,在c++中是通过objc_msgSendSuper发送class消息。
    • ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("class"))
  • objc_msgSendSuper的第一个参数是一个结构体,而objc_msgSend第一个参数是id类型

我们回到objc开源代码中,看看objc_msgSendSuper是如何定义的

找不到定义,但是找到了声明,通过注释了解到是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 */
};
  • 一个有两个参数:分别是id类型的receiver变量和Class类型的父类变量。通过c++代码看到receiver变量赋值的还是self
  • 注意最后一行注释:使用super_class在查找方法的时候更快。就是跳过子类的方法流程,直接在父类中查找

此时就清楚原因了:

  • self调用class方法时,先在当前类中查找方法,找不到就去父类中找。最后到NSObject中。self调用时,编译期间调用的是objc_msgSend函数,函数中有两个默认参数第一个是self,第二个是sel。调用class返回的是self的isa。
  • super是直接在父类中查找,最后也是找到NSObject中。super时,编译期间调用的是objc_msgSendSuper函数,也有两个默认参数,第一个是struct objc_super * 类型,第二个是sel。而结构体中第一个属性接受者,也是self。这样调用class返回的也是self的isa。
  • ==因此打印的都是Student==

华丽的分割区域,请接着往下看。


上面的分析是有小小的瑕疵

上面的结果是对的,但是分析中有小小的瑕疵。我们使用的是编译期间的代码,但是OC可是动态的。
[super class]代码处打断点,打开汇编调试:

  • 在运行时调用的是objc_msgSendSuper2,我们在objc源码中看看它的实现
/********************************************************************
 * id objc_msgSendSuper2(struct objc_super *super, SEL op, ...)
 *
 * struct objc_super {
 *     id receiver;
 *     Class cls;   // SUBCLASS of the class to search
 * }
 ********************************************************************/
    
    ENTRY _objc_msgSendSuper2
    
    ldr r9, [r0, #CLASS]    // class = struct super->class
    ldr r9, [r9, #SUPERCLASS]   // class = class->superclass
    CacheLookup NORMAL, _objc_msgSendSuper2
    // cache hit, IMP in r12, eq already set for nonstret forwarding
    ldr r0, [r0, #RECEIVER] // load real receiver
    bx  r12         // call imp

    CacheLookup2 NORMAL, _objc_msgSendSuper2
    // cache miss
    ldr r9, [r0, #CLASS]    // class = struct super->class
    ldr r9, [r9, #SUPERCLASS]   // class = class->superclass
    ldr r0, [r0, #RECEIVER] // load real receiver
    b   __objc_msgSend_uncached
    
    END_ENTRY _objc_msgSendSuper2
  • 注释中解释了,objc_super的第二个属性是当前类
  • 汇编代码还是获取super class进行查找,和我们上面的分析还是一样的。

总结

clangxcrun命令只是编译期的一个中间产物,也只是一个参考。运行起来后才是真正的情况。

就想这道题,通过运行后,我们发现super真正底层调用的是_objc_msgSendSuper2。虽然结果是一样的,但是以后进行底层分析的时候建议还是要以运行时的为准。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。