runtime的學(xué)習(xí)整理
對象
類
消息
- IOS底層原理之Runimte 運(yùn)行時(shí)&方法的本質(zhì)
- _lookUpImpOrForward慢速方法查找
- Objective-C 動(dòng)態(tài)方法決議
- Objective-C 消息轉(zhuǎn)發(fā)
應(yīng)用程序加載渊抽、類、分類初始化
- dyld應(yīng)用程序加載
- 類的加載(上)-- _objc_init&read_images
- 類的加載(中)
- 類的加載(下)-- 分類的加載
- iOS 類擴(kuò)展&關(guān)聯(lián)對象
相關(guān)面試題
1. load
和initialize
方法的調(diào)用原則和調(diào)用順序?
-
load
方法-
load
方法在應(yīng)用程序加載過程中(dyld
)完成調(diào)用丧叽,在main
函數(shù)之前
- 在底層進(jìn)行
load_images
處理時(shí),維護(hù)了兩個(gè)load
加載表,一個(gè)類
的表洗做,另一個(gè)為分類
的表,優(yōu)先對類的load
方法發(fā)起調(diào)用 - 在對類
load
方法進(jìn)行處理時(shí)彰居,進(jìn)行了遞歸處理
诚纸,以確保父類優(yōu)先
被處理 - 所以
load
方法的調(diào)用順序?yàn)?code>父類、子類
陈惰、分類
- 而
分類
中load
方法的調(diào)用順序根據(jù)編譯順序
為準(zhǔn)
-
-
initialize
方法-
initialize
在第一次消息發(fā)送
的時(shí)候調(diào)用畦徘,所以load
先于initialize
調(diào)用 - 分類的?法是在類
realize
之后attach
進(jìn)去的插在前?,所以如果分類中實(shí)現(xiàn)了initialize
方法,會優(yōu)先調(diào)?分類的initialize
方法 -
initialize
內(nèi)部實(shí)現(xiàn)原理是消息發(fā)送
旧烧,所以如果子類沒有實(shí)現(xiàn)initialize
會調(diào)用父類的initialize
方法影钉,并且會調(diào)用兩次
- 因?yàn)閮?nèi)部同時(shí)使用了
遞歸
,所以如果子類
和父類
都實(shí)現(xiàn)了initialize
方法掘剪,那么會優(yōu)先
調(diào)用父類
的平委,再調(diào)用子類
的
-
具體的實(shí)現(xiàn)以及底層邏輯在類的加載(上)-- _objc_init&read_images
中。
補(bǔ)充c++
構(gòu)造函數(shù)
- 在分析
dyld
之后夺谁,可以確定這樣的一個(gè)調(diào)用順序廉赔,load->c++->main
函數(shù) - 但是如果
c++
寫在objc
工程中,在objc_init()
調(diào)用時(shí)匾鸥,會通過static_init()
方法優(yōu)先調(diào)用c++
函數(shù)蜡塌,而不需要等到_dyld_objc_notify_register
向dyld
注冊load_images
之后再調(diào)用 - 同時(shí),如果
objc_init()
自啟的話也不需要dyld
進(jìn)行啟動(dòng)勿负,也可能會發(fā)生c++
函數(shù)在load方法之前
調(diào)用的情況
2.Runtime
是什么馏艾?
-
Runtime
是由C
和C++
匯編實(shí)現(xiàn)的?套API
,為OC
語?加?了?向?qū)ο?/code>奴愉,
運(yùn)?時(shí)
的功能 - 運(yùn)?時(shí)
(Runtime
)是指將數(shù)據(jù)類型的確定由編譯時(shí)
推遲到了運(yùn)?時(shí)
琅摩,如類擴(kuò)展
和分類
的區(qū)別 - 平時(shí)編寫的
OC
代碼,在程序運(yùn)?過程中锭硼,其實(shí)最終會轉(zhuǎn)換成Runtime
的C
語?代碼房资,Runtime
是Object-C
的幕后?作者
3.?法的本質(zhì),sel
是什么檀头?IMP
是什么轰异?兩者之間的關(guān)系?是什么?
- ?法的本質(zhì):
發(fā)送消息
暑始,消息會有以下?個(gè)流程:-
快速查找
(objc_msgSend
)~cache_t
緩存消息 -
慢速查找
~ 遞歸??或?類 ~lookUpImpOrForward
-
查找不到消息
:動(dòng)態(tài)?法解析
~resolveInstanceMethod
-
消息快速轉(zhuǎn)發(fā)
~forwardingTargetForSelector
-
消息慢速轉(zhuǎn)發(fā)
~methodSignatureForSelector
和forwardInvocation
-
-
sel
是?法編號
搭独,在read_images
期間就編譯進(jìn)?了內(nèi)存
-
sel
的內(nèi)存結(jié)構(gòu):typedef struct objc_selector *SEL
;
-
-
imp
就是我們函數(shù)實(shí)現(xiàn)指針
,找imp
就是找函數(shù)的過程 -
sel
就相當(dāng)于書本的?錄tittle
廊镜,imp
就是書本的?碼
4.能否向編譯后
的得到的類中增加實(shí)例變量戳稽?能否向運(yùn)?時(shí)創(chuàng)建的類中添加實(shí)例變量?
-
不能
向編譯后的得到的類中增加實(shí)例變量- 編譯好的實(shí)例變量存儲的位置在
ro
期升,?旦編譯完成,內(nèi)存結(jié)構(gòu)就完全確定
互躬; - 可以通過
分類
向類中添加方法
和屬性
(關(guān)聯(lián)對象
)
- 編譯好的實(shí)例變量存儲的位置在
-
可以
向運(yùn)行時(shí)
創(chuàng)建的類中添加實(shí)例變量播赁,只要內(nèi)沒有注冊到內(nèi)存
還是可以添加- 可以通過
objc_allocateClassPair
在運(yùn)行時(shí)創(chuàng)建類,并向其中添加成員變量和屬性吼渡,見下面代碼:
- 可以通過
// 使用objc_allocateClassPair創(chuàng)建一個(gè)類Class
const char * className = "SelClass";
Class SelfClass = objc_getClass(className);
if (!SelfClass){
Class superClass = [NSObject class];
SelfClass = objc_allocateClassPair(superClass, className, 0);
}
// 使用class_addIvar添加一個(gè)成員變量
BOOL isSuccess = class_addIvar(SelfClass, "name", sizeof(NSString *), log2(_Alignof(NSString *)), @encode(NSString *));
class_addMethod(SelfClass, @selector(addMethodForMyClass:), (IMP)addMethodForMyClass, "V@:");
5.[self class]
和[super class]
區(qū)別和解析容为?
通過代碼案例分析
這個(gè)問題,首先創(chuàng)建LGTeacher
繼承LGPerson
,并在LGTeacher
的init
初始化方法中坎背,調(diào)用了[self class]和[super class]
替劈,查看輸出結(jié)果。
// LGPerson
@interface LGPerson : NSObject
@end
@implementation LGPerson
@end
// LGTeacher
@interface LGTeacher : LGPerson
@end
@implementation LGTeacher
- (instancetype)init{
self = [super init];
if (self) {
NSLog(@"%@ - %@", [self class], [super class]);
}
return self;
}
@end
案例分析:
很清楚LGTeaher
與LGPerson
都沒有實(shí)現(xiàn)class
方法得滤,那么根據(jù)消息發(fā)送的原理陨献,他們最終都會調(diào)用到NSObject
的實(shí)例方法class,該方法實(shí)現(xiàn)如下:
- (Class)class {
return object_getClass(self);
}
調(diào)用方法的本質(zhì)是發(fā)送消息objc_msgSend
懂更,并且有兩個(gè)隱藏參數(shù)
眨业,分別是id self
和SEL sel
,這里的隱藏參數(shù)self
就是我們要分析的類型沮协。
-
[self class]
輸出是LGTeacher
龄捡,這個(gè)沒有什么問題稳其!因?yàn)橄⒌陌l(fā)送者是LGTeacher
對象耙册,通過消息發(fā)送機(jī)制,找到NSObejct
并調(diào)用class
方法续挟,但是消息的接受者沒有發(fā)生改變行瑞,依然是LGTeacher
對象奸腺! -
[super class]
就不一樣了,同過xcrun
查看main.cpp
文件蘑辑,查看底層源碼得出以下:
super
關(guān)鍵字洋机,在底層最終使用了objc_msgSendSuper
方法,同時(shí)其接受者是(id)self
洋魂,全局搜搜objc_msgSendSuper
的邏輯绷旗,見下圖:
根據(jù) Objc-818.2源碼查看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 */
};
可以看到結(jié)構(gòu)體中只有兩個(gè)參數(shù),分別是id receiver
和Class super_class
副砍,其中super_class
表示第一個(gè)要去查找的類衔肢,至此我們可以得出結(jié)論,在LGTeacher
中調(diào)用[super class]
豁翎,其內(nèi)部會調(diào)用objc_msgSendSuper
方法角骤,并且會傳入?yún)?shù)objc_super
,其中receiver
是LGTeacher
對象心剥,super_class
是LGTeacher
類通過class_getsuperclass
獲取的父類邦尊,也就是要第一個(gè)
查找的類。
通過下符號斷點(diǎn)--objc_msgSendSuper2
优烧,查看寄存器蝉揍,其中第一個(gè)地址為發(fā)放的第一個(gè)隱藏
參數(shù),也就是objc_super
畦娄,通過類型強(qiáng)制又沾,該結(jié)構(gòu)體封裝的recevier
是LGTeacher
弊仪,super_class
是LGPerson
,具體看下圖:
得出結(jié)論:
[super class]
的接收者依然是LGTeacher
對象杖刷,去調(diào)用父類
的方法励饵。
最后查看運(yùn)行結(jié)果:
果然輸出都是
LGTeacher!;肌役听!
補(bǔ)充:objc_msgSendSuper
為什么會調(diào)用到了objc_msgSendSuper2
?
通過 Objc-818.2源碼查看的出:
全局搜索
objc_msgSendSuper
不瓶,進(jìn)入?yún)R編實(shí)現(xiàn)流程中禾嫉,在匯編流程中,最終會調(diào)用objc_msgSendSuper2
蚊丐,見下圖:注意:這題還不夠明白的話建議參考以上的消息相關(guān)文章熙参,寫得比較詳細(xì)哦。
5.指針平移和消息發(fā)送原理案例分析
LGPerson
類有一個(gè)實(shí)例方法saySomething
麦备,在viewDidLoad
中通過兩種
方式調(diào)用該方法孽椰,一種是通過創(chuàng)建LGPerson
對象調(diào)用,另一種是通過橋接調(diào)用
凛篙,見下面代碼:
- (void)viewDidLoad {
[super viewDidLoad];
LGPerson *person = [LGPerson alloc];
[person saySomething];
Class cls = [LGPerson class];
void *kc = &cls;
[(__bridge id)kc saySomething];
}
@implementation LGPerson
- (void)saySomething{
NSLog(@"%s - %@",__func__);
}
@end
問題1:是否能夠調(diào)用成功黍匾?
- 方法調(diào)用的本質(zhì)是
發(fā)送消息
,通過對象的isa
找到類
地址呛梆,進(jìn)行地址平移锐涯,通過sel
找到對應(yīng)的方法實(shí)現(xiàn)imp
毋庸置疑,
person saySomething];
此種方式肯定是沒問題的
通過person
對象的isa
指針找到對應(yīng)的類填物,在類中進(jìn)行地址平移
纹腌,首先在
cache_t
中快速查找
,如果找不到滞磺,則在方法列表
以及父類的方法列表
中查找升薯,總結(jié)一下就是:以類的地址作為入口,進(jìn)行地址平移击困,最終找到對應(yīng)的imp
涎劈。[(__bridge id)kc saySomething];
是否可以呢?
首先Class cls = [LGPerson class];
阅茶,cls
是什么蛛枚?cls
是一個(gè)指針,Class
的定義是一個(gè)指針脸哀,指向一個(gè)objc_class
的指針坤候,這里就是指向LGPerson
類。將cls的地址賦值給kc
企蹭,此時(shí)kc
為cls
的地址白筹,也指向了類
。
分析得出:兩者調(diào)用的入口是一致
的谅摄,從同一個(gè)地址開始進(jìn)行方法查找流程徒河,肯定是可以調(diào)用
到的,person
除了有地址送漠,還有內(nèi)存數(shù)據(jù)結(jié)構(gòu)
顽照;kc
只有一個(gè)地址,是一個(gè)偽裝的person
對象闽寡。請看下圖:
通過lldb
調(diào)試可以發(fā)現(xiàn)代兵,kc
指向類,見下圖:
最后運(yùn)行代碼:
案例擴(kuò)展
在LGPerson
中添加一個(gè)屬性kc_name
爷狈,實(shí)現(xiàn)代碼如下:
- (void)viewDidLoad {
[super viewDidLoad];
LGPerson *person = [LGPerson alloc];
person.kc_name = @"name123";
[person saySomething];
Class cls = [LGPerson class];
void *kc = &cls;
[(__bridge id)kc saySomething];
}
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *kc_name;
- (void)saySomething;
@end
@implementation LGPerson
- (void)saySomething{
NSLog(@"%s - %@", __func__, self.kc_name);
}
@end
那么這樣子的輸出結(jié)果優(yōu)勢怎么樣子呢植影?是不是跟上面的一樣都能輸出呢?哈哈涎永,以下繼續(xù)進(jìn)行lldb
調(diào)試思币,請繼續(xù)走!
經(jīng)過調(diào)試可以知道
person
進(jìn)行地址平移
獲取屬性kc_name
羡微,此數(shù)據(jù)結(jié)構(gòu)是在堆
中谷饿,而kc
只是一個(gè)地址,獲取kc
數(shù)據(jù)結(jié)構(gòu)只是輸出了其在棧
中的數(shù)據(jù)信息妈倔。
引申出壓棧的概念
通過上面的案例分析博投,可以知道根本原因是棧中地址平移
的問題,那么在程序運(yùn)行過程中盯蝴,壓棧邏輯是怎樣的呢毅哗?先入后出,這個(gè)比較清楚结洼,那結(jié)構(gòu)體是如何壓棧
的呢黎做,函數(shù)調(diào)用中參數(shù)的壓棧邏輯
又是怎樣的?
-
壓棧
松忍,地址從大到小蒸殿,先進(jìn)去的地址大(棧開辟由高地址到低地址
)
- 添加
結(jié)構(gòu)體
,查看棧中的地址
添加完結(jié)構(gòu)體后鸣峭,通過lldb的出下圖:
明顯看出結(jié)構(gòu)體占用了16
字節(jié)宏所,那么結(jié)構(gòu)體內(nèi)容在棧中的位置是怎么樣子的呢?繼續(xù)進(jìn)行lldb
調(diào)試:
通過lldb
輸出結(jié)構(gòu)體中兩個(gè)屬性的地址摊溶,發(fā)現(xiàn)爬骤,num1
在num2
的上面,所以在壓棧過程中莫换,按照下圖中的方式進(jìn)行的:
函數(shù)參數(shù)壓棧順序
通過下面的案例代碼進(jìn)行進(jìn)一步探索:
有上面可以得出:
-
viewDidLoad
方法中person
指針的地址和kcFunction
中person
指針地址是不一樣
的霞玄,雖然他們都執(zhí)行了同一片堆區(qū)
- 根據(jù)指針的地址發(fā)現(xiàn)骤铃,參數(shù)在壓棧時(shí)是根據(jù)
參數(shù)的順序進(jìn)行
的,第一個(gè)參數(shù)先入棧坷剧,然后依次壓棧
補(bǔ)充:runtime面試題持續(xù)更新中哦惰爬。。惫企。敬請期待撕瞧!