招聘一個(gè)靠譜的 iOS(下)

轉(zhuǎn)載:ChenYilong/iOSInterviewQuestions

說(shuō)明:面試題來(lái)源是微博@我就叫Sunny怎么了的這篇博文:《招聘一個(gè)靠譜的 iOS》宙帝,其中共55題,除第一題為糾錯(cuò)題外庇麦,其他54道均為簡(jiǎn)答題。

出題者簡(jiǎn)介: 孫源(sunnyxx),目前就職于百度,負(fù)責(zé)百度知道 iOS 客戶(hù)端的開(kāi)發(fā)工作粱玲,對(duì)技術(shù)喜歡刨根問(wèn)底和總結(jié)最佳實(shí)踐,熱愛(ài)分享和開(kāi)源拜轨,維護(hù)一個(gè)叫 forkingdog 的開(kāi)源小組密幔。

答案為微博@iOS程序犭袁整理,未經(jīng)出題者校對(duì)撩轰,如有紕漏,請(qǐng)向微博@iOS程序犭袁指正堪嫂。


索引

  1. 25. _objc_msgForward 函數(shù)是做什么的,直接調(diào)用它將會(huì)發(fā)生什么?
  2. 26. runtime如何實(shí)現(xiàn)weak變量的自動(dòng)置nil翅萤?
  3. 27. 能否向編譯后得到的類(lèi)中增加實(shí)例變量?能否向運(yùn)行時(shí)創(chuàng)建的類(lèi)中添加實(shí)例變量?為什么阵苇?
  4. 28. runloop和線程有什么關(guān)系?
  5. 29. runloop的mode作用是什么?
  6. 30. 以+ scheduledTimerWithTimeInterval...的方式觸發(fā)的timer搪花,在滑動(dòng)頁(yè)面上的列表時(shí),timer會(huì)暫定回調(diào),為什么咧擂?如何解決攻臀?
  7. 31. 猜想runloop內(nèi)部是如何實(shí)現(xiàn)的?
  8. 32. objc使用什么機(jī)制管理對(duì)象內(nèi)存?
  9. 33. ARC通過(guò)什么方式幫助開(kāi)發(fā)者管理內(nèi)存?
  10. 34. 不手動(dòng)指定autoreleasepool的前提下耀盗,一個(gè)autorealese對(duì)象在什么時(shí)刻釋放忿薇?(比如在一個(gè)vc的viewDidLoad中創(chuàng)建)
  11. 35. BAD_ACCESS在什么情況下出現(xiàn)?
  12. 36. 蘋(píng)果是如何實(shí)現(xiàn)autoreleasepool的婿崭?
  13. 37. 使用block時(shí)什么情況會(huì)發(fā)生引用循環(huán)授瘦,如何解決?
  14. 38. 在block內(nèi)如何修改block外部變量挪捕?
  15. 39. 使用系統(tǒng)的某些block api(如UIView的block版本寫(xiě)動(dòng)畫(huà)時(shí))孩锡,是否也考慮引用循環(huán)問(wèn)題男韧?
  16. 40. GCD的隊(duì)列(dispatch_queue_t)分哪兩種類(lèi)型?
  17. 41. 如何用GCD同步若干個(gè)異步調(diào)用荆隘?(如根據(jù)若干個(gè)url異步加載多張圖片,然后在都下載完成后合成一張整圖)
  18. 42. dispatch_barrier_async的作用是什么积锅?
  19. 43. 蘋(píng)果為什么要廢棄dispatch_get_current_queue硫痰?
  20. 44. 以下代碼運(yùn)行結(jié)果如何敌完?
- (void)viewDidLoad
{
   [super viewDidLoad];
   NSLog(@"1");
   dispatch_sync(dispatch_get_main_queue(), ^{
       NSLog(@"2");
   });
   NSLog(@"3");
}
  1. 45. addObserver:forKeyPath:options:context:各個(gè)參數(shù)的作用分別是什么勤家,observer中需要實(shí)現(xiàn)哪個(gè)方法才能獲得KVO回調(diào)近尚?
  2. 46. 如何手動(dòng)觸發(fā)一個(gè)value的KVO
  3. 47. 若一個(gè)類(lèi)有實(shí)例變量 NSString *_foo 拒迅,調(diào)用setValue:forKey:時(shí)骚秦,可以以foo還是 _foo 作為key?
  4. 48. KVC的keyPath中的集合運(yùn)算符如何使用璧微?
  5. 49. KVC和KVO的keyPath一定是屬性么作箍?
  6. 50. 如何關(guān)閉默認(rèn)的KVO的默認(rèn)實(shí)現(xiàn),并進(jìn)入自定義的KVO實(shí)現(xiàn)前硫?
  7. 51. apple用什么方式實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO胞得?
  8. 52. IBOutlet連出來(lái)的視圖屬性為什么可以被設(shè)置成weak?
  9. 53. IB中User Defined Runtime Attributes如何使用?
  10. 54. 如何調(diào)試BAD_ACCESS錯(cuò)誤
  11. 55. lldb(gdb)常用的調(diào)試命令开瞭?

25. _objc_msgForward函數(shù)是做什么的懒震,直接調(diào)用它將會(huì)發(fā)生什么罩息?

_objc_msgForward是 IMP 類(lèi)型,用于消息轉(zhuǎn)發(fā)的:當(dāng)向一個(gè)對(duì)象發(fā)送一條消息个扰,但它并沒(méi)有實(shí)現(xiàn)的時(shí)候瓷炮,_objc_msgForward會(huì)嘗試做消息轉(zhuǎn)發(fā)。

我們可以這樣創(chuàng)建一個(gè)_objc_msgForward對(duì)象:

IMP msgForwardIMP = _objc_msgForward;

上篇中的《objc中向一個(gè)對(duì)象發(fā)送消息[obj foo]objc_msgSend()函數(shù)之間有什么關(guān)系递宅?》曾提到objc_msgSend在“消息傳遞”中的作用娘香。在“消息傳遞”過(guò)程中,objc_msgSend的動(dòng)作比較清晰:首先在 Class 中的緩存查找 IMP (沒(méi)緩存則初始化緩存)办龄,如果沒(méi)找到烘绽,則向父類(lèi)的 Class 查找。如果一直查找到根類(lèi)仍舊沒(méi)有實(shí)現(xiàn)俐填,則用_objc_msgForward函數(shù)指針代替 IMP 安接。最后,執(zhí)行這個(gè) IMP 英融。

Objective-C運(yùn)行時(shí)是開(kāi)源的盏檐,所以我們可以看到它的實(shí)現(xiàn)。打開(kāi) Apple Open Source 里Mac代碼里的obj包 下載一個(gè)最新版本驶悟,找到 objc-runtime-new.mm胡野,進(jìn)入之后搜索_objc_msgForward

enter image description here

里面有對(duì)_objc_msgForward的功能解釋?zhuān)?/p>

enter image description here
/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. 
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known. 
*   If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use 
*   must be converted to _objc_msgForward or _objc_msgForward_stret.
*   If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/

對(duì) objc-runtime-new.mm文件里與_objc_msgForward有關(guān)的三個(gè)函數(shù)使用偽代碼展示下:

