有关[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。虽然结果是一样的,但是以后进行底层分析的时候建议还是要以运行时的为准。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343