面试题分析

load和initialize方法的调用原则和调用顺序?

  1. load方法的调用时在dyld加载程序的时候调用,在main函数之前,调用顺序:父类,子类,分类,如果有多个分类,看谁先编译,编译的顺序可以通过Build Phases -> Compile Sources设置顺序
  2. initialize 类第一次发送消息的时候调用,调用顺序先调用父类的,在调用子类的

Runtime是什么?

Runtime是由C和C++汇编实现的⼀套API,为OC语⾔加⼊了⾯向对象,运⾏时的功能
运⾏时(Runtime)是指将数据类型的确定由编译时推迟到了运⾏时,如类扩展和分类的区别
平时编写的OC代码,在程序运⾏过程中,其实最终会转换成Runtime的C语⾔代码,Runtime 是 Object-C 的幕后⼯作者

super相关面试题

@interface HFPerson : NSObject
@end
@interface HFTeacher : HFPerson
@end
@implementation HFTeacher

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

@end

打印的结果为
 HFTeacher---HFTeacher

是不是觉得很奇怪,[self class]打印HFTeacher能理解, [super class]就有点不能理解了
我们通过汇编来看看[super class] 底层调用的方法,前面的[self class]调用objc_msgSend方法而[super class]调用的是objc_msgSendSuper2

image.png

汇编中我们可以看到[super class]底层是通过objc_msgSendSuper2来发送消息的\

id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)

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 */
};

从上面的代码可以知道objc_msgSendSuper2的接收者在objc_super结构体里的receiver,那这个receiver会是self吗?带着这个疑问我们调试看看?

image.png

首先我们断点来到这边
然后开始lldb打印寄存器x0地址
image.png

x0地址的首地址就是self,也就是说receiver = self,接收对象还是self,也就是通过self去调用父类的方法,然后我们再来看看父类的class方法
我们已知这边的self是实例对象,所以调用的是父类的实例方法也就是

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

这边的self是HFTeacher实例对象,所以返回的也就是实例对象的isa-> HFTeacher

内存平移

接下来我们在来看一道更有意思的面试题

@interface HFPerson : NSObject

@property (nonatomic, strong) NSString *hf_name;

- (void)saySomething;

@end

@implementation HFPerson
- (void)saySomething{
    NSLog(@"%s - %@",__func__,self.hf_name);
}

- (void)sayHello {
    NSLog(@"123123---%@", self);
}

@end
首先定义一个类HFPerson

- (void)viewDidLoad {
    [super viewDidLoad];
    HFPerson *person = [[HFPerson alloc] init];
    [person sayHello];
    Class cls = [HFPerson class];
    void *p = &cls;
    [(__bridge id)p sayHello];
}

结果输出:
2021-08-03 14:34:35.432347+0800 内存平移[3075:666318] 123123---<HFPerson: 0x2824f0920>
2021-08-03 14:34:35.432440+0800 内存平移[3075:666318] 123123---<HFPerson: 0x16d9c1c70>

发现都可以调用到sayHello,接下来我们来分析一下
首先[person sayHello]是实例对象调用方法,能够输出是正常,但是[(__bridge id)p sayHello]为什么也能够调用呢,我们要清楚一点的是,方法是存放在哪里,对象的方法是存放在类里面的,对象能够找到方法是通过对象的首地址isa获取方法。而现在我们Class cls = [HFPerson class] void *p = &cls 这边的p指针指向的就是HFPerson的首地址isa,所以这边也能找到方法并执行调用。
接下来我们变换一下:

- (void)sayHello {
    NSLog(@"%@---%@", self, self.hf_name);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    HFPerson *person = [[HFPerson alloc] init];
    person.hf_name = @"hf";
    [person sayHello];
    Class cls = [HFPerson class];
    void *p = &cls;
    [(__bridge id)p sayHello];
}
输出结果:
2021-08-03 14:46:57.475086+0800 内存平移[3090:669951] <HFPerson: 0x2838a3880>---hf
2021-08-03 14:46:57.475187+0800 内存平移[3090:669951] <HFPerson: 0x16f4f9c70>---<HFPerson: 0x2838a3880>

对于第一条打印结果就不解释了,直接来看看第二条。
首先我们知道p指针指向的是HFPerson对象的isa,所以他也可以通过isa来查找到方法并调用,而这边的hf_name是对象的属性,self.hf_name调用的是hf_nameget方法,get方法实现实际就是通过实例对象的指针偏移获取,目前我们只有一个成员变量,所以要获取到hf_name需要偏移8个字节(前面还有个isa指针),所以p指针也会偏移8个字节,但是他是往哪里偏移呢?我们来看一幅图

image.png

通过lldb我们可以清楚知道是往高地址偏移
p指针如何往高地址偏移呢?也就是偏移后指向哪里?
我们还是lldb查看一下
image.png

lldb看到,person地址就是p地址偏移8字节后的地址。其实也不难理解,personcls都是栈上的变量,而这边我们也得出栈地址是从高位开始分配。
接下来我们在探究一下如果继续往上偏移打印的会是什么呢?

@interface HFPerson : NSObject
@property (nonatomic, strong) NSString *hf_data1;
@property (nonatomic, strong) NSString *hf_name;
@end
输出:
2021-08-03 15:12:11.497457+0800 内存平移[3119:677027] <HFPerson: 0x16da01c70>---<ViewController: 0x147006ca0>

@interface HFPerson : NSObject
@property (nonatomic, strong) NSString *hf_data1;
@property (nonatomic, strong) NSString *hf_data2;
@property (nonatomic, strong) NSString *hf_name;
@end
输出:
2021-08-03 15:12:55.672174+0800 内存平移[3125:677782] <HFPerson: 0x16eec1c70>---ViewController

通过添加两个成员属性和三个属性看到打印的结果都不一样,两个成员属性,需要偏移16字节,而这边打印的是<ViewController: 0x147006ca0>三个属性的时候偏移24个字节打印的是ViewController,<ViewController: 0x147006ca0>似乎是个对象,而ViewController似乎是个类名。首先我们要先知道目前栈里面到底有哪些对象。viewDidLoad函数隐含着两个参数:selfsel,而这两个参数都是会入栈的,[super viewDidLoad]; 这边我们知道实际调用的是objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...),所以栈里面会有struct objc_super * super这个参数.
所以刚刚的<ViewController: 0x147006ca0>和ViewController应该是struct objc_super * super的值,这个我们可以通过lldb来验证一下

image.png

通过lldb我们就可以看到偏移16个字节的时候打印的<HFPerson: 0x16f065c70>---<ViewController: 0x101300b30>就是struct objc_super * super里面的receiver当我们添加三个成员属性的时候打印的是struct objc_super * super里面的super_class
可能这时候又有一个疑问,ViewController的super_class不是UIViewController吗?
image.png

注意:这边评估注释:/* super_class is the first class to search */,super_class是第一个开始查找的类,并没有说是父类,而第一个开始查找的类是当前类。
通过上面分析我们也可以得出,结构体成员变量入栈顺序是从后面往前,也就是越后面的成员越先入栈用一幅图来表示
image.png

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

推荐阅读更多精彩内容