//  objc-runtime-new.mm 文件里與 _objc_msgForward 有關(guān)的三個(gè)函數(shù)使用偽代碼展示
//  Created by https://github.com/ChenYilong
//  Copyright (c)  微博@iOS程序犭袁(http://weibo.com/luohanchenyilong/). All rights reserved.
//  同時(shí)痕鳍,這也是 obj_msgSend 的實(shí)現(xiàn)過(guò)程

id objc_msgSend(id self, SEL op, ...) {
    if (!self) return nil;
    IMP imp = class_getMethodImplementation(self->isa, SEL op);
    imp(self, op, ...); //調(diào)用這個(gè)函數(shù)硫豆,偽代碼...
}
 
//查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
    if (!cls || !sel) return nil;
    IMP imp = lookUpImpOrNil(cls, sel);
    if (!imp) return _objc_msgForward; //_objc_msgForward 用于消息轉(zhuǎn)發(fā)
    return imp;
}
 
IMP lookUpImpOrNil(Class cls, SEL sel) {
    if (!cls->initialize()) {
        _class_initialize(cls);
    }
 
    Class curClass = cls;
    IMP imp = nil;
    do { //先查緩存,緩存沒(méi)有時(shí)重建,仍舊沒(méi)有則向父類(lèi)查詢(xún)
        if (!curClass) break;
        if (!curClass->cache) fill_cache(cls, curClass);
        imp = cache_getImp(curClass, sel);
        if (imp) break;
    } while (curClass = curClass->superclass);
 
    return imp;
}

雖然Apple沒(méi)有公開(kāi)_objc_msgForward的實(shí)現(xiàn)源碼,但是我們還是能得出結(jié)論:

_objc_msgForward是一個(gè)函數(shù)指針(和 IMP 的類(lèi)型一樣)笼呆,是用于消息轉(zhuǎn)發(fā)的:當(dāng)向一個(gè)對(duì)象發(fā)送一條消息熊响,但它并沒(méi)有實(shí)現(xiàn)的時(shí)候,_objc_msgForward會(huì)嘗試做消息轉(zhuǎn)發(fā)诗赌。

上篇中的《objc中向一個(gè)對(duì)象發(fā)送消息[obj foo]objc_msgSend()函數(shù)之間有什么關(guān)系耘眨?》曾提到objc_msgSend在“消息傳遞”中的作用。在“消息傳遞”過(guò)程中境肾,objc_msgSend的動(dòng)作比較清晰:首先在 Class 中的緩存查找 IMP (沒(méi)緩存則初始化緩存)剔难,如果沒(méi)找到,則向父類(lèi)的 Class 查找奥喻。如果一直查找到根類(lèi)仍舊沒(méi)有實(shí)現(xiàn)偶宫,則用_objc_msgForward函數(shù)指針代替 IMP 。最后环鲤,執(zhí)行這個(gè) IMP 纯趋。

為了展示消息轉(zhuǎn)發(fā)的具體動(dòng)作,這里嘗試向一個(gè)對(duì)象發(fā)送一條錯(cuò)誤的消息,并查看一下_objc_msgForward是如何進(jìn)行轉(zhuǎn)發(fā)的吵冒。

首先開(kāi)啟調(diào)試模式纯命、打印出所有運(yùn)行時(shí)發(fā)送的消息:
可以在代碼里執(zhí)行下面的方法:

(void)instrumentObjcMessageSends(YES);

或者斷點(diǎn)暫停程序運(yùn)行,并在 gdb 中輸入下面的命令:

call (void)instrumentObjcMessageSends(YES)

以第二種為例痹栖,操作如下所示:

enter image description here

之后亿汞,運(yùn)行時(shí)發(fā)送的所有消息都會(huì)打印到/tmp/msgSend-xxxx文件里了。

終端中輸入命令前往:

open /private/tmp
enter image description here

可能看到有多條揪阿,找到最新生成的疗我,雙擊打開(kāi)

在模擬器上執(zhí)行執(zhí)行以下語(yǔ)句(這一套調(diào)試方案僅適用于模擬器,真機(jī)不可用南捂,關(guān)于該調(diào)試方案的拓展鏈接: Can the messages sent to an object in Objective-C be monitored or printed out? )吴裤,向一個(gè)對(duì)象發(fā)送一條錯(cuò)誤的消息:

//
//  main.m
//  CYLObjcMsgForwardTest
//
//  Created by http://weibo.com/luohanchenyilong/.
//  Copyright (c) 2015年 微博@iOS程序犭袁. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "CYLTest.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        CYLTest *test = [[CYLTest alloc] init];
        [test performSelector:(@selector(iOS程序犭袁))];
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

enter image description here

你可以在/tmp/msgSend-xxxx(我這一次是/tmp/msgSend-9805)文件里,看到打印出來(lái):

enter image description here
+ CYLTest NSObject initialize
+ CYLTest NSObject alloc
- CYLTest NSObject init
- CYLTest NSObject performSelector:
+ CYLTest NSObject resolveInstanceMethod:
+ CYLTest NSObject resolveInstanceMethod:
- CYLTest NSObject forwardingTargetForSelector:
- CYLTest NSObject forwardingTargetForSelector:
- CYLTest NSObject methodSignatureForSelector:
- CYLTest NSObject methodSignatureForSelector:
- CYLTest NSObject class
- CYLTest NSObject doesNotRecognizeSelector:
- CYLTest NSObject doesNotRecognizeSelector:
- CYLTest NSObject class

結(jié)合《NSObject官方文檔》溺健,排除掉 NSObject 做的事麦牺,剩下的就是_objc_msgForward消息轉(zhuǎn)發(fā)做的幾件事:

  1. 調(diào)用resolveInstanceMethod:方法 (或 resolveClassMethod:)。允許用戶(hù)在此時(shí)為該 Class 動(dòng)態(tài)添加實(shí)現(xiàn)鞭缭。如果有實(shí)現(xiàn)了枕面,則調(diào)用并返回YES,那么重新開(kāi)始objc_msgSend流程缚去。這一次對(duì)象會(huì)響應(yīng)這個(gè)選擇器,一般是因?yàn)樗呀?jīng)調(diào)用過(guò)class_addMethod琼开。如果仍沒(méi)實(shí)現(xiàn)易结,繼續(xù)下面的動(dòng)作。

  2. 調(diào)用forwardingTargetForSelector:方法柜候,嘗試找到一個(gè)能響應(yīng)該消息的對(duì)象搞动。如果獲取到,則直接把消息轉(zhuǎn)發(fā)給它渣刷,返回非 nil 對(duì)象鹦肿。否則返回 nil ,繼續(xù)下面的動(dòng)作辅柴。注意箩溃,這里不要返回 self ,否則會(huì)形成死循環(huán)碌嘀。

  3. 調(diào)用methodSignatureForSelector:方法涣旨,嘗試獲得一個(gè)方法簽名。如果獲取不到股冗,則直接調(diào)用doesNotRecognizeSelector拋出異常霹陡。如果能獲取,則返回非nil:創(chuàng)建一個(gè) NSlnvocation 并傳給forwardInvocation:

  4. 調(diào)用forwardInvocation:方法烹棉,將第3步獲取到的方法簽名包裝成 Invocation 傳入攒霹,如何處理就在這里面了,并返回非ni浆洗。

  5. 調(diào)用doesNotRecognizeSelector: 催束,默認(rèn)的實(shí)現(xiàn)是拋出異常。如果第3步?jīng)]能獲得一個(gè)方法簽名辅髓,執(zhí)行該步驟泣崩。

