iOS Runtime经典面试题解析

面试题

这道面试题如下,问最后print方法能不能调用成功?如果能最后打印什么?

@interface Person: NSObject
@property (copy,nonatomic) NSString * name;
-(void)print;
@end
  
@implementation Person
-(void)print{
    NSLog(@"my name is %@",self.name);
}
@end
  
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    id cls = [Person class];
    void * obj = &cls;
    [(__bridge id)obj print];
}
@end

这是一道非常好的面试题,主要考察了iOS底层的函数调用机制以及函数调用栈的问题。

解答

pint可以调用成功

首先我们先看print函数是否可以调用成功的问题,[Person class]返回的是Person的类对象,在底层类对象是一个结构体,结构体里首个变量是isa指针,接下来是superclass指针,cache的方法缓存指针以及具体的类信息指针,都指向的是具体结构体,类对象在底层的结构体结构大概如下:

struct objc_class {
  Class isa;
  Class superclass;
  cache_t cache;
  class_data_bits bits;
}
struct class_rw_t {
  units32_t flags;
  units32_t version;
  const class_ro_t *ro;
  method_list_t *methods;// 方法列表
  property_list_t *properties;//属性列表
  const protocol_list_t *protocols;//协议列表
  Class firstSubclass;
  Class nextSiblingClass;
  char *demangledName;
}
struct class_ro_t {
  unit32_t flags;
  unit32_t instanceStart;
  unit32_t instanceSize;
  #ifdef __LP64__
  unit32_t reserved;
  #endif
  const unit8_t *ivarLayout;
  const char *name;//类名
  method_list_t *baseMethodlist;
  protocol_list_t *baseProtocols;
  const ivar_list_t *ivars;//成员变量列表
  const unit8_t *weakIvarLayout;
  property_list_t *baseProperties;
}

从上面的代码可以看出来obj存放的是cls的地址,同时cls的地址指向的是Person类对象。根据Runtime的底层原理,iOS的函数调用在底层是通过objc_msgSend函数来给函数调用者发送消息,objc_msgSend的执行流程有三大阶段:

  • 消息发送

  • 动态方法解析

  • 消息转发

这里我们重点看消息发送阶段,下面这张经典的图基本阐述了消息发送阶段:

首先实例通过isa指针找到类对象,查看类对象的方法列表里是否存在对应的方法,如果没有则通过类对象里面的superclass找到其父类的类对象,在父类对象的类对象里继续查找,直至到顶层的NSObject

那么在回头看看[(__bridge id)obj print]的调用,同样是消息发送,首先找到objisa指针,从上面的分析可以看出来,obj的前8个字节存放的应该就是isa指针,因为不管是实例对象还是类对象其底层的struct结构体中,前8个字节就是isa指针,由于obj存放的是cls的地址,所以这里的isa其实就是cls的地址,而这个isa指向的是Person的类对象,所以最后能在Person类对象的方法列表中找到print方法。

最后的打印

既然能调用方法,那么最后打印什么呢?其实最后需要确定的是self->_name这个成员变量的值是什么。在底层实例对象的成员变量是紧挨着isa指针的,在isa指针的下面的一段连续的存储空间中,所以我们需要弄清楚上面的isa指针紧挨着的8个字节的存储空间中到底是什么?

栈空间

viewDidLoad调用时会开辟一段栈空间在作为函数调用的临时空间,函数调用完毕后就回收此空间,当然函数调用时里面的局部变量也是存在这个栈空间里的,里面的[super viewDidLoad]会继续开辟一段栈空间,二段栈空间是连续的,栈空间的回收是先开辟的后回收,这也符合栈数据结构的特点,[super viewDidLoad]方法在底层是通过objc_msgSendSuper2来调用的,其需要接受二个参数:

  • struct objc_super2
  • SEL

其中objc_super2结构体如下:

struct objc_super2 {
  id receiver;
  Class current_class;
}

receiver是消息接受者,current_classreceiverClass对象,由于此结构体要当参数传入方法,所以在开辟的栈空间内会存放receivercurrent_class这二个临时变量,在这里receiverselfcurrent_classViewController class。由于栈空间是从高地址到地址的,占空间的内部大致如下:

最后按照上面寻找成员变量的方式,跳过isa指针就是成员变量,由于上面已经分析指导isa指针就是cls,所以self就是找到的第一个成员变量,由于person只有一个成员变量_name,所以这里self就等于_name这个成员变量,最后的打印结果为:my name is <ViewController: 0x13a110720>

调试打印

首先我们打印出obj的地址值,然后打印后面的连续48字节的地址,分别打印地址的内容:

这很好的证明了cls后面的8个字节存储的是viewController,在往后8个字节存放的是viewController的类对象。

思考

如果代码为下面这种情况打印什么?

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString * str = @"mamba";
    id cls = [Person class];
    void * obj = &cls;
    [(__bridge id)obj print];
    
}

通过上面的分析,str这个字符串局部变量会紧挨着cls的地址,所以最后输出是my name is mamba,如果注释掉

[super viewDidLoad]的调用,则会发生坏内存访问,程序崩溃。

总结

本文根据一个实际的面试题来回复了Runtime中的函数调用消息机制以及函数调用栈的相关知识,通过这个面试题能到加深对iOS底层知识的理解。

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