上篇文章:Runtime在工作中的運(yùn)用
1.objc在向一個(gè)對(duì)象發(fā)送消息時(shí)橡庞,發(fā)生了什么?
objc在向一個(gè)對(duì)象發(fā)送消息時(shí)立叛,runtime會(huì)根據(jù)對(duì)象的isa指針找到該對(duì)象實(shí)際所屬的類,然后在該類中的方法列表以及其父類方法列表中尋找方法運(yùn)行,如果一直到根類還沒找到泼诱,轉(zhuǎn)向攔截調(diào)用,走消息轉(zhuǎn)發(fā)機(jī)制赊锚,一旦找到 治筒,就去執(zhí)行它的實(shí)現(xiàn)
IMP
。
詳解:請(qǐng)看Runtime在工作中的運(yùn)用 第二章Runtime消息機(jī)制舷蒲;
2.objc中向一個(gè)nil對(duì)象發(fā)送消息將會(huì)發(fā)生什么耸袜?
如果向一個(gè)nil對(duì)象發(fā)送消息,首先在尋找對(duì)象的isa指針時(shí)就是0地址返回了牲平,所以不會(huì)出現(xiàn)任何錯(cuò)誤堤框。也不會(huì)崩潰。
詳解:
如果一個(gè)方法返回值是一個(gè)對(duì)象纵柿,那么發(fā)送給nil的消息將返回0(nil)蜈抓;
如果方法返回值為指針類型,其指針大小為小于或者等于sizeof(void*) 昂儒,float沟使,double,long double 或者long long的整型標(biāo)量渊跋,發(fā)送給nil的消息將返回0腊嗡;
如果方法返回值為結(jié)構(gòu)體,發(fā)送給nil的消息將返回0。結(jié)構(gòu)體中各個(gè)字段的值將都是0拾酝;
如果方法的返回值不是上述提到的幾種情況燕少,那么發(fā)送給nil的消息的返回值將是未定義的。
3.objc中向一個(gè)對(duì)象發(fā)送消息[obj foo]和objc_msgSend()
函數(shù)之間有什么關(guān)系微宝?
在objc編譯時(shí)棺亭,[obj foo] 會(huì)被轉(zhuǎn)意為:
objc_msgSend(obj, @selector(foo));
。
詳解:請(qǐng)看Runtime在工作中的運(yùn)用 第二章Runtime消息機(jī)制蟋软;
4.什么時(shí)候會(huì)報(bào)unrecognized selector的異常镶摘?
objc在向一個(gè)對(duì)象發(fā)送消息時(shí),runtime庫會(huì)根據(jù)對(duì)象的isa指針找到該對(duì)象實(shí)際所屬的類岳守,然后在該類中的方法列表以及其父類方法列表中尋找方法運(yùn)行凄敢,如果,在最頂層的父類中依然找不到相應(yīng)的方法時(shí)湿痢,會(huì)進(jìn)入消息轉(zhuǎn)發(fā)階段涝缝,如果消息三次轉(zhuǎn)發(fā)流程仍未實(shí)現(xiàn)扑庞,則程序在運(yùn)行時(shí)會(huì)掛掉并拋出異常unrecognized selector sent to XXX 。
詳解:請(qǐng)看Runtime在工作中的運(yùn)用 第三章Runtime方法調(diào)用流程拒逮;
5.能否向編譯后得到的類中增加實(shí)例變量罐氨?能否向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量?為什么滩援?
不能向編譯后得到的類中增加實(shí)例變量栅隐;
能向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量;
1.因?yàn)榫幾g后的類已經(jīng)注冊(cè)在 runtime 中,類結(jié)構(gòu)體中的 objc_ivar_list 實(shí)例變量的鏈表和 instance_size 實(shí)例變量的內(nèi)存大小已經(jīng)確定玩徊,同時(shí)runtime會(huì)調(diào)用 class_setvarlayout 或 class_setWeaklvarLayout 來處理strong weak 引用.所以不能向存在的類中添加實(shí)例變量租悄。
2.運(yùn)行時(shí)創(chuàng)建的類是可以添加實(shí)例變量,調(diào)用class_addIvar函數(shù). 但是的在調(diào)用 objc_allocateClassPair 之后恩袱,objc_registerClassPair 之前,原因同上.
6.給類添加一個(gè)屬性后泣棋,在類結(jié)構(gòu)體里哪些元素會(huì)發(fā)生變化?
instance_size :實(shí)例的內(nèi)存大信纤潭辈;objc_ivar_list *ivars:屬性列表
7.一個(gè)objc對(duì)象的isa的指針指向什么?有什么作用俩檬?
指向他的類對(duì)象,從而可以找到對(duì)象上的方法
詳解:下圖很好的描述了對(duì)象萎胰,類,元類之間的關(guān)系:
圖中實(shí)線是 super_class指針棚辽,虛線是isa指針技竟。
- Root class (class)其實(shí)就是NSObject,NSObject是沒有超類的屈藐,所以Root class(class)的superclass指向nil榔组。
- 每個(gè)Class都有一個(gè)isa指針指向唯一的Meta class
- Root class(meta)的superclass指向Root class(class),也就是NSObject联逻,形成一個(gè)回路搓扯。
- 每個(gè)Meta class的isa指針都指向Root class (meta)。
8.[self class] 與 [super class]
下面的代碼輸出什么?
@implementation Son : Father
- (id)init
{
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
NSStringFromClass([self class]) = Son
NSStringFromClass([super class]) = Son
詳解:這個(gè)題目主要是考察關(guān)于 Objective-C 中對(duì) self 和 super 的理解。
self 是類的隱藏參數(shù)若治,指向當(dāng)前調(diào)用方法的這個(gè)類的實(shí)例滑负;
super 本質(zhì)是一個(gè)編譯器標(biāo)示符充包,和 self 是指向的同一個(gè)消息接受者。不同點(diǎn)在于:super 會(huì)告訴編譯器,當(dāng)調(diào)用方法時(shí),去調(diào)用父類的方法沾鳄,而不是本類中的方法。
當(dāng)使用 self 調(diào)用方法時(shí)确憨,會(huì)從當(dāng)前類的方法列表中開始找译荞,如果沒有瓤的,就從父類中再找;而當(dāng)使用 super 時(shí)吞歼,則從父類的方法列表中開始找圈膏。然后調(diào)用父類的這個(gè)方法。
在調(diào)用[super class]
的時(shí)候浆熔,runtime會(huì)去調(diào)用objc_msgSendSuper
方法本辐,而不是objc_msgSend
;
OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained 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 Class class;
#else
__unsafe_unretained Class super_class;
#endif
/* super_class is the first class to search */
};
在objc_msgSendSuper方法中医增,第一個(gè)參數(shù)是一個(gè)objc_super的結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體里面有兩個(gè)變量老虫,一個(gè)是接收消息的receiver叶骨,一個(gè)是當(dāng)前類的父類super_class。
objc_msgSendSuper的工作原理應(yīng)該是這樣的:
從objc_super結(jié)構(gòu)體指向的superClass父類的方法列表開始查找selector祈匙,找到后以objc->receiver去調(diào)用父類的這個(gè)selector忽刽。注意,最后的調(diào)用者是objc->receiver夺欲,而不是super_class跪帝!
那么objc_msgSendSuper最后就轉(zhuǎn)變成:
// 注意這里是從父類開始msgSend,而不是從本類開始
objc_msgSend(objc_super->receiver, @selector(class))
/// Specifies an instance of a class. 這是類的一個(gè)實(shí)例
__unsafe_unretained id receiver;
// 由于是實(shí)例調(diào)用些阅,所以是減號(hào)方法
- (Class)class {
return object_getClass(self);
}
由于找到了父類NSObject里面的class方法的IMP伞剑,又因?yàn)閭魅氲娜雲(yún)bjc_super->receiver = self。self就是son市埋,調(diào)用class黎泣,所以父類的方法class執(zhí)行IMP之后,輸出還是son缤谎,最后輸出兩個(gè)都一樣抒倚,都是輸出son。
9.runtime如何通過selector找到對(duì)應(yīng)的IMP地址坷澡?
每一個(gè)類對(duì)象中都一個(gè)方法列表,方法列表中記錄著方法的名稱,方法實(shí)現(xiàn),以及參數(shù)類型,其實(shí)selector本質(zhì)就是方法名稱,通過這個(gè)方法名稱就可以在方法列表中找到對(duì)應(yīng)的方法實(shí)現(xiàn).
10._objc_msgForward函數(shù)是做什么的托呕,直接調(diào)用它將會(huì)發(fā)生什么?
_objc_msgForward
是 IMP 類型频敛,用于消息轉(zhuǎn)發(fā)的:當(dāng)向一個(gè)對(duì)象發(fā)送一條消息项郊,但它并沒有實(shí)現(xiàn)的時(shí)候,_objc_msgForward
會(huì)嘗試做消息轉(zhuǎn)發(fā)姻政。
詳解:_objc_msgForward
在進(jìn)行消息轉(zhuǎn)發(fā)的過程中會(huì)涉及以下這幾個(gè)方法:
-
resolveInstanceMethod:
方法 (或resolveClassMethod:
)呆抑。 -
forwardingTargetForSelector:
方法 -
methodSignatureForSelector:
方法 -
forwardInvocation:
方法 -
doesNotRecognizeSelector:
方法
具體請(qǐng)見:請(qǐng)看Runtime在工作中的運(yùn)用 第三章Runtime方法調(diào)用流程;
11. runtime如何實(shí)現(xiàn)weak變量的自動(dòng)置nil汁展?知道SideTable嗎鹊碍?
runtime 對(duì)注冊(cè)的類會(huì)進(jìn)行布局厌殉,對(duì)于 weak 修飾的對(duì)象會(huì)放入一個(gè) hash 表中。 用 weak 指向的對(duì)象內(nèi)存地址作為 key侈咕,當(dāng)此對(duì)象的引用計(jì)數(shù)為0的時(shí)候會(huì) dealloc公罕,假如 weak 指向的對(duì)象內(nèi)存地址是a,那么就會(huì)以a為鍵耀销, 在這個(gè) weak 表中搜索楼眷,找到所有以a為鍵的 weak 對(duì)象,從而設(shè)置為 nil熊尉。
更細(xì)一點(diǎn)的回答:
1.初始化時(shí):runtime會(huì)調(diào)用objc_initWeak函數(shù)罐柳,初始化一個(gè)新的weak指針指向?qū)ο蟮牡刂贰?br> 2.添加引用時(shí):objc_initWeak函數(shù)會(huì)調(diào)用objc_storeWeak() 函數(shù), objc_storeWeak() 的作用是更新指針指向狰住,創(chuàng)建對(duì)應(yīng)的弱引用表张吉。
3.釋放時(shí),調(diào)用clearDeallocating函數(shù)。clearDeallocating函數(shù)首先根據(jù)對(duì)象地址獲取所有weak指針地址的數(shù)組催植,然后遍歷這個(gè)數(shù)組把其中的數(shù)據(jù)設(shè)為nil肮蛹,最后把這個(gè)entry從weak表中刪除,最后清理對(duì)象的記錄创南。
SideTable結(jié)構(gòu)體是負(fù)責(zé)管理類的引用計(jì)數(shù)表和weak表伦忠,
詳解:參考自《Objective-C高級(jí)編程》一書
1.初始化時(shí):runtime會(huì)調(diào)用objc_initWeak函數(shù),初始化一個(gè)新的weak指針指向?qū)ο蟮牡刂贰?/strong>
{
NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;
}
當(dāng)我們初始化一個(gè)weak變量時(shí)稿辙,runtime會(huì)調(diào)用 NSObject.mm 中的objc_initWeak函數(shù)昆码。
// 編譯器的模擬代碼
id obj1;
objc_initWeak(&obj1, obj);
/*obj引用計(jì)數(shù)變?yōu)?,變量作用域結(jié)束*/
objc_destroyWeak(&obj1);
通過objc_initWeak
函數(shù)初始化“附有weak修飾符的變量(obj1)”邓深,在變量作用域結(jié)束時(shí)通過objc_destoryWeak
函數(shù)釋放該變量(obj1)未桥。
2.添加引用時(shí):objc_initWeak函數(shù)會(huì)調(diào)用objc_storeWeak() 函數(shù), objc_storeWeak() 的作用是更新指針指向芥备,創(chuàng)建對(duì)應(yīng)的弱引用表冬耿。
objc_initWeak
函數(shù)將“附有weak修飾符的變量(obj1)”初始化為0(nil)后,會(huì)將“賦值對(duì)象”(obj)作為參數(shù)萌壳,調(diào)用objc_storeWeak
函數(shù)亦镶。
obj1 = 0;
obj_storeWeak(&obj1, obj);
也就是說:
weak 修飾的指針默認(rèn)值是 nil (在Objective-C中向nil發(fā)送消息是安全的)
然后obj_destroyWeak
函數(shù)將0(nil)作為參數(shù)袱瓮,調(diào)用objc_storeWeak
函數(shù)缤骨。
objc_storeWeak(&obj1, 0);
前面的源代碼與下列源代碼相同。
// 編譯器的模擬代碼
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
/* ... obj的引用計(jì)數(shù)變?yōu)?尺借,被置nil ... */
objc_storeWeak(&obj1, 0);
objc_storeWeak
函數(shù)把第二個(gè)參數(shù)的賦值對(duì)象(obj)的內(nèi)存地址作為鍵值绊起,將第一個(gè)參數(shù)__weak修飾的屬性變量(obj1)的內(nèi)存地址注冊(cè)到 weak 表中。如果第二個(gè)參數(shù)(obj)為0(nil)燎斩,那么把變量(obj1)的地址從weak表中刪除虱歪。
由于一個(gè)對(duì)象可同時(shí)賦值給多個(gè)附有__weak修飾符的變量中蜂绎,所以對(duì)于一個(gè)鍵值,可注冊(cè)多個(gè)變量的地址笋鄙。
可以把objc_storeWeak(&a, b)
理解為:objc_storeWeak(value, key)
师枣,并且當(dāng)key變nil,將value置nil萧落。在b非nil時(shí)践美,a和b指向同一個(gè)內(nèi)存地址,在b變nil時(shí)找岖,a變nil陨倡。此時(shí)向a發(fā)送消息不會(huì)崩潰:在Objective-C中向nil發(fā)送消息是安全的。
3.釋放時(shí),調(diào)用clearDeallocating函數(shù)许布。clearDeallocating函數(shù)首先根據(jù)對(duì)象地址獲取所有weak指針地址的數(shù)組玫膀,然后遍歷這個(gè)數(shù)組把其中的數(shù)據(jù)設(shè)為nil,最后把這個(gè)entry從weak表中刪除爹脾,最后清理對(duì)象的記錄。
當(dāng)weak引用指向的對(duì)象被釋放時(shí)箕昭,又是如何去處理weak指針的呢灵妨?當(dāng)釋放對(duì)象時(shí),其基本流程如下:
1.調(diào)用objc_release
2.因?yàn)閷?duì)象的引用計(jì)數(shù)為0落竹,所以執(zhí)行dealloc
3.在dealloc中泌霍,調(diào)用了_objc_rootDealloc函數(shù)
4.在_objc_rootDealloc中,調(diào)用了object_dispose函數(shù)
5.調(diào)用objc_destructInstance
6.最后調(diào)用objc_clear_deallocating
對(duì)象被釋放時(shí)調(diào)用的objc_clear_deallocating函數(shù):
1.從weak表中獲取廢棄對(duì)象的地址為鍵值的記錄
2.將包含在記錄中的所有附有 weak修飾符變量的地址述召,賦值為nil
3.將weak表中該記錄刪除
4.從引用計(jì)數(shù)表中刪除廢棄對(duì)象的地址為鍵值的記錄
總結(jié):
其實(shí)Weak表是一個(gè)hash(哈希)表朱转,Key是weak所指對(duì)象的地址,Value是weak指針的地址(這個(gè)地址的值是所指對(duì)象指針的地址)數(shù)組积暖。
12.isKindOfClass 與 isMemberOfClass
下面代碼輸出什么藤为?
@interface Sark : NSObject
@end
@implementation Sark
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
NSLog(@"%d %d %d %d", res1, res2, res3, res4);
}
return 0;
}
1000
詳解:
在isKindOfClass
中有一個(gè)循環(huán),先判斷class
是否等于meta class
夺刑,不等就繼續(xù)循環(huán)判斷是否等于meta class
的super class
缅疟,不等再繼續(xù)取super class
,如此循環(huán)下去遍愿。
[NSObject class]
執(zhí)行完之后調(diào)用isKindOfClass
存淫,第一次判斷先判斷NSObject
和 NSObject
的meta class
是否相等,之前講到meta class
的時(shí)候放了一張很詳細(xì)的圖沼填,從圖上我們也可以看出桅咆,NSObject
的meta class
與本身不等。接著第二次循環(huán)判斷NSObject
與meta class
的superclass
是否相等坞笙。還是從那張圖上面我們可以看到:Root class(meta)
的superclass
就是 Root class(class)
岩饼,也就是NSObject本身荚虚。所以第二次循環(huán)相等,于是第一行res1輸出應(yīng)該為YES忌愚。
同理曲管,[Sark class]
執(zhí)行完之后調(diào)用isKindOfClass
,第一次for循環(huán)硕糊,Sark的Meta Class
與[Sark class]
不等院水,第二次for循環(huán),Sark Meta Class
的super class
指向的是 NSObject Meta Class
简十, 和Sark Class
不相等檬某。第三次for循環(huán),NSObject Meta Class
的super class
指向的是NSObject Class
螟蝙,和 Sark Class
不相等恢恼。第四次循環(huán),NSObject Class
的super class
指向 nil胰默, 和 Sark Class
不相等场斑。第四次循環(huán)之后,退出循環(huán)牵署,所以第三行的res3輸出為NO漏隐。
isMemberOfClass
的源碼實(shí)現(xiàn)是拿到自己的isa指針和自己比較,是否相等奴迅。
第二行isa 指向 NSObject
的 Meta Class
青责,所以和 NSObject Class
不相等。第四行取具,isa指向Sark的Meta Class
脖隶,和Sark Class
也不等,所以第二行res2和第四行res4都輸出NO暇检。
13.使用runtime Associate方法關(guān)聯(lián)的對(duì)象产阱,需要在主對(duì)象dealloc的時(shí)候釋放么?
無論在MRC下還是ARC下均不需要占哟,被關(guān)聯(lián)的對(duì)象在生命周期內(nèi)要比對(duì)象本身釋放的晚很多心墅,它們會(huì)在被 NSObject -dealloc 調(diào)用的object_dispose()方法中釋放。
詳解:
1榨乎、調(diào)用 -release :引用計(jì)數(shù)變?yōu)榱?對(duì)象正在被銷毀怎燥,生命周期即將結(jié)束.
不能再有新的 __weak 弱引用,否則將指向 nil.
調(diào)用 [self dealloc]
2蜜暑、 父類調(diào)用 -dealloc
繼承關(guān)系中最直接繼承的父類再調(diào)用 -dealloc
如果是 MRC 代碼 則會(huì)手動(dòng)釋放實(shí)例變量們(iVars)
繼承關(guān)系中每一層的父類 都再調(diào)用 -dealloc
>3铐姚、NSObject 調(diào) -dealloc
只做一件事:調(diào)用 Objective-C runtime 中object_dispose() 方法
>4. 調(diào)用 object_dispose()
為 C++ 的實(shí)例變量們(iVars)調(diào)用 destructors
為 ARC 狀態(tài)下的 實(shí)例變量們(iVars) 調(diào)用 -release
解除所有使用 runtime Associate方法關(guān)聯(lián)的對(duì)象
解除所有 __weak 引用
調(diào)用 free()
14. 什么是method swizzling(俗稱黑魔法)
簡(jiǎn)單說就是進(jìn)行方法交換
詳解:請(qǐng)看Runtime在工作中的運(yùn)用 第五章Runtime方法交換;
在Objective-C中調(diào)用一個(gè)方法,其實(shí)是向一個(gè)對(duì)象發(fā)送消息隐绵,查找消息的唯一依據(jù)是selector的名字之众。利用Objective-C的動(dòng)態(tài)特性,可以實(shí)現(xiàn)在運(yùn)行時(shí)偷換selector對(duì)應(yīng)的方法實(shí)現(xiàn)依许,達(dá)到給方法掛鉤的目的棺禾。
每個(gè)類都有一個(gè)方法列表,存放著方法的名字和方法實(shí)現(xiàn)的映射關(guān)系峭跳,selector的本質(zhì)其實(shí)就是方法名膘婶,IMP有點(diǎn)類似函數(shù)指針,指向具體的Method實(shí)現(xiàn)蛀醉,通過selector就可以找到對(duì)應(yīng)的IMP悬襟。
換方法的幾種實(shí)現(xiàn)方式
- 利用 method_exchangeImplementations 交換兩個(gè)方法的實(shí)現(xiàn)
- 利用 class_replaceMethod 替換方法的實(shí)現(xiàn)
- 利用 method_setImplementation 來直接設(shè)置某個(gè)方法的IMP
15.Compile Error / Runtime Crash / NSLog…?
下面的代碼會(huì)?Compile Error
/ Runtime Crash
/ NSLog…
?
@interface NSObject (Sark)
+ (void)foo;
- (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo {
NSLog(@"IMP: -[NSObject (Sark) foo]");
}
@end
// 測(cè)試代碼
[NSObject foo];
[[NSObject new] performSelector:@selector(foo)];
IMP: -[NSObject(Sark) foo] 拯刁,全都正常輸出脊岳,編譯和運(yùn)行都沒有問題。
詳解:
這道題和上一道題很相似垛玻,第二個(gè)調(diào)用肯定沒有問題割捅,第一個(gè)調(diào)用后會(huì)從元類中查找方法,然而方法并不在元類中帚桩,所以找元類的superclass
棺牧。方法定義在是NSObject
的Category
,由于NSObject
的對(duì)象模型比較特殊朗儒,元類的superclass
是類對(duì)象,所以從類對(duì)象中找到了方法并調(diào)用参淹。
感謝:
霜神醉锄、iOS程序犭袁、sunnyxx