上面前4個(gè)方法均是模板方法,開(kāi)發(fā)者可以override洛口,由 runtime 來(lái)調(diào)用矫付。最常見(jiàn)的實(shí)現(xiàn)消息轉(zhuǎn)發(fā):就是重寫(xiě)方法3和4,吞掉一個(gè)消息或者代理給其他對(duì)象都是沒(méi)問(wèn)題的

也就是說(shuō)_objc_msgForward在進(jìn)行消息轉(zhuǎn)發(fā)的過(guò)程中會(huì)涉及以下這幾個(gè)方法:

  1. resolveInstanceMethod:方法 (或 resolveClassMethod:)第焰。

  2. forwardingTargetForSelector:方法

  3. methodSignatureForSelector:方法

  4. forwardInvocation:方法

  5. doesNotRecognizeSelector: 方法

為了能更清晰地理解這些方法的作用买优,git倉(cāng)庫(kù)里也給出了一個(gè)Demo,名稱(chēng)叫“ _objc_msgForward_demo ”,可運(yùn)行起來(lái)看看挺举。

下面回答下第二個(gè)問(wèn)題“直接_objc_msgForward調(diào)用它將會(huì)發(fā)生什么杀赢?”

直接調(diào)用_objc_msgForward是非常危險(xiǎn)的事,如果用不好會(huì)直接導(dǎo)致程序Crash湘纵,但是如果用得好脂崔,能做很多非常酷的事梧喷。

就好像跑酷砌左,干得好,叫“似痰校酷”汇歹,干不好就叫“作死”。

正如前文所說(shuō):

_objc_msgForward是 IMP 類(lèi)型偿凭,用于消息轉(zhuǎn)發(fā)的:當(dāng)向一個(gè)對(duì)象發(fā)送一條消息产弹,但它并沒(méi)有實(shí)現(xiàn)的時(shí)候,_objc_msgForward會(huì)嘗試做消息轉(zhuǎn)發(fā)弯囊。

如何調(diào)用_objc_msgForward痰哨?
_objc_msgForward隸屬 C 語(yǔ)言,有三個(gè)參數(shù) :

-- _objc_msgForward參數(shù) 類(lèi)型
1. 所屬對(duì)象 id類(lèi)型
2. 方法名 SEL類(lèi)型
3. 可變參數(shù) 可變參數(shù)類(lèi)型

首先了解下如何調(diào)用 IMP 類(lèi)型的方法匾嘱,IMP類(lèi)型是如下格式:

為了直觀作谭,我們可以通過(guò)如下方式定義一個(gè) IMP類(lèi)型 :

typedef void (*voidIMP)(id, SEL, ...)

一旦調(diào)用_objc_msgForward,將跳過(guò)查找 IMP 的過(guò)程奄毡,直接觸發(fā)“消息轉(zhuǎn)發(fā)”折欠,

如果調(diào)用了_objc_msgForward,即使這個(gè)對(duì)象確實(shí)已經(jīng)實(shí)現(xiàn)了這個(gè)方法,你也會(huì)告訴objc_msgSend

“我沒(méi)有在這個(gè)對(duì)象里找到這個(gè)方法的實(shí)現(xiàn)”

想象下objc_msgSend會(huì)怎么做锐秦?通常情況下咪奖,下面這張圖就是你正常走objc_msgSend過(guò)程,和直接調(diào)用_objc_msgForward的前后差別:

enter image description here

有哪些場(chǎng)景需要直接調(diào)用_objc_msgForward酱床?最常見(jiàn)的場(chǎng)景是:你想獲取某方法所對(duì)應(yīng)的NSInvocation對(duì)象羊赵。舉例說(shuō)明:

JSPatch (Github 鏈接)就是直接調(diào)用_objc_msgForward來(lái)實(shí)現(xiàn)其核心功能的:

JSPatch 以小巧的體積做到了讓JS調(diào)用/替換任意OC方法,讓iOS APP具備熱更新的能力扇谣。

作者的博文《JSPatch實(shí)現(xiàn)原理詳解》詳細(xì)記錄了實(shí)現(xiàn)原理昧捷,有興趣可以看下。

26. runtime如何實(shí)現(xiàn)weak變量的自動(dòng)置nil罐寨?

runtime 對(duì)注冊(cè)的類(lèi)靡挥, 會(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拧簸。

上篇中的《runtime 如何實(shí)現(xiàn) weak 屬性》有論述。(注:在上篇的《使用runtime Associate方法關(guān)聯(lián)的對(duì)象男窟,需要在主對(duì)象dealloc的時(shí)候釋放么盆赤?》里給出的“對(duì)象的內(nèi)存銷(xiāo)毀時(shí)間表”也提到__weak引用的解除時(shí)間。)

我們可以設(shè)計(jì)一個(gè)函數(shù)(偽代碼)來(lái)表示上述機(jī)制:

objc_storeWeak(&a, b)函數(shù):

objc_storeWeak函數(shù)把第二個(gè)參數(shù)--賦值對(duì)象(b)的內(nèi)存地址作為鍵值key蝎宇,將第一個(gè)參數(shù)--weak修飾的屬性變量(a)的內(nèi)存地址(&a)作為value,注冊(cè)到 weak 表中祷安。如果第二個(gè)參數(shù)(b)為0(nil)姥芥,那么把變量(a)的內(nèi)存地址(&a)從weak表中刪除,

你可以把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ā)送消息是安全的簿训。

而如果a是由assign修飾的,則:
在b非nil時(shí),a和b指向同一個(gè)內(nèi)存地址强品,在b變nil時(shí)膘侮,a還是指向該內(nèi)存地址,變野指針的榛。此時(shí)向a發(fā)送消息極易崩潰琼了。

下面我們將基于objc_storeWeak(&a, b)函數(shù),使用偽代碼模擬“runtime如何實(shí)現(xiàn)weak屬性”:

// 使用偽代碼模擬:runtime如何實(shí)現(xiàn)weak屬性
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong

 id obj1;
 objc_initWeak(&obj1, obj);
/*obj引用計(jì)數(shù)變?yōu)?夫晌,變量作用域結(jié)束*/
 objc_destroyWeak(&obj1);

下面對(duì)用到的兩個(gè)方法objc_initWeakobjc_destroyWeak做下解釋?zhuān)?/p>

總體說(shuō)來(lái)雕薪,作用是:
通過(guò)objc_initWeak函數(shù)初始化“附有weak修飾符的變量(obj1)”,在變量作用域結(jié)束時(shí)通過(guò)objc_destoryWeak函數(shù)釋放該變量(obj1)晓淀。

下面分別介紹下方法的內(nèi)部實(shí)現(xiàn):

objc_initWeak函數(shù)的實(shí)現(xiàn)是這樣的:在將“附有weak修飾符的變量(obj1)”初始化為0(nil)后所袁,會(huì)將“賦值對(duì)象”(obj)作為參數(shù),調(diào)用objc_storeWeak函數(shù)要糊。

