runtime怎么添加屬性廷支、方法等
- ivar表示成員變量
- class_addIvar
- class_addMethod
- class_addProperty
- class_addProtocol
- class_replaceProperty
是否可以把比較耗時的操作放在NSNotificationCenter中
- 首先必須明確通知在哪個線程中發(fā)出蓝晒,那么處理接受到通知的方法也在這個線程中調(diào)用
- 如果在異步線程發(fā)的通知腹殿,那么可以執(zhí)行比較耗時的操作薪夕;
- 如果在主線程發(fā)的通知浓瞪,那么就不可以執(zhí)行比較耗時的操作
runtime 如何實現(xiàn) weak 屬性
首先要搞清楚weak屬性的特點
weak策略表明該屬性定義了一種“非擁有關(guān)系” (nonowning relationship)蹄咖。
為這種屬性設(shè)置新值時,設(shè)置方法既不保留新值缸血,也不釋放舊值。此特質(zhì)同assign類似;
然而在屬性所指的對象遭到摧毀時械筛,屬性值也會清空(nil out)
作為一個開發(fā)者捎泻,有一個學(xué)習(xí)的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:413038000埋哟,不管你是大牛還是小白都歡迎入駐 笆豁,分享BAT,阿里面試題、面試經(jīng)驗赤赊,討論技術(shù)闯狱, 大家一起交流學(xué)習(xí)成長!
推薦閱讀
iOS開發(fā)——最新 BAT面試題合集(持續(xù)更新中)
那么runtime如何實現(xiàn)weak變量的自動置nil砍鸠?
runtime對注冊的類扩氢,會進行布局,會將 weak 對象放入一個 hash 表中爷辱。
用 weak 指向的對象內(nèi)存地址作為 key录豺,當(dāng)此對象的引用計數(shù)為0的時候會調(diào)用對象的 dealloc 方法,
假設(shè) weak 指向的對象內(nèi)存地址是a饭弓,那么就會以a為key双饥,在這個 weak hash表中搜索,找到所有以a為key的 weak 對象弟断,從而設(shè)置為 nil咏花。
weak屬性需要在dealloc中置nil么
- 在ARC環(huán)境無論是強指針還是弱指針都無需在 dealloc 設(shè)置為 nil , ARC 會自動幫我們處理
- 即便是編譯器不幫我們做這些,weak也不需要在dealloc中置nil
- 在屬性所指的對象遭到摧毀時昏翰,屬性值也會清空
// 模擬下weak的setter方法苍匆,大致如下
- (void)setObject:(NSObject *)object
{
objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
[object cyl_runAtDealloc:^{
_object = nil;
}];
}
一個Objective-C對象如何進行內(nèi)存布局?(考慮有父類的情況)
- 所有父類的成員變量和自己的成員變量都會存放在該對象所對應(yīng)的存儲空間中
- 父類的方法和自己的方法都會緩存在類對象的方法緩存中棚菊,類方法是緩存在元類對象中
- 每一個對象內(nèi)部都有一個isa指針,指向他的類對象,類對象中存放著本對象的如下信息
- 對象方法列表
- 成員變量的列表
- 屬性列表
- 每個 Objective-C 對象都有相同的結(jié)構(gòu)浸踩,如下圖所示
Objective-C 對象的結(jié)構(gòu)圖 |
---|
ISA指針 |
根類(NSObject)的實例變量 |
倒數(shù)第二層父類的實例變量 |
... |
父類的實例變量 |
類的實例變量 |
- 根類對象就是NSObject,它的super class指針指向nil
- 類對象既然稱為對象统求,那它也是一個實例检碗。類對象中也有一個isa指針指向它的元類(meta class),即類對象是元類的實例码邻。元類內(nèi)部存放的是類方法列表折剃,根元類的isa指針指向自己,superclass指針指向NSObject類
一個objc對象的isa的指針指向什么像屋?有什么作用怕犁?
- 每一個對象內(nèi)部都有一個isa指針,這個指針是指向它的真實類型
- 根據(jù)這個指針就能知道將來調(diào)用哪個類的方法
下面的代碼輸出什么开睡?
@implementation Son : Father
- (id)init
{
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
答案:都輸出 Son
-
這個題目主要是考察關(guān)于objc中對 self 和 super 的理解:
self 是類的隱藏參數(shù)因苹,指向當(dāng)前調(diào)用方法的這個類的實例。而 super 本質(zhì)是一個編譯器標(biāo)示符篇恒,和 self 是指向的同一個消息接受者
當(dāng)使用 self 調(diào)用方法時扶檐,會從當(dāng)前類的方法列表中開始找,如果沒有胁艰,就從父類中再找款筑;
而當(dāng)使用 super時,則從父類的方法列表中開始找腾么。然后調(diào)用父類的這個方法
-
調(diào)用[self class] 時奈梳,會轉(zhuǎn)化成 objc_msgSend函數(shù)
id objc_msgSend(id self, SEL op, ...)
-
調(diào)用 [super class]時,會轉(zhuǎn)化成 objc_msgSendSuper函數(shù)
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
-
第一個參數(shù)是 objc_super 這樣一個結(jié)構(gòu)體解虱,其定義如下
struct objc_super { __unsafe_unretained id receiver; __unsafe_unretained Class super_class; };
第一個成員是 receiver, 類似于上面的 objc_msgSend函數(shù)第一個參數(shù)self
第二個成員是記錄當(dāng)前類的父類是什么攘须,告訴程序從父類中開始找方法,找到方法后殴泰,最后內(nèi)部是使用 objc_msgSend(objc_super->receiver, @selector(class))去調(diào)用于宙, 此時已經(jīng)和[self class]調(diào)用相同了,故上述輸出結(jié)果仍然返回 Son
objc Runtime開源代碼對- (Class)class方法的實現(xiàn)
-(Class)class {
return object_getClass(self);
}
runtime如何通過selector找到對應(yīng)的IMP地址悍汛?(分別考慮類方法和實例方法)
- 每一個類對象中都一個對象方法列表(對象方法緩存)
- 類方法列表是存放在類對象中isa指針指向的元類對象中(類方法緩存)
- 方法列表中每個方法結(jié)構(gòu)體中記錄著方法的名稱,方法實現(xiàn),以及參數(shù)類型捞魁,其實selector本質(zhì)就是方法名稱,通過這個方法名稱就可以在方法列表中找到對應(yīng)的方法實現(xiàn).
- 當(dāng)我們發(fā)送一個消息給一個NSObject對象時,這條消息會在對象的類對象方法列表里查找
- 當(dāng)我們發(fā)送一個消息給一個類時离咐,這條消息會在類的Meta Class對象的方法列表里查找
objc中的類方法和實例方法有什么本質(zhì)區(qū)別和聯(lián)系
類方法
1 類方法是屬于類對象的
2 類方法只能通過類對象調(diào)用
3 類方法中的self是類對象
4 類方法可以調(diào)用其他的類方法
5 類方法中不能訪問成員變量
6 類方法中不能直接調(diào)用對象方法
7 類方法是存儲在元類對象的方法緩存中
實例方法
1 實例方法是屬于實例對象的
2 實例方法只能通過實例對象調(diào)用
3 實例方法中的self是實例對象
4 實例方法中可以訪問成員變量
5 實例方法中直接調(diào)用實例方法
6 實例方法中可以調(diào)用類方法(通過類名)
7 實例方法是存放在類對象的方法緩存中
使用runtime Associate方法關(guān)聯(lián)的對象谱俭,需要在主對象dealloc的時候釋放么?
- 無論在MRC下還是ARC下均不需要
- 被關(guān)聯(lián)的對象在生命周期內(nèi)要比對象本身釋放的晚很多,它們會在被 NSObject -dealloc 調(diào)用的 object_dispose()方法中釋放
- 補充:對象的內(nèi)存銷毀時間表昆著,分四個步驟
1.調(diào)用 -release :引用計數(shù)變?yōu)榱? * 對象正在被銷毀县貌,生命周期即將結(jié)束.
* 不能再有新的 __weak 弱引用,否則將指向 nil.
* 調(diào)用 [self dealloc]
2\. 父類調(diào)用 -dealloc
* 繼承關(guān)系中最直接繼承的父類再調(diào)用 -dealloc
* 如果是 MRC 代碼 則會手動釋放實例變量們(iVars)
* 繼承關(guān)系中每一層的父類 都再調(diào)用 -dealloc
3\. NSObject 調(diào) -dealloc
* 只做一件事:調(diào)用 Objective-C runtime 中的 object_dispose() 方法
4\. 調(diào)用 object_dispose()
* 為 C++ 的實例變量們(iVars)調(diào)用 destructors
* 為 ARC 狀態(tài)下的 實例變量們(iVars) 調(diào)用 -release
* 解除所有使用 runtime Associate方法關(guān)聯(lián)的對象
* 解除所有 __weak 引用
* 調(diào)用 free()
_objc_msgForward函數(shù)是做什么的宣吱?直接調(diào)用它將會發(fā)生什么窃这?
- _objc_msgForward是IMP類型,用于消息轉(zhuǎn)發(fā)的:當(dāng)向一個對象發(fā)送一條消息征候,但它并沒有實現(xiàn)的時候,_objc_msgForward會嘗試做消息轉(zhuǎn)發(fā)
- 直接調(diào)用_objc_msgForward是非常危險的事祟敛,這是把雙刃刀疤坝,如果用不好會直接導(dǎo)致程序Crash,但是如果用得好馆铁,能做很多非撑苋啵酷的事
- JSPatch就是直接調(diào)用_objc_msgForward來實現(xiàn)其核心功能的
能否向編譯后得到的類中增加實例變量?能否向運行時創(chuàng)建的類中添加實例變量埠巨?為什么历谍?
- 不能向編譯后得到的類中增加實例變量;
- 能向運行時創(chuàng)建的類中添加實例變量辣垒;
- 分析如下:
- 因為編譯后的類已經(jīng)注冊在runtime中望侈,類結(jié)構(gòu)體中的objc_ivar_list 實例變量的鏈表和instance_size實例變量的內(nèi)存大小已經(jīng)確定,同時runtime 會調(diào)用class_setIvarLayout 或 class_setWeakIvarLayout來處理strong weak引用勋桶,所以不能向存在的類中添加實例變量
- 運行時創(chuàng)建的類是可以添加實例變量脱衙,調(diào)用 class_addIvar函數(shù),但是得在調(diào)用objc_allocateClassPair之后例驹,objc_registerClassPair之前捐韩,原因同上。
runloop和線程有什么關(guān)系鹃锈?
- 每條線程都有唯一的一個RunLoop對象與之對應(yīng)的
- 主線程的RunLoop是自動創(chuàng)建并啟動
- 子線程的RunLoop需要手動創(chuàng)建
- 子線程的RunLoop創(chuàng)建步驟如下:
在子線程中調(diào)用[NSRunLoop currentRunLoop]創(chuàng)建RunLoop對象(懶加載荤胁,只創(chuàng)建一次)
-
獲得RunLoop對象后要調(diào)用run方法來啟動一個運行循環(huán)
// 啟動RunLoop [[NSRunLoop currentRunLoop] run];
-
RunLoop的其他啟動方法
// 第一個參數(shù):指定運行模式 // 第二個參數(shù):指定RunLoop的過期時間,即:到了這個時間后RunLoop就失效了 [[NSRunLoop currentRunLoop] runMode:kCFRunLoopDefaultMode beforeDate:[NSDate distantFuture]];
runloop的mode作用是什么屎债?
- 用來控制一些特殊操作只能在指定模式下運行仅政,一般可以通過指定操作的運行mode來控制執(zhí)行時機,以提高用戶體驗
- 系統(tǒng)默認(rèn)注冊了5個Mode
- kCFRunLoopDefaultMode:App的默認(rèn)Mode扔茅,通常主線程是在這個Mode下運行已旧,對應(yīng)OC中的:NSDefaultRunLoopMode
- UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動召娜,保證界面滑動時不受其他Mode影響
- kCFRunLoopCommonModes:這是一個標(biāo)記Mode运褪,不是一種真正的Mode,事件可以運行在所有標(biāo)有common modes標(biāo)記的模式中,對應(yīng)OC中的NSRunLoopCommonModes秸讹,帶有common modes標(biāo)記的模式有:UITrackingRunLoopMode和kCFRunLoopDefaultMode
- UIInitializationRunLoopMode:在啟動 App時進入的第一個 Mode檀咙,啟動完成后就不再使用
- GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內(nèi)部Mode,通常用不到
以+scheduledTimerWithTimeInterval...的方式觸發(fā)的timer璃诀,在滑動頁面上的列表時弧可,timer會暫定回調(diào),為什么劣欢?如何解決棕诵?
這里強調(diào)一點:在主線程中以+scheduledTimerWithTimeInterval...的方式觸發(fā)的timer默認(rèn)是運行在NSDefaultRunLoopMode模式下的,當(dāng)滑動頁面上的列表時凿将,進入了UITrackingRunLoopMode模式校套,這時候timer就會停止
可以修改timer的運行模式為NSRunLoopCommonModes,這樣定時器就可以一直運行了
-
以下是我的筆記補充:
-
在子線程中通過scheduledTimerWithTimeInterval:...方法來構(gòu)建NSTimer
- 方法內(nèi)部已經(jīng)創(chuàng)建NSTimer對象牧抵,并加入到RunLoop中笛匙,運行模式為NSDefaultRunLoopMode
- 由于Mode有timer對象,所以RunLoop就開始監(jiān)聽定時器事件了犀变,從而開始進入運行循環(huán)
- 這個方法僅僅是創(chuàng)建RunLoop對象妹孙,并不會主動啟動RunLoop,需要再調(diào)用run方法來啟動
-
如果在主線程中通過scheduledTimerWithTimeInterval:...方法來構(gòu)建NSTimer获枝,就不需要主動啟動RunLoop對象蠢正,因為主線程的RunLoop對象在程序運行起來就已經(jīng)被啟動了
// userInfo參數(shù):用來給NSTimer的userInfo屬性賦值,userInfo是只讀的映琳,只能在構(gòu)建NSTimer對象時賦值 [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(run:) userInfo:@"ya了個hoo" repeats:YES]; // scheduledTimer...方法創(chuàng)建出來NSTimer雖然已經(jīng)指定了默認(rèn)模式机隙,但是【允許你修改模式】 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; // 【僅在子線程】需要手動啟動RunLoop對象,進入運行循環(huán) [[NSRunLoop currentRunLoop] run];
-
猜想runloop內(nèi)部是如何實現(xiàn)的萨西?
- 從字面意思看:運行循環(huán)有鹿、跑圈;
- 本質(zhì):內(nèi)部就是do-while循環(huán)谎脯,在這個循環(huán)內(nèi)部不斷地處理各種事件(任務(wù))葱跋,比如:Source、Timer源梭、Observer娱俺;
- 每條線程都有唯一一個RunLoop對象與之對應(yīng),主線程的RunLoop默認(rèn)已經(jīng)啟動废麻,子線程的RunLoop需要手動啟動荠卷;
- 每次RunLoop啟動時,只能指定其中一個 Mode烛愧,這個Mode被稱作 CurrentMode油宜,如果需要切換Mode掂碱,只能退出Loop,再重新指定一個Mode進入慎冤,這樣做主要是為了隔離不同Mode中的Source疼燥、Timer、Observer蚁堤,讓其互不影響醉者;
不手動指定autoreleasepool的前提下,一個autorealese對象在什么時刻釋放披诗?(比如在一個vc的viewDidLoad中創(chuàng)建)
- 分兩種情況:手動干預(yù)釋放時機撬即、系統(tǒng)自動去釋放
- 手動干預(yù)釋放時機:指定autoreleasepool就是所謂的:當(dāng)前作用域大括號結(jié)束時就立即釋放
- 系統(tǒng)自動去釋放:不手動指定autoreleasepool,Autorelease對象會在當(dāng)前的 runloop 迭代結(jié)束時釋放藤巢,下面詳細說明釋放時機
- RunLoop中的三個狀態(tài)會處理自動釋放池搞莺,通過打印代碼發(fā)現(xiàn)有兩個Observer監(jiān)聽到狀態(tài)值為:1和160(32+128)
- kCFRunLoopEntry(1) // 第一次進入會創(chuàng)建一個自動釋放池
- kCFRunLoopBeforeWaiting(32) // 進入休眠狀態(tài)前先銷毀自動釋放池,再創(chuàng)建一個新的自動釋放池
- kCFRunLoopExit(128) // 退出RunLoop時銷毀最后一次創(chuàng)建的自動釋放池
- RunLoop中的三個狀態(tài)會處理自動釋放池搞莺,通過打印代碼發(fā)現(xiàn)有兩個Observer監(jiān)聽到狀態(tài)值為:1和160(32+128)
- 如果在一個vc的viewDidLoad中創(chuàng)建一個Autorelease對象掂咒,那么該對象會在 viewDidAppear 方法執(zhí)行前就被銷毀了(是這樣的嗎?迈喉?绍刮?)
蘋果是如何實現(xiàn)autoreleasepool的?
- autoreleasepool以一個隊列數(shù)組的形式實現(xiàn),主要通過下列三個函數(shù)完成.
objc_autoreleasepoolPush
objc_autoreleasepoolPop
objc_aurorelease
- 看函數(shù)名就可以知道挨摸,對autorelease分別執(zhí)行push孩革,和pop操作。銷毀對象時執(zhí)行release操作
GCD的隊列(dispatch_queue_t)分哪兩種類型得运?背后的線程模型是什么樣的膝蜈?
- 串行隊列
- 并行隊列
- dispatch_global_queue();是全局并發(fā)隊列
- dispatch_main_queue();是一種特殊串行隊列
- 背后的線程模型:自定義隊列 dispatch_queue_t queue; 可以自定義是并行:DISPATCH_QUEUE_CONCURRENT 或者 串行DISPATCH_QUEUE_SERIAL
蘋果為什么要廢棄dispatch_get_current_queue?
- 容易誤用造成死鎖
如何用GCD同步若干個異步調(diào)用熔掺?(如根據(jù)若干個url異步加載多張圖片饱搏,然后在都下載完成后合成一張整圖)
- 必須是并發(fā)隊列才起作用
- 需求分析
- 首先,分別異步執(zhí)行2個耗時的操作
- 其次置逻,等2個異步操作都執(zhí)行完畢后推沸,再回到主線程執(zhí)行一些操作
- 使用隊列組實現(xiàn)上面的需求
// 創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
// 獲取全局并發(fā)隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 往隊列組中添加耗時操作
dispatch_group_async(group, queue, ^{
// 執(zhí)行耗時的異步操作1
});
// 往隊列組中添加耗時操作
dispatch_group_async(group, queue, ^{
// 執(zhí)行耗時的異步操作2
});
// 當(dāng)并發(fā)隊列組中的任務(wù)執(zhí)行完畢后才會執(zhí)行這里的代碼
dispatch_group_notify(group, queue, ^{
// 如果這里還有基于上面兩個任務(wù)的結(jié)果繼續(xù)執(zhí)行一些代碼,建議還是放到子線程中券坞,等代碼執(zhí)行完畢后在回到主線程
// 回到主線程
dispatch_async(group, dispatch_get_main_queue(), ^{
// 執(zhí)行相關(guān)代碼...
});
});
dispatch_barrier_async的作用是什么鬓催?
- 函數(shù)定義
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
- 必須是并發(fā)隊列,要是串行隊列恨锚,這個函數(shù)就沒啥意義了
- 注意:這個函數(shù)的第一個參數(shù)queue不能是全局的并發(fā)隊列
- 作用:在它前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行宇驾,在它后面的任務(wù)等它執(zhí)行完成后才會執(zhí)
- 示例代碼
-(void)barrier
{
dispatch_queue_t queue = dispatch_queue_create("12342234", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----2-----%@", [NSThread currentThread]);
});
// 在它前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行,在它后面的任務(wù)等它執(zhí)行完成后才會執(zhí)行
dispatch_barrier_async(queue, ^{
NSLog(@"----barrier-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----4-----%@", [NSThread currentThread]);
});
}
以下代碼運行結(jié)果如何猴伶?
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
- 答案:主線程死鎖
lldb(gdb)常用的調(diào)試命令课舍?
- po:打印對象塌西,會調(diào)用對象description方法。是print-object的簡寫
- expr:可以在調(diào)試時動態(tài)執(zhí)行指定表達式布卡,并將結(jié)果打印出來雨让,很有用的命令
- print:也是打印命令,需要指定類型
- bt:打印調(diào)用堆棧忿等,是thread backtrace的簡寫栖忠,加all可打印所有thread的堆棧
- br l:是breakpoint list的簡寫
BAD_ACCESS在什么情況下出現(xiàn)?
- 訪問一個僵尸對象贸街,訪問僵尸對象的成員變量或者向其發(fā)消息
- 死循環(huán)
如何調(diào)試BAD_ACCESS錯誤
- 設(shè)置全局?jǐn)帱c快速定位問題代碼所在行
- 開啟僵尸對象調(diào)試功能
簡述下Objective-C中調(diào)用方法的過程(runtime)
- Objective-C是動態(tài)語言庵寞,每個方法在運行時會被動態(tài)轉(zhuǎn)為消息發(fā)送,即:objc_msgSend(receiver, selector)薛匪,整個過程介紹如下:
- objc在向一個對象發(fā)送消息時捐川,runtime庫會根據(jù)對象的isa指針找到該對象實際所屬的類
- 然后在該類中的方法列表以及其父類方法列表中尋找方法運行
- 如果,在最頂層的父類(一般也就NSObject)中依然找不到相應(yīng)的方法時逸尖,程序在運行時會掛掉并拋出異常unrecognized selector sent to XXX
- 但是在這之前古沥,objc的運行時會給出三次拯救程序崩潰的機會,這三次拯救程序奔潰的說明見問題《什么時候會報unrecognized selector的異辰扛》中的說明
- 補充說明:Runtime 鑄就了Objective-C 是動態(tài)語言的特性岩齿,使得C語言具備了面向?qū)ο蟮奶匦裕诔绦蜻\行期創(chuàng)建苞俘,檢查盹沈,修改類、對象及其對應(yīng)的方法吃谣,這些操作都可以使用runtime中的對應(yīng)方法實現(xiàn)乞封。
什么是method swizzling(俗稱黑魔法)
- 簡單說就是進行方法交換
- 在Objective-C中調(diào)用一個方法,其實是向一個對象發(fā)送消息岗憋,查找消息的唯一依據(jù)是selector的名字肃晚。利用Objective-C的動態(tài)特性,可以實現(xiàn)在運行時偷換selector對應(yīng)的方法實現(xiàn)澜驮,達到給方法掛鉤的目的
- 每個類都有一個方法列表陷揪,存放著方法的名字和方法實現(xiàn)的映射關(guān)系,selector的本質(zhì)其實就是方法名杂穷,IMP有點類似函數(shù)指針悍缠,指向具體的Method實現(xiàn),通過selector就可以找到對應(yīng)的IMP
- 交換方法的幾種實現(xiàn)方式
- 利用 method_exchangeImplementations 交換兩個方法的實現(xiàn)
- 利用 class_replaceMethod 替換方法的實現(xiàn)
- 利用 method_setImplementation 來直接設(shè)置某個方法的IMP
objc中向一個nil對象發(fā)送消息將會發(fā)生什么耐量?
-
在Objective-C中向nil發(fā)送消息是完全有效的——只是在運行時不會有任何作用
- 如果一個方法返回值是一個對象飞蚓,那么發(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)體中各個字段的值將都是0
- 如果方法的返回值不是上述提到的幾種情況著榴,那么發(fā)送給nil的消息的返回值將是未定義的
-
具體原因分析
- objc是動態(tài)語言添履,每個方法在運行時會被動態(tài)轉(zhuǎn)為消息發(fā)送,即:objc_msgSend(receiver, selector)
- 為了方便理解這個內(nèi)容脑又,還是貼一個objc的源代碼
struct objc_class
{
// isa指針指向Meta Class暮胧,因為Objc的類的本身也是一個Object,
// 為了處理這個關(guān)系问麸,runtime就創(chuàng)造了Meta Class往衷,
// 當(dāng)給類發(fā)送[NSObject alloc]這樣消息時,實際上是把這個消息發(fā)給了Class Object
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息严卖,默認(rèn)為0
long info OBJC2_UNAVAILABLE; // 類信息席舍,供運行期使用的一些位標(biāo)識
long instance_size OBJC2_UNAVAILABLE; // 該類的實例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
// 方法緩存,對象接到一個消息會根據(jù)isa指針查找消息對象哮笆,
// 這時會在method Lists中遍歷来颤,
// 如果cache了,常用的方法調(diào)用時就能夠提高調(diào)用的效率稠肘。
// 這個方法緩存只存在一份脚曾,不是每個類的實例對象都有一個方法緩存
// 子類會在自己的方法緩存中緩存父類的方法,父類在自己的方法緩存中也會緩存自己的方法启具,而不是說子類就不緩存父類方法了
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
- objc在向一個對象發(fā)送消息時,runtime庫會根據(jù)對象的isa指針找到該對象實際所屬的類珊泳,然后在該類中的方法列表以及其父類方法列表中尋找方法運行鲁冯,然后再發(fā)送消息的時候,objc_msgSend方法不會返回值色查,所謂的返回內(nèi)容都是具體調(diào)用時執(zhí)行的薯演。
- 如果向一個nil對象發(fā)送消息,首先在尋找對象的isa指針時就是0地址返回了秧了,所以不會出現(xiàn)任何錯誤
objc中向一個對象發(fā)送消息[obj foo]和objc_msgSend()函數(shù)之間有什么關(guān)系跨扮?
- [obj foo];在objc動態(tài)編譯時,會被轉(zhuǎn)意為:objc_msgSend(obj, @selector(foo));
什么時候會報unrecognized selector的異常验毡?
當(dāng)調(diào)用該對象上某個方法,而該對象上沒有實現(xiàn)這個方法的時候衡创, 可以通過“消息轉(zhuǎn)發(fā)”進行解決,如果還是不行就會報unrecognized selector異常
-
objc是動態(tài)語言晶通,每個方法在運行時會被動態(tài)轉(zhuǎn)為消息發(fā)送璃氢,即:objc_msgSend(receiver, selector),整個過程介紹如下:
- objc在向一個對象發(fā)送消息時狮辽,runtime庫會根據(jù)對象的isa指針找到該對象實際所屬的類
- 然后在該類中的方法列表以及其父類方法列表中尋找方法運行
- 如果一也,在最頂層的父類中依然找不到相應(yīng)的方法時巢寡,程序在運行時會掛掉并拋出異常unrecognized selector sent to XXX 。但是在這之前抑月,objc的運行時會給出三次拯救程序崩潰的機會
-
三次拯救程序崩潰的機會
- Method resolution
- objc運行時會調(diào)用+resolveInstanceMethod:或者 +resolveClassMethod:,讓你有機會提供一個函數(shù)實現(xiàn)舆蝴。
- 如果你添加了函數(shù)并返回 YES谦絮,那運行時系統(tǒng)就會重新啟動一次消息發(fā)送的過程
- 如果 resolve 方法返回 NO ,運行時就會移到下一步须误,消息轉(zhuǎn)發(fā)
- Fast forwarding
- 如果目標(biāo)對象實現(xiàn)了-forwardingTargetForSelector:挨稿,Runtime 這時就會調(diào)用這個方法,給你把這個消息轉(zhuǎn)發(fā)給其他對象的機會
- 只要這個方法返回的不是nil和self京痢,整個消息發(fā)送的過程就會被重啟奶甘,當(dāng)然發(fā)送的對象會變成你返回的那個對象。
- 否則祭椰,就會繼續(xù)Normal Fowarding臭家。
- 這里叫Fast,只是為了區(qū)別下一步的轉(zhuǎn)發(fā)機制方淤。因為這一步不會創(chuàng)建任何新的對象钉赁,但Normal forwarding轉(zhuǎn)發(fā)會創(chuàng)建一個NSInvocation對象,相對Normal forwarding轉(zhuǎn)發(fā)更快點携茂,所以這里叫Fast forwarding
- Normal forwarding
- 這一步是Runtime最后一次給你挽救的機會你踩。
- 首先它會發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型。
- 如果-methodSignatureForSelector:返回nil讳苦,Runtime則會發(fā)出-doesNotRecognizeSelector:消息带膜,程序這時也就掛掉了。
- 如果返回了一個函數(shù)簽名鸳谜,Runtime就會創(chuàng)建一個NSInvocation對象并發(fā)送-forwardInvocation:消息給目標(biāo)對象
- Method resolution
使用系統(tǒng)的某些block api(如UIView的block版本寫動畫時)膝藕,是否也考慮循環(huán)引用問題?
- 系統(tǒng)的某些block api中咐扭,UIView的block版本寫動畫時不需要考慮芭挽,但也有一些api 需要考慮
- 以下這些使用方式不會引起循環(huán)引用的問題
[UIView animateWithDuration:duration animations:^
{ [self.superview layoutIfNeeded]; }];
[[NSOperationQueue mainQueue] addOperationWithBlock:^
{ self.someProperty = xyz; }];
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification"
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * notification)
{ self.someProperty = xyz; }];
-
但如果方法中的一些參數(shù)是 成員變量,那么可以造成循環(huán)引用蝗肪,如 GCD 袜爪、NSNotificationCenter調(diào)用就要小心一點,比如 GCD 內(nèi)部如果引用了 self穗慕,而且 GCD 的參數(shù)是 成員變量饿敲,則要考慮到循環(huán)引用,舉例如下:
-
GCD
- 分析:self-->_operationsQueue-->block-->self形成閉環(huán)逛绵,就造成了循環(huán)引用
__weak __typeof__(self) weakSelf = self; dispatch_group_async(_operationsGroup, _operationsQueue, ^ { [weakSelf doSomething]; [weakSelf doSomethingElse]; } );
-
NSNotificationCenter
- 分析:self-->_observer-->block-->self形成閉環(huán)怀各,就造成了循環(huán)引用
__weak __typeof__(self) weakSelf = self; _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey" object:nil queue:nil usingBlock:^(NSNotification *note){ [weakSelf dismissModalViewControllerAnimated:YES]; }];
-
作為一個開發(fā)者倔韭,有一個學(xué)習(xí)的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:413038000瓢对,不管你是大牛還是小白都歡迎入駐 寿酌,分享BAT,阿里面試題、面試經(jīng)驗硕蛹,討論技術(shù)醇疼, 大家一起交流學(xué)習(xí)成長!