load和initialize方法的調用原則和調用順序?
- load方法的調用時在dyld加載程序的時候調用,在main函數(shù)之前,調用順序:父類,子類,分類,如果有多個分類,看誰先編譯,編譯的順序可以通過
Build Phases -> Compile Sources設置順序 - initialize 類第一次發(fā)送消息的時候調用,調用順序先調用父類的,在調用子類的
Runtime是什么?
Runtime是由C和C++匯編實現(xiàn)的?套API,為OC語?加?了?向對象,運?時的功能
運?時(Runtime)是指將數(shù)據(jù)類型的確定由編譯時推遲到了運?時,如類擴展和分類的區(qū)別
平時編寫的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

匯編中我們可以看到
[super class]底層是通過objc_msgSendSuper2來發(fā)送消息的\
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嗎?帶著這個疑問我們調試看看?

首先我們斷點來到這邊
然后開始lldb打印寄存器
x0地址
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>
發(fā)現(xiàn)都可以調用到sayHello,接下來我們來分析一下
首先[person sayHello]是實例對象調用方法,能夠輸出是正常,但是[(__bridge id)p sayHello]為什么也能夠調用呢,我們要清楚一點的是,方法是存放在哪里,對象的方法是存放在類里面的,對象能夠找到方法是通過對象的首地址isa獲取方法。而現(xiàn)在我們Class cls = [HFPerson class] void *p = &cls 這邊的p指針指向的就是HFPerson的首地址isa,所以這邊也能找到方法并執(zhí)行調用。
接下來我們變換一下:
- (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_name的get方法,get方法實現(xiàn)實際就是通過實例對象的指針偏移獲取,目前我們只有一個成員變量,所以要獲取到hf_name需要偏移8個字節(jié)(前面還有個isa指針),所以p指針也會偏移8個字節(jié),但是他是往哪里偏移呢?我們來看一幅圖

通過lldb我們可以清楚知道是往高地址偏移
p指針如何往高地址偏移呢?也就是偏移后指向哪里?
我們還是lldb查看一下

lldb看到,person地址就是p地址偏移8字節(jié)后的地址。其實也不難理解,
person,cls都是棧上的變量,而這邊我們也得出棧地址是從高位開始分配。接下來我們在探究一下如果繼續(xù)往上偏移打印的會是什么呢?
@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字節(jié),而這邊打印的是<ViewController: 0x147006ca0>三個屬性的時候偏移24個字節(jié)打印的是ViewController,<ViewController: 0x147006ca0>似乎是個對象,而ViewController似乎是個類名。首先我們要先知道目前棧里面到底有哪些對象。viewDidLoad函數(shù)隱含著兩個參數(shù):self和sel,而這兩個參數(shù)都是會入棧的,[super viewDidLoad]; 這邊我們知道實際調用的是objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...),所以棧里面會有struct objc_super * super這個參數(shù).
所以剛剛的<ViewController: 0x147006ca0>和ViewController應該是struct objc_super * super的值,這個我們可以通過lldb來驗證一下

通過lldb我們就可以看到偏移16個字節(jié)的時候打印的
<HFPerson: 0x16f065c70>---<ViewController: 0x101300b30>就是struct objc_super * super里面的receiver當我們添加三個成員屬性的時候打印的是struct objc_super * super里面的super_class。可能這時候又有一個疑問,ViewController的super_class不是UIViewController嗎?

注意:這邊評估注釋:/* super_class is the first class to search */,super_class是第一個開始查找的類,并沒有說是父類,而第一個開始查找的類是當前類。
通過上面分析我們也可以得出,結構體成員變量入棧順序是從后面往前,也就是越后面的成員越先入棧用一幅圖來表示