obj1 = 0纲熏;
obj_storeWeak(&obj1, obj);

也就是說(shuō):

weak 修飾的指針默認(rèn)值是 nil (在Objective-C中向nil發(fā)送消息是安全的)

然后obj_destroyWeak函數(shù)將0(nil)作為參數(shù),調(diào)用objc_storeWeak函數(shù)锄俄。

objc_storeWeak(&obj1, 0);

前面的源代碼與下列源代碼相同局劲。

// 使用偽代碼模擬:runtime如何實(shí)現(xiàn)weak屬性
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong

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表中刪除毅戈。

27. 能否向編譯后得到的類(lèi)中增加實(shí)例變量苹丸?能否向運(yùn)行時(shí)創(chuàng)建的類(lèi)中添加實(shí)例變量?為什么苇经?

  • 不能向編譯后得到的類(lèi)中增加實(shí)例變量赘理;
  • 能向運(yùn)行時(shí)創(chuàng)建的類(lèi)中添加實(shí)例變量;

解釋下:

  • 因?yàn)榫幾g后的類(lèi)已經(jīng)注冊(cè)在 runtime 中扇单,類(lèi)結(jié)構(gòu)體中的 objc_ivar_list 實(shí)例變量的鏈表 和 instance_size 實(shí)例變量的內(nèi)存大小已經(jīng)確定商模,同時(shí)runtime 會(huì)調(diào)用 class_setIvarLayoutclass_setWeakIvarLayout 來(lái)處理 strong weak 引用。所以不能向存在的類(lèi)中添加實(shí)例變量蜘澜;

  • 運(yùn)行時(shí)創(chuàng)建的類(lèi)是可以添加實(shí)例變量施流,調(diào)用 class_addIvar 函數(shù)。但是得在調(diào)用 objc_allocateClassPair 之后鄙信,objc_registerClassPair 之前瞪醋,原因同上。

28. runloop和線程有什么關(guān)系装诡?

總的說(shuō)來(lái)银受,Run loop践盼,正如其名,loop表示某種循環(huán)蚓土,和run放在一起就表示一直在運(yùn)行著的循環(huán)宏侍。實(shí)際上,run loop和線程是緊密相連的蜀漆,可以這樣說(shuō)run loop是為了線程而生谅河,沒(méi)有線程,它就沒(méi)有存在的必要确丢。Run loops是線程的基礎(chǔ)架構(gòu)部分绷耍, Cocoa 和 CoreFundation 都提供了 run loop 對(duì)象方便配置和管理線程的 run loop (以下都以 Cocoa 為例)。每個(gè)線程鲜侥,包括程序的主線程( main thread )都有與之相應(yīng)的 run loop 對(duì)象褂始。

runloop 和線程的關(guān)系:

  1. 主線程的run loop默認(rèn)是啟動(dòng)的。

iOS的應(yīng)用程序里面描函,程序啟動(dòng)后會(huì)有一個(gè)如下的main()函數(shù)

int main(int argc, char * argv[]) {
   @autoreleasepool {
       return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
   }
}

重點(diǎn)是UIApplicationMain()函數(shù)崎苗,這個(gè)方法會(huì)為main thread設(shè)置一個(gè)NSRunLoop對(duì)象,這就解釋了:為什么我們的應(yīng)用可以在無(wú)人操作的時(shí)候休息舀寓,需要讓它干活的時(shí)候又能立馬響應(yīng)胆数。

  1. 對(duì)其它線程來(lái)說(shuō),run loop默認(rèn)是沒(méi)有啟動(dòng)的互墓,如果你需要更多的線程交互則可以手動(dòng)配置和啟動(dòng)必尼,如果線程只是去執(zhí)行一個(gè)長(zhǎng)時(shí)間的已確定的任務(wù)則不需要。

  2. 在任何一個(gè) Cocoa 程序的線程中篡撵,都可以通過(guò)以下代碼來(lái)獲取到當(dāng)前線程的 run loop 判莉。

NSRunLoop *runloop = [NSRunLoop currentRunLoop];

參考鏈接:《Objective-C之run loop詳解》

29. runloop的mode作用是什么育谬?

model 主要是用來(lái)指定事件在運(yùn)行循環(huán)中的優(yōu)先級(jí)的券盅,分為:

  • NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默認(rèn),空閑狀態(tài)
  • UITrackingRunLoopMode:ScrollView滑動(dòng)時(shí)
  • UIInitializationRunLoopMode:?jiǎn)?dòng)時(shí)
  • NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合

蘋(píng)果公開(kāi)提供的 Mode 有兩個(gè):

  1. NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
  2. NSRunLoopCommonModes(kCFRunLoopCommonModes)

30. 以+ scheduledTimerWithTimeInterval...的方式觸發(fā)的timer膛檀,在滑動(dòng)頁(yè)面上的列表時(shí)锰镀,timer會(huì)暫定回調(diào),為什么宿刮?如何解決互站?

RunLoop只能運(yùn)行在一種mode下私蕾,如果要換mode僵缺,當(dāng)前的loop也需要停下重啟成新的。利用這個(gè)機(jī)制踩叭,ScrollView滾動(dòng)過(guò)程中NSDefaultRunLoopMode(kCFRunLoopDefaultMode)的mode會(huì)切換到UITrackingRunLoopMode來(lái)保證ScrollView的流暢滑動(dòng):只能在NSDefaultRunLoopMode模式下處理的事件會(huì)影響ScrollView的滑動(dòng)磕潮。

如果我們把一個(gè)NSTimer對(duì)象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主運(yùn)行循環(huán)中的時(shí)候,
ScrollView滾動(dòng)過(guò)程中會(huì)因?yàn)閙ode的切換翠胰,而導(dǎo)致NSTimer將不再被調(diào)度。

同時(shí)因?yàn)閙ode還是可定制的自脯,所以:

Timer計(jì)時(shí)會(huì)被scrollView的滑動(dòng)影響的問(wèn)題可以通過(guò)將timer添加到NSRunLoopCommonModes(kCFRunLoopCommonModes)來(lái)解決之景。代碼如下:

// 
// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)
// https://github.com/ChenYilong

//將timer添加到NSDefaultRunLoopMode中
[NSTimer scheduledTimerWithTimeInterval:1.0
     target:self
     selector:@selector(timerTick:)
     userInfo:nil
     repeats:YES];
//然后再添加到NSRunLoopCommonModes里
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0
     target:self
     selector:@selector(timerTick:)
     userInfo:nil
     repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

31. 猜想runloop內(nèi)部是如何實(shí)現(xiàn)的?

一般來(lái)講膏潮,一個(gè)線程一次只能執(zhí)行一個(gè)任務(wù)锻狗,執(zhí)行完成后線程就會(huì)退出。如果我們需要一個(gè)機(jī)制焕参,讓線程能隨時(shí)處理事件但并不退出轻纪,通常的代碼邏輯
是這樣的:

function loop() {
    initialize();
    do {
        var message = get_next_message();
        process_message(message);
    } while (message != quit);
}

或使用偽代碼來(lái)展示下:

// 
// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)
// https://github.com/ChenYilong
int main(int argc, char * argv[]) {
 //程序一直運(yùn)行狀態(tài)
 while (AppIsRunning) {
      //睡眠狀態(tài),等待喚醒事件
      id whoWakesMe = SleepForWakingUp();
      //得到喚醒事件
      id event = GetEvent(whoWakesMe);
      //開(kāi)始處理事件
      HandleEvent(event);
 }
 return 0;
}

參考鏈接:

  1. 《深入理解RunLoop》
  2. 摘自博文CFRunLoop叠纷,原作者是微博@我就叫Sunny怎么了

32. objc使用什么機(jī)制管理對(duì)象內(nèi)存刻帚?

通過(guò) retainCount 的機(jī)制來(lái)決定對(duì)象是否需要釋放。
每次 runloop 的時(shí)候涩嚣,都會(huì)檢查對(duì)象的 retainCount崇众,如果retainCount 為 0,說(shuō)明該對(duì)象沒(méi)有地方需要繼續(xù)使用了航厚,可以釋放掉了顷歌。

33. ARC通過(guò)什么方式幫助開(kāi)發(fā)者管理內(nèi)存?

<p><del>編譯時(shí)根據(jù)代碼上下文阶淘,插入 retain/release
</del></p>
ARC相對(duì)于MRC衙吩,不是在編譯時(shí)添加retain/release/autorelease這么簡(jiǎn)單。應(yīng)該是編譯期和運(yùn)行期兩部分共同幫助開(kāi)發(fā)者管理內(nèi)存溪窒。

在編譯期坤塞,ARC用的是更底層的C接口實(shí)現(xiàn)的retain/release/autorelease,這樣做性能更好澈蚌,也是為什么不能在ARC環(huán)境下手動(dòng)retain/release/autorelease摹芙,同時(shí)對(duì)同一上下文的同一對(duì)象的成對(duì)retain/release操作進(jìn)行優(yōu)化(即忽略掉不必要的操作);ARC也包含運(yùn)行期組件宛瞄,這個(gè)地方做的優(yōu)化比較復(fù)雜浮禾,但也不能被忽略》莺梗【TODO:后續(xù)更新會(huì)詳細(xì)描述下】

34. 不手動(dòng)指定autoreleasepool的前提下盈电,一個(gè)autorealese對(duì)象在什么時(shí)刻釋放?(比如在一個(gè)vc的viewDidLoad中創(chuàng)建)

分兩種情況:手動(dòng)干預(yù)釋放時(shí)機(jī)杯活、系統(tǒng)自動(dòng)去釋放匆帚。

  1. 手動(dòng)干預(yù)釋放時(shí)機(jī)--指定autoreleasepool
    就是所謂的:當(dāng)前作用域大括號(hào)結(jié)束時(shí)釋放。
  2. 系統(tǒng)自動(dòng)去釋放--不手動(dòng)指定autoreleasepool

Autorelease對(duì)象出了作用域之后旁钧,會(huì)被添加到最近一次創(chuàng)建的自動(dòng)釋放池中吸重,并會(huì)在當(dāng)前的 runloop 迭代結(jié)束時(shí)釋放互拾。

釋放的時(shí)機(jī)總結(jié)起來(lái),可以用下圖來(lái)表示:

autoreleasepool與 runloop 的關(guān)系圖

下面對(duì)這張圖進(jìn)行詳細(xì)的解釋?zhuān)?/p>

從程序啟動(dòng)到加載完成是一個(gè)完整的運(yùn)行循環(huán)嚎幸,然后會(huì)停下來(lái)颜矿,等待用戶(hù)交互,用戶(hù)的每一次交互都會(huì)啟動(dòng)一次運(yùn)行循環(huán)嫉晶,來(lái)處理用戶(hù)所有的點(diǎn)擊事件骑疆、觸摸事件。

我們都是知道:
所有 autorelease 的對(duì)象替废,在出了作用域之后封断,會(huì)被自動(dòng)添加到最近創(chuàng)建的自動(dòng)釋放池中。

但是如果每次都放進(jìn)應(yīng)用程序的 main.m 中的 autoreleasepool 中舶担,遲早有被撐滿(mǎn)的一刻坡疼。這個(gè)過(guò)程中必定有一個(gè)釋放的動(dòng)作。何時(shí)衣陶?

在一次完整的運(yùn)行循環(huán)結(jié)束之前柄瑰,會(huì)被銷(xiāo)毀。

那什么時(shí)間會(huì)創(chuàng)建自動(dòng)釋放池剪况?運(yùn)行循環(huán)檢測(cè)到事件并啟動(dòng)后教沾,就會(huì)創(chuàng)建自動(dòng)釋放池。

子線程的 runloop 默認(rèn)是不工作译断,無(wú)法主動(dòng)創(chuàng)建授翻,必須手動(dòng)創(chuàng)建。

自定義的 NSOperation 和 NSThread 需要手動(dòng)創(chuàng)建自動(dòng)釋放池孙咪。比如: 自定義的 NSOperation 類(lèi)中的 main 方法里就必須添加自動(dòng)釋放池堪唐。否則出了作用域后,自動(dòng)釋放對(duì)象會(huì)因?yàn)闆](méi)有自動(dòng)釋放池去處理它翎蹈,而造成內(nèi)存泄露淮菠。

但對(duì)于 blockOperation 和 invocationOperation 這種默認(rèn)的Operation ,系統(tǒng)已經(jīng)幫我們封裝好了荤堪,不需要手動(dòng)創(chuàng)建自動(dòng)釋放池合陵。

@autoreleasepool 當(dāng)自動(dòng)釋放池被銷(xiāo)毀或者耗盡時(shí),會(huì)向自動(dòng)釋放池中的所有對(duì)象發(fā)送 release 消息澄阳,釋放自動(dòng)釋放池中的所有對(duì)象拥知。

如果在一個(gè)vc的viewDidLoad中創(chuàng)建一個(gè) Autorelease對(duì)象,那么該對(duì)象會(huì)在 viewDidAppear 方法執(zhí)行前就被銷(xiāo)毀了碎赢。

參考鏈接:《黑幕背后的Autorelease》

35. BAD_ACCESS在什么情況下出現(xiàn)低剔?

訪問(wèn)了野指針,比如對(duì)一個(gè)已經(jīng)釋放的對(duì)象執(zhí)行了release揩抡、訪問(wèn)已經(jīng)釋放對(duì)象的成員變量或者發(fā)消息户侥。
死循環(huán)

36. 蘋(píng)果是如何實(shí)現(xiàn)autoreleasepool的?

autoreleasepool 以一個(gè)隊(duì)列數(shù)組的形式實(shí)現(xiàn),主要通過(guò)下列三個(gè)函數(shù)完成.

  1. objc_autoreleasepoolPush
  2. objc_autoreleasepoolPop
  3. objc_autorelease

看函數(shù)名就可以知道峦嗤,對(duì) autorelease 分別執(zhí)行 push蕊唐,和 pop 操作。銷(xiāo)毀對(duì)象時(shí)執(zhí)行release操作烁设。

舉例說(shuō)明:我們都知道用類(lèi)方法創(chuàng)建的對(duì)象都是 Autorelease 的替梨,那么一旦 Person 出了作用域,當(dāng)在 Person 的 dealloc 方法中打上斷點(diǎn)装黑,我們就可以看到這樣的調(diào)用堆棧信息:

enter image description here

37. 使用block時(shí)什么情況會(huì)發(fā)生引用循環(huán)副瀑,如何解決?

一個(gè)對(duì)象中強(qiáng)引用了block恋谭,在block中又使用了該對(duì)象糠睡,就會(huì)發(fā)射循環(huán)引用。
解決方法是將該對(duì)象使用__weak或者_(dá)_block修飾符修飾之后再在block中使用疚颊。

  1. id weak weakSelf = self;
    或者 weak __typeof(&*self)weakSelf = self該方法可以設(shè)置宏
  2. id __block weakSelf = self;

38. 在block內(nèi)如何修改block外部變量狈孔?

默認(rèn)情況下,在block中訪問(wèn)的外部變量是復(fù)制過(guò)去的材义,即:寫(xiě)操作不對(duì)原變量生效均抽。但是你可以加上__block來(lái)讓其寫(xiě)操作生效,示例代碼如下:

__block int a = 0;
void  (^foo)(void) = ^{ 
    a = 1; 
}
f00(); 
//這里其掂,a的值被修改為1

參考鏈接:微博@唐巧_boy的著作《iOS開(kāi)發(fā)進(jìn)階》中的第11.2.3章節(jié)

39. 使用系統(tǒng)的某些block api(如UIView的block版本寫(xiě)動(dòng)畫(huà)時(shí))油挥,是否也考慮引用循環(huán)問(wèn)題?

系統(tǒng)的某些block api中款熬,UIView的block版本寫(xiě)動(dòng)畫(huà)時(shí)不需要考慮深寥,但也有一些api 需要考慮:

所謂“引用循環(huán)”是指雙向的強(qiáng)引用,所以那些“單向的強(qiáng)引用”(block 強(qiáng)引用 self )沒(méi)有問(wè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; }]; 

這些情況不需要考慮“引用循環(huán)”翩迈。

但如果你使用一些參數(shù)中可能含有 ivar 的系統(tǒng) api ,如 GCD 盔夜、NSNotificationCenter就要小心一點(diǎn):比如GCD 內(nèi)部如果引用了 self负饲,而且 GCD 的其他參數(shù)是 ivar,則要考慮到循環(huán)引用:

__weak __typeof__(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doSomethingElse];
} );

類(lèi)似的:

 __weak __typeof__(self) weakSelf = self;
 _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
                                                               object:nil
                                                                queue:nil
                                                           usingBlock:^(NSNotification *note) {
     __typeof__(self) strongSelf = weakSelf;
     [strongSelf dismissModalViewControllerAnimated:YES];
 }];

self --> _observer --> block --> self 顯然這也是一個(gè)循環(huán)引用喂链。

40. GCD的隊(duì)列(dispatch_queue_t)分哪兩種類(lèi)型返十?

  1. 串行隊(duì)列Serial Dispatch Queue
  2. 并行隊(duì)列Concurrent Dispatch Queue

41. 如何用GCD同步若干個(gè)異步調(diào)用遭垛?(如根據(jù)若干個(gè)url異步加載多張圖片涣雕,然后在都下載完成后合成一張整圖)

使用Dispatch Group追加block到Global Group Queue,這些block如果全部執(zhí)行完畢涩惑,就會(huì)執(zhí)行Main Dispatch Queue中的結(jié)束處理的block屁倔。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*加載圖片1 */ });
dispatch_group_async(group, queue, ^{ /*加載圖片2 */ });
dispatch_group_async(group, queue, ^{ /*加載圖片3 */ }); 
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 合并圖片
});

42. dispatch_barrier_async的作用是什么疆拘?

在并行隊(duì)列中,為了保持某些任務(wù)的順序狭园,需要等待一些任務(wù)完成后才能繼續(xù)進(jìn)行缀蹄,使用 barrier 來(lái)等待之前任務(wù)完成,避免數(shù)據(jù)競(jìng)爭(zhēng)等問(wèn)題排拷。
dispatch_barrier_async 函數(shù)會(huì)等待追加到Concurrent Dispatch Queue并行隊(duì)列中的操作全部執(zhí)行完之后侧漓,然后再執(zhí)行 dispatch_barrier_async 函數(shù)追加的處理,等 dispatch_barrier_async 追加的處理執(zhí)行結(jié)束之后监氢,Concurrent Dispatch Queue才恢復(fù)之前的動(dòng)作繼續(xù)執(zhí)行布蔗。

打個(gè)比方:比如你們公司周末跟團(tuán)旅游,高速休息站上浪腐,司機(jī)說(shuō):大家都去上廁所纵揍,速戰(zhàn)速?zèng)Q,上完廁所就上高速议街。超大的公共廁所泽谨,大家同時(shí)去,程序猿很快就結(jié)束了特漩,但程序媛就可能會(huì)慢一些隔盛,即使你第一個(gè)回來(lái),司機(jī)也不會(huì)出發(fā)拾稳,司機(jī)要等待所有人都回來(lái)后吮炕,才能出發(fā)。 dispatch_barrier_async 函數(shù)追加的內(nèi)容就如同 “上完廁所就上高速”這個(gè)動(dòng)作访得。

(注意:使用 dispatch_barrier_async 龙亲,該函數(shù)只能搭配自定義并行隊(duì)列 dispatch_queue_t 使用。不能使用: dispatch_get_global_queue 悍抑,否則 dispatch_barrier_async 的作用會(huì)和 dispatch_async 的作用一模一樣鳄炉。 )

43. 蘋(píng)果為什么要廢棄dispatch_get_current_queue

dispatch_get_current_queue容易造成死鎖

44. 以下代碼運(yùn)行結(jié)果如何搜骡?

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    NSLog(@"3");
}

只輸出:1 拂盯。發(fā)生主線程鎖死。

45. addObserver:forKeyPath:options:context:各個(gè)參數(shù)的作用分別是什么记靡,observer中需要實(shí)現(xiàn)哪個(gè)方法才能獲得KVO回調(diào)谈竿?

// 添加鍵值觀察
/*
1 觀察者,負(fù)責(zé)處理監(jiān)聽(tīng)事件的對(duì)象
2 觀察的屬性
3 觀察的選項(xiàng)
4 上下文
*/
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"Person Name"];

observer中需要實(shí)現(xiàn)一下方法:

// 所有的 kvo 監(jiān)聽(tīng)到事件摸吠,都會(huì)調(diào)用此方法
/*
 1. 觀察的屬性
 2. 觀察的對(duì)象
 3. change 屬性變化字典(新/舊)
 4. 上下文空凸,與監(jiān)聽(tīng)的時(shí)候傳遞的一致
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;

46. 如何手動(dòng)觸發(fā)一個(gè)value的KVO

所謂的“手動(dòng)觸發(fā)”是區(qū)別于“自動(dòng)觸發(fā)”:

自動(dòng)觸發(fā)是指類(lèi)似這種場(chǎng)景:在注冊(cè) KVO 之前設(shè)置一個(gè)初始值,注冊(cè)之后寸痢,設(shè)置一個(gè)不一樣的值呀洲,就可以觸發(fā)了。

想知道如何手動(dòng)觸發(fā),必須知道自動(dòng)觸發(fā) KVO 的原理:

鍵值觀察通知依賴(lài)于 NSObject 的兩個(gè)方法: willChangeValueForKey:didChangevlueForKey: 道逗。在一個(gè)被觀察屬性發(fā)生改變之前兵罢, willChangeValueForKey: 一定會(huì)被調(diào)用,這就
會(huì)記錄舊的值滓窍。而當(dāng)改變發(fā)生后卖词, didChangeValueForKey: 會(huì)被調(diào)用,繼而 observeValueForKey:ofObject:change:context: 也會(huì)被調(diào)用贰您。如果可以手動(dòng)實(shí)現(xiàn)這些調(diào)用,就可以實(shí)現(xiàn)“手動(dòng)觸發(fā)”了拢操。

那么“手動(dòng)觸發(fā)”的使用場(chǎng)景是什么锦亦?一般我們只在希望能控制“回調(diào)的調(diào)用時(shí)機(jī)”時(shí)才會(huì)這么做。

具體做法如下:

如果這個(gè) value 是 表示時(shí)間的 self.now 令境,那么代碼如下:最后兩行代碼缺一不可杠园。

//  .m文件
//  Created by https://github.com/ChenYilong
//  微博@iOS程序犭袁(http://weibo.com/luohanchenyilong/).
//  手動(dòng)觸發(fā) value 的KVO,最后兩行代碼缺一不可舔庶。

//@property (nonatomic, strong) NSDate *now;
- (void)viewDidLoad
{
   [super viewDidLoad];
   [self willChangeValueForKey:@"now"]; // “手動(dòng)觸發(fā)self.now的KVO”抛蚁,必寫(xiě)。
   [self didChangeValueForKey:@"now"]; // “手動(dòng)觸發(fā)self.now的KVO”惕橙,必寫(xiě)瞧甩。
}

但是平時(shí)我們一般不會(huì)這么干,我們都是等系統(tǒng)去“自動(dòng)觸發(fā)”弥鹦《且荩“自動(dòng)觸發(fā)”的實(shí)現(xiàn)原理:

比如調(diào)用 setNow: 時(shí),系統(tǒng)還會(huì)以某種方式在中間插入 wilChangeValueForKey: 彬坏、 didChangeValueForKey:observeValueForKeyPath:ofObject:change:context: 的調(diào)用朦促。

大家可能以為這是因?yàn)?setNow: 是合成方法,有時(shí)候我們也能看到人們這么寫(xiě)代碼:

- (void)setNow:(NSDate *)aDate {
   [self willChangeValueForKey:@"now"]; // 沒(méi)有必要
   _now = aDate;
   [self didChangeValueForKey:@"now"];// 沒(méi)有必要
}

這是完全沒(méi)有必要的代碼栓始,不要這么做务冕,這樣的話,KVO代碼會(huì)被調(diào)用兩次幻赚。KVO在調(diào)用存取方法之前總是調(diào)用 willChangeValueForKey: 禀忆,之后總是調(diào)用 didChangeValueForkey: 。怎么做到的呢?答案是通過(guò) isa 混寫(xiě)(isa-swizzling)落恼。下文《apple用什么方式實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO油湖?》會(huì)有詳述。

參考鏈接: Manual Change Notification---Apple 官方文檔

47. 若一個(gè)類(lèi)有實(shí)例變量 NSString *_foo 领跛,調(diào)用setValue:forKey:時(shí)乏德,可以以foo還是 _foo 作為key?

都可以。

48. KVC的keyPath中的集合運(yùn)算符如何使用喊括?

  1. 必須用在集合對(duì)象上或普通對(duì)象的集合屬性上
  2. 簡(jiǎn)單集合運(yùn)算符有@avg胧瓜, @count , @max 郑什, @min 府喳,@sum,
  3. 格式 @"@sum.age"或 @"集合屬性.@max.age"

49. KVC和KVO的keyPath一定是屬性么蘑拯?

KVO支持實(shí)例變量

50. 如何關(guān)閉默認(rèn)的KVO的默認(rèn)實(shí)現(xiàn)钝满,并進(jìn)入自定義的KVO實(shí)現(xiàn)?

請(qǐng)參考:《如何自己動(dòng)手實(shí)現(xiàn) KVO》

51. apple用什么方式實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO申窘?

Apple 的文檔對(duì) KVO 實(shí)現(xiàn)的描述:

Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ...

Apple 的文檔可以看出:Apple 并不希望過(guò)多暴露 KVO 的實(shí)現(xiàn)細(xì)節(jié)弯蚜。不過(guò),要是借助 runtime 提供的方法去深入挖掘剃法,所有被掩蓋的細(xì)節(jié)都會(huì)原形畢露:

當(dāng)你觀察一個(gè)對(duì)象時(shí)碎捺,一個(gè)新的類(lèi)會(huì)被動(dòng)態(tài)創(chuàng)建。這個(gè)類(lèi)繼承自該對(duì)象的原本的類(lèi)贷洲,并重寫(xiě)了被觀察屬性的 setter 方法收厨。重寫(xiě)的 setter 方法會(huì)負(fù)責(zé)在調(diào)用原 setter 方法之前和之后,通知所有觀察對(duì)象:值的更改优构。最后通過(guò) isa 混寫(xiě)(isa-swizzling) 把這個(gè)對(duì)象的 isa 指針 ( isa 指針告訴 Runtime 系統(tǒng)這個(gè)對(duì)象的類(lèi)是什么 ) 指向這個(gè)新創(chuàng)建的子類(lèi)诵叁,對(duì)象就神奇的變成了新創(chuàng)建的子類(lèi)的實(shí)例。我畫(huà)了一張示意圖钦椭,如下所示:

enter image description here

KVO 確實(shí)有點(diǎn)黑魔法:

Apple 使用了 isa 混寫(xiě)(isa-swizzling)來(lái)實(shí)現(xiàn) KVO 黎休。

下面做下詳細(xì)解釋?zhuān)?/p>

鍵值觀察通知依賴(lài)于 NSObject 的兩個(gè)方法: willChangeValueForKey:didChangevlueForKey: 。在一個(gè)被觀察屬性發(fā)生改變之前玉凯, willChangeValueForKey: 一定會(huì)被調(diào)用势腮,這就會(huì)記錄舊的值。而當(dāng)改變發(fā)生后漫仆, didChangeValueForKey: 會(huì)被調(diào)用捎拯,繼而 observeValueForKey:ofObject:change:context: 也會(huì)被調(diào)用∶ぱ幔可以手動(dòng)實(shí)現(xiàn)這些調(diào)用署照,但很少有人這么做。一般我們只在希望能控制回調(diào)的調(diào)用時(shí)機(jī)時(shí)才會(huì)這么做吗浩。大部分情況下建芙,改變通知會(huì)自動(dòng)調(diào)用。

比如調(diào)用 setNow: 時(shí)懂扼,系統(tǒng)還會(huì)以某種方式在中間插入 wilChangeValueForKey: 禁荸、 didChangeValueForKey:observeValueForKeyPath:ofObject:change:context: 的調(diào)用右蒲。大家可能以為這是因?yàn)?setNow: 是合成方法,有時(shí)候我們也能看到人們這么寫(xiě)代碼:

- (void)setNow:(NSDate *)aDate {
   [self willChangeValueForKey:@"now"]; // 沒(méi)有必要
   _now = aDate;
   [self didChangeValueForKey:@"now"];// 沒(méi)有必要
}

這是完全沒(méi)有必要的代碼赶熟,不要這么做瑰妄,這樣的話,KVO代碼會(huì)被調(diào)用兩次映砖。KVO在調(diào)用存取方法之前總是調(diào)用 willChangeValueForKey: 间坐,之后總是調(diào)用 didChangeValueForkey: 。怎么做到的呢?答案是通過(guò) isa 混寫(xiě)(isa-swizzling)邑退。第一次對(duì)一個(gè)對(duì)象調(diào)用 addObserver:forKeyPath:options:context: 時(shí)竹宋,框架會(huì)創(chuàng)建這個(gè)類(lèi)的新的 KVO 子類(lèi),并將被觀察對(duì)象轉(zhuǎn)換為新子類(lèi)的對(duì)象地技。在這個(gè) KVO 特殊子類(lèi)中蜈七, Cocoa 創(chuàng)建觀察屬性的 setter ,大致工作原理如下:

- (void)setNow:(NSDate *)aDate {
   [self willChangeValueForKey:@"now"];
   [super setValue:aDate forKey:@"now"];
   [self didChangeValueForKey:@"now"];
}

這種繼承和方法注入是在運(yùn)行時(shí)而不是編譯時(shí)實(shí)現(xiàn)的乓土。這就是正確命名如此重要的原因宪潮。只有在使用KVC命名約定時(shí)溯警,KVO才能做到這一點(diǎn)趣苏。

KVO 在實(shí)現(xiàn)中通過(guò) isa 混寫(xiě)(isa-swizzling) 把這個(gè)對(duì)象的 isa 指針 ( isa 指針告訴 Runtime 系統(tǒng)這個(gè)對(duì)象的類(lèi)是什么 ) 指向這個(gè)新創(chuàng)建的子類(lèi),對(duì)象就神奇的變成了新創(chuàng)建的子類(lèi)的實(shí)例梯轻。這在Apple 的文檔可以得到印證:

Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ...

然而 KVO 在實(shí)現(xiàn)中使用了 isa 混寫(xiě)( isa-swizzling) 食磕,這個(gè)的確不是很容易發(fā)現(xiàn):Apple 還重寫(xiě)、覆蓋了 -class 方法并返回原來(lái)的類(lèi)喳挑。 企圖欺騙我們:這個(gè)類(lèi)沒(méi)有變彬伦,就是原本那個(gè)類(lèi)。伊诵。单绑。

但是,假設(shè)“被監(jiān)聽(tīng)的對(duì)象”的類(lèi)對(duì)象是 MYClass 曹宴,有時(shí)候我們能看到對(duì) NSKVONotifying_MYClass 的引用而不是對(duì) MYClass 的引用搂橙。借此我們得以知道 Apple 使用了 isa 混寫(xiě)(isa-swizzling)。具體探究過(guò)程可參考 這篇博文 笛坦。

52. IBOutlet連出來(lái)的視圖屬性為什么可以被設(shè)置成weak?

參考鏈接: Should IBOutlets be strong or weak under ARC?

文章告訴我們:

因?yàn)榧热挥型怄溎敲匆晥D在xib或者storyboard中肯定存在区转,視圖已經(jīng)對(duì)它有一個(gè)強(qiáng)引用了。

不過(guò)這個(gè)回答漏了個(gè)重要知識(shí)版扩,使用storyboard(xib不行)創(chuàng)建的vc废离,會(huì)有一個(gè)叫_topLevelObjectsToKeepAliveFromStoryboard的私有數(shù)組強(qiáng)引用所有top level的對(duì)象,所以這時(shí)即便outlet聲明成weak也沒(méi)關(guān)系

53. IB中User Defined Runtime Attributes如何使用礁芦?

它能夠通過(guò)KVC的方式配置一些你在interface builder 中不能配置的屬性蜻韭。當(dāng)你希望在IB中作盡可能多得事情,這個(gè)特性能夠幫助你編寫(xiě)更加輕量級(jí)的viewcontroller

54. 如何調(diào)試BAD_ACCESS錯(cuò)誤

  1. 重寫(xiě)object的respondsToSelector方法,現(xiàn)實(shí)出現(xiàn)EXEC_BAD_ACCESS前訪問(wèn)的最后一個(gè)object

  2. 通過(guò) Zombie


    enter image description here
  3. 設(shè)置全局?jǐn)帱c(diǎn)快速定位問(wèn)題代碼所在行

  4. Xcode 7 已經(jīng)集成了BAD_ACCESS捕獲功能:Address Sanitizer湘捎。
    用法如下:在配置中勾選?Enable Address Sanitizer

    enter image description here
    enter image description here

55. lldb(gdb)常用的調(diào)試命令诀豁?

  • breakpoint 設(shè)置斷點(diǎn)定位到某一個(gè)函數(shù)
  • n 斷點(diǎn)指針下一步
  • po打印對(duì)象

更多 lldb(gdb) 調(diào)試命令可查看

  1. The LLDB Debugger
  2. 蘋(píng)果官方文檔: iOS Debugging Magic 窥妇。

Posted by 微博@iOS程序犭袁
原創(chuàng)文章舷胜,版權(quán)聲明:自由轉(zhuǎn)載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市活翩,隨后出現(xiàn)的幾起案子烹骨,更是在濱河造成了極大的恐慌,老刑警劉巖材泄,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沮焕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡拉宗,警方通過(guò)查閱死者的電腦和手機(jī)峦树,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)旦事,“玉大人魁巩,你說(shuō)我怎么就攤上這事〗愀。” “怎么了谷遂?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)卖鲤。 經(jīng)常有香客問(wèn)我肾扰,道長(zhǎng),這世上最難降的妖魔是什么蛋逾? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任集晚,我火速辦了婚禮,結(jié)果婚禮上区匣,老公的妹妹穿的比我還像新娘偷拔。我一直安慰自己,他們只是感情好沉颂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布条摸。 她就那樣靜靜地躺著,像睡著了一般铸屉。 火紅的嫁衣襯著肌膚如雪钉蒲。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天彻坛,我揣著相機(jī)與錄音顷啼,去河邊找鬼踏枣。 笑死,一個(gè)胖子當(dāng)著我的面吹牛钙蒙,可吹牛的內(nèi)容都是我干的茵瀑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼躬厌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼马昨!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起扛施,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鸿捧,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后疙渣,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體匙奴,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年妄荔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泼菌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡啦租,死狀恐怖哗伯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情刷钢,我是刑警寧澤笋颤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布乳附,位于F島的核電站内地,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏赋除。R本人自食惡果不足惜阱缓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望举农。 院中可真熱鬧荆针,春花似錦、人聲如沸颁糟。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)棱貌。三九已至玖媚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間婚脱,已是汗流浹背今魔。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工勺像, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人错森。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓吟宦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親涩维。 傳聞我的和親對(duì)象是個(gè)殘疾皇子殃姓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容