一嚣潜、_objc_msgForward函數(shù)是做什么的口猜,直接調(diào)用它將會(huì)發(fā)生什么?
_objc_msgForward是一個(gè)函數(shù)指針(和 IMP 的類型一樣)鸵钝,是用于消息轉(zhuǎn)發(fā)的:當(dāng)向一個(gè)對(duì)象發(fā)送一條消息疏哗,但它并沒有實(shí)現(xiàn)的時(shí)候呛讲,_objc_msgForward會(huì)嘗試做消息轉(zhuǎn)發(fā)。
在“消息傳遞”過程中返奉,objc_msgSend的動(dòng)作比較清晰:首先在 Class 中的緩存查找 IMP (沒緩存則初始化緩存)贝搁,如果沒找到,則向父類的 Class 查找芽偏。如果一直查找到根類仍舊沒有實(shí)現(xiàn)雷逆,則用_objc_msgForward函數(shù)指針代替 IMP 。最后污尉,執(zhí)行這個(gè) IMP 膀哲。
_objc_msgForward消息轉(zhuǎn)發(fā)做的幾件事:
a.調(diào)用resolveInstanceMethod:方法 (或resolveClassMethod:)。允許用戶在此時(shí)為該 Class 動(dòng)態(tài)添加實(shí)現(xiàn)十厢。如果有實(shí)現(xiàn)了等太,則調(diào)用并返回YES捂齐,那么重新開始o(jì)bjc_msgSend流程蛮放。這一次對(duì)象會(huì)響應(yīng)這個(gè)選擇器,一般是因?yàn)樗呀?jīng)調(diào)用過class_addMethod奠宜。如果仍沒實(shí)現(xiàn)包颁,繼續(xù)下面的動(dòng)作瞻想。
b.調(diào)用forwardingTargetForSelector:方法,嘗試找到一個(gè)能響應(yīng)該消息的對(duì)象娩嚼。如果獲取到蘑险,則直接把消息轉(zhuǎn)發(fā)給它,返回非 nil 對(duì)象岳悟。否則返回 nil 佃迄,繼續(xù)下面的動(dòng)作。注意贵少,這里不要返回 self 呵俏,否則會(huì)形成死循環(huán)。
c.調(diào)用methodSignatureForSelector:方法滔灶,嘗試獲得一個(gè)方法簽名普碎。如果獲取不到,則直接調(diào)用doesNotRecognizeSelector拋出異常录平。如果能獲取麻车,則返回非nil:創(chuàng)建一個(gè) NSlnvocation 并傳給forwardInvocation:。
d.調(diào)用forwardInvocation:方法斗这,將第3步獲取到的方法簽名包裝成 Invocation 傳入动猬,如何處理就在這里面了,并返回非ni表箭。
e.調(diào)用doesNotRecognizeSelector:枣察,默認(rèn)的實(shí)現(xiàn)是拋出異常。如果第3步?jīng)]能獲得一個(gè)方法簽名燃逻,執(zhí)行該步驟序目。
上面前4個(gè)方法均是模板方法,開發(fā)者可以override伯襟,由 runtime 來調(diào)用猿涨。最常見的實(shí)現(xiàn)消息轉(zhuǎn)發(fā):就是重寫方法3和4,吞掉一個(gè)消息或者代理給其他對(duì)象都是沒問題的
如何調(diào)用_objc_msgForward姆怪?_objc_msgForward隸屬 C 語(yǔ)言叛赚,有三個(gè)參數(shù) :
--_objc_msgForward參數(shù)類型
1.所屬對(duì)象id類型
2.方法名 | SEL類型
3.可變參數(shù) |可變參數(shù)類型
二、 runtime如何實(shí)現(xiàn)weak變量的自動(dòng)置nil稽揭?
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随闪。
三阳似、能否向編譯后得到的類中增加實(shí)例變量?能否向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量铐伴?為什么撮奏?
不能向編譯后得到的類中增加實(shí)例變量;
能向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量当宴;
解釋下:
因?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_setIvarLayout或class_setWeakIvarLayout來處理 strong weak 引用即供。所以不能向存在的類中添加實(shí)例變量定拟;
運(yùn)行時(shí)創(chuàng)建的類是可以添加實(shí)例變量,調(diào)用class_addIvar函數(shù)逗嫡。但是得在調(diào)用objc_allocateClassPair之后青自,objc_registerClassPair之前,原因同上驱证。
四延窜、runloop和線程有什么關(guān)系?
總的說來抹锄,Run loop逆瑞,正如其名,loop表示某種循環(huán)伙单,和run放在一起就表示一直在運(yùn)行著的循環(huán)获高。實(shí)際上,run loop和線程是緊密相連的吻育,可以這樣說run loop是為了線程而生念秧,沒有線程,它就沒有存在的必要布疼。Run loops是線程的基礎(chǔ)架構(gòu)部分摊趾, Cocoa 和 CoreFundation 都提供了 run loop 對(duì)象方便配置和管理線程的 run loop (以下都以 Cocoa 為例)。每個(gè)線程游两,包括程序的主線程( main thread )都有與之相應(yīng)的 run loop 對(duì)象砾层。
runloop 和線程的關(guān)系:
主線程的run loop默認(rèn)是啟動(dòng)的。
iOS的應(yīng)用程序里面贱案,程序啟動(dòng)后會(huì)有一個(gè)如下的main()函數(shù)
intmain(intargc,char* argv[]) {? @autoreleasepool {returnUIApplicationMain(argc, argv,nil,NSStringFromClass([AppDelegateclass]));? }}
重點(diǎn)是UIApplicationMain()函數(shù)肛炮,這個(gè)方法會(huì)為main thread設(shè)置一個(gè)NSRunLoop對(duì)象,這就解釋了:為什么我們的應(yīng)用可以在無人操作的時(shí)候休息,需要讓它干活的時(shí)候又能立馬響應(yīng)铸董。
對(duì)其它線程來說,run loop默認(rèn)是沒有啟動(dòng)的肴沫,如果你需要更多的線程交互則可以手動(dòng)配置和啟動(dòng)粟害,如果線程只是去執(zhí)行一個(gè)長(zhǎng)時(shí)間的已確定的任務(wù)則不需要。
在任何一個(gè) Cocoa 程序的線程中颤芬,都可以通過以下代碼來獲取到當(dāng)前線程的 run loop 悲幅。
NSRunLoop*runloop = [NSRunLoopcurrentRunLoop];
五、runloop的mode作用是什么站蝠?
model 主要是用來指定事件在運(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集合
蘋果公開提供的 Mode 有兩個(gè):
NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
NSRunLoopCommonModes(kCFRunLoopCommonModes)
六菱魔、以+ scheduledTimerWithTimeInterval...的方式觸發(fā)的timer留荔,在滑動(dòng)頁(yè)面上的列表時(shí),timer會(huì)暫定回調(diào)澜倦,為什么聚蝶?如何解決?
//這樣寫默認(rèn)把timer加入到了默認(rèn)runloop藻治。
ScrollView滾動(dòng)過程中NSDefaultRunLoopMode(kCFRunLoopDefaultMode)的mode會(huì)切換到UITrackingRunLoopMode來保證ScrollView的流暢滑動(dòng)碘勉。
Timer計(jì)時(shí)會(huì)被scrollView的滑動(dòng)影響的問題可以通過將timer添加到NSRunLoopCommonModes(kCFRunLoopCommonModes)來解決。
七桩卵、猜想runloop內(nèi)部是如何實(shí)現(xiàn)的验靡?
一般來講,一個(gè)線程一次只能執(zhí)行一個(gè)任務(wù)雏节,執(zhí)行完成后線程就會(huì)退出胜嗓。如果我們需要一個(gè)機(jī)制,讓線程能隨時(shí)處理事件但并不退出钩乍,通常的代碼邏輯 是這樣的:
function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}
八兼蕊、objc使用什么機(jī)制管理對(duì)象內(nèi)存?
通過 retainCount 的機(jī)制來決定對(duì)象是否需要釋放件蚕。 每次 runloop 的時(shí)候孙技,都會(huì)檢查對(duì)象的 retainCount,如果retainCount 為 0排作,說明該對(duì)象沒有地方需要繼續(xù)使用了牵啦,可以釋放掉了。
九妄痪、ARC通過什么方式幫助開發(fā)者管理內(nèi)存哈雏?
ARC相對(duì)于MRC,不是在編譯時(shí)添加retain/release/autorelease這么簡(jiǎn)單。應(yīng)該是編譯期和運(yùn)行期兩部分共同幫助開發(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ù)雜还最,但也不能被忽略。
十毡惜、不手動(dòng)指定autoreleasepool的前提下拓轻,一個(gè)autorealese對(duì)象在什么時(shí)刻釋放?(比如在一個(gè)vc的viewDidLoad中創(chuàng)建)
分兩種情況:手動(dòng)干預(yù)釋放時(shí)機(jī)经伙、系統(tǒng)自動(dòng)去釋放扶叉。
手動(dòng)干預(yù)釋放時(shí)機(jī)--指定autoreleasepool 就是所謂的:當(dāng)前作用域大括號(hào)結(jié)束時(shí)釋放。
系統(tǒng)自動(dòng)去釋放--不手動(dòng)指定autoreleasepool
Autorelease對(duì)象出了作用域之后帕膜,會(huì)被添加到最近一次創(chuàng)建的自動(dòng)釋放池中辜梳,并會(huì)在當(dāng)前的 runloop 迭代結(jié)束時(shí)釋放。
從程序啟動(dòng)到加載完成是一個(gè)完整的運(yùn)行循環(huán)泳叠,然后會(huì)停下來作瞄,等待用戶交互,用戶的每一次交互都會(huì)啟動(dòng)一次運(yùn)行循環(huán)危纫,來處理用戶所有的點(diǎn)擊事件宗挥、觸摸事件。
我們都知道:所有 autorelease 的對(duì)象种蝶,在出了作用域之后契耿,會(huì)被自動(dòng)添加到最近創(chuàng)建的自動(dòng)釋放池中。
但是如果每次都放進(jìn)應(yīng)用程序的main.m中的 autoreleasepool 中螃征,遲早有被撐滿的一刻搪桂。這個(gè)過程中必定有一個(gè)釋放的動(dòng)作。何時(shí)盯滚?
在一次完整的運(yùn)行循環(huán)結(jié)束之前踢械,會(huì)被銷毀。
那什么時(shí)間會(huì)創(chuàng)建自動(dòng)釋放池魄藕?運(yùn)行循環(huán)檢測(cè)到事件并啟動(dòng)后内列,就會(huì)創(chuàng)建自動(dòng)釋放池。
子線程的 runloop 默認(rèn)是不工作背率,無法主動(dòng)創(chuàng)建话瞧,必須手動(dòng)創(chuàng)建嫩与。
自定義的 NSOperation 和 NSThread 需要手動(dòng)創(chuàng)建自動(dòng)釋放池。比如: 自定義的 NSOperation 類中的 main 方法里就必須添加自動(dòng)釋放池交排。否則出了作用域后划滋,自動(dòng)釋放對(duì)象會(huì)因?yàn)闆]有自動(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)釋放池被銷毀或者耗盡時(shí)稻薇,會(huì)向自動(dòng)釋放池中的所有對(duì)象發(fā)送 release 消息嫂冻,釋放自動(dòng)釋放池中的所有對(duì)象胶征。
如果在一個(gè)vc的viewDidLoad中創(chuàng)建一個(gè) Autorelease對(duì)象,那么該對(duì)象會(huì)在 viewDidAppear 方法執(zhí)行前就被銷毀了桨仿。
11.BAD_ACCESS在什么情況下出現(xiàn)睛低?
訪問了懸垂指針,比如對(duì)一個(gè)已經(jīng)釋放的對(duì)象執(zhí)行了release服傍、訪問已經(jīng)釋放對(duì)象的成員變量或者發(fā)消息钱雷。 死循環(huán)
12、蘋果是如何實(shí)現(xiàn)autoreleasepool的吹零?
autoreleasepool 以一個(gè)隊(duì)列數(shù)組的形式實(shí)現(xiàn),主要通過下列三個(gè)函數(shù)完成.
objc_autoreleasepoolPush
objc_autoreleasepoolPop
objc_autorelease
看函數(shù)名就可以知道罩抗,對(duì) autorelease 分別執(zhí)行 push,和 pop 操作灿椅。銷毀對(duì)象時(shí)執(zhí)行release操作套蒂。
13.使用block時(shí)什么情況會(huì)發(fā)生引用循環(huán),如何解決茫蛹?
一個(gè)對(duì)象中強(qiáng)引用了block操刀,在block中又強(qiáng)引用了該對(duì)象,就會(huì)發(fā)射循環(huán)引用婴洼。
解決方法是將該對(duì)象使用__weak或者_(dá)_block修飾符修飾之后再在block中使用骨坑。
id weak weakSelf = self; 或者 weak __typeof(&*self)weakSelf = self該方法可以設(shè)置宏
id __block weakSelf = self;
或者將其中一方強(qiáng)制制空xxx = nil。
14.在block內(nèi)如何修改block外部變量柬采?
默認(rèn)情況下欢唾,在block中訪問的外部變量是復(fù)制過去的,即:寫操作不對(duì)原變量生效粉捻。但是你可以加上__block來讓其寫操作生效匈辱,示例代碼如下:
__blockinta =0;void(^foo)(void) = ^{? ? ? ? a =1;? ? };foo();//這里,a的值被修改為1
真正的原因是這樣的:
我們都知道:Block不允許修改外部變量的值杀迹,這里所說的外部變量的值亡脸,指的是棧中指針的內(nèi)存地址押搪。__block所起到的作用就是只要觀察到該變量被 block 所持有,就將“外部變量”在棧中的內(nèi)存地址放到了堆中浅碾。進(jìn)而在block內(nèi)部也可以修改外部變量的值大州。
Block不允許修改外部變量的值。Apple這樣設(shè)計(jì)垂谢,應(yīng)該是考慮到了block的特殊性厦画,block也屬于“函數(shù)”的范疇,變量進(jìn)入block滥朱,實(shí)際就是已經(jīng)改變了作用域根暑。在幾個(gè)作用域之間進(jìn)行切換時(shí),如果不加上這樣的限制徙邻,變量的可維護(hù)性將大大降低排嫌。又比如我想在block內(nèi)聲明了一個(gè)與外部同名的變量,此時(shí)是允許呢還是不允許呢缰犁?只有加上了這樣的限制淳地,這樣的情景才能實(shí)現(xiàn)。于是棧區(qū)變成了紅燈區(qū)帅容,堆區(qū)變成了綠燈區(qū)颇象。
我們可以打印下內(nèi)存地址來進(jìn)行驗(yàn)證:
__blockinta =0;NSLog(@"定義前:%p", &a);//棧區(qū)void(^foo)(void) = ^{? ? ? a =1;NSLog(@"block內(nèi)部:%p", &a);//堆區(qū)};NSLog(@"定義后:%p", &a);//堆區(qū)foo();
2016-05-1702:03:33.559LeanCloudChatKit-iOS[1505:713679] 定義前:0x16fda86f82016-05-1702:03:33.559LeanCloudChatKit-iOS[1505:713679] 定義后:0x155b22fc82016-05-1702:03:33.559LeanCloudChatKit-iOS[1505:713679] block內(nèi)部:0x155b22fc8
“定義后”和“block內(nèi)部”兩者的內(nèi)存地址是一樣的,我們都知道 block 內(nèi)部的變量會(huì)被 copy 到堆區(qū)并徘,“block內(nèi)部”打印的是堆地址遣钳,因而也就可以知道,“定義后”打印的也是堆的地址麦乞。
那么如何證明“block內(nèi)部”打印的是堆地址蕴茴?
把三個(gè)16進(jìn)制的內(nèi)存地址轉(zhuǎn)成10進(jìn)制就是:
定義后前:6171559672
block內(nèi)部:5732708296
定義后后:5732708296
中間相差438851376個(gè)字節(jié),也就是 418.5M 的空間路幸,因?yàn)槎训刂芬∮跅5刂芳隹忠驗(yàn)閕OS中一個(gè)進(jìn)程的棧區(qū)內(nèi)存只有1M,Mac也只有8M简肴,顯然a已經(jīng)是在堆區(qū)了晃听。
這也證實(shí)了:a 在定義前是棧區(qū),但只要進(jìn)入了 block 區(qū)域砰识,就變成了堆區(qū)能扒。這才是__block關(guān)鍵字的真正作用。
__block關(guān)鍵字修飾后辫狼,int類型也從4字節(jié)變成了32字節(jié)初斑,這是 Foundation 框架 malloc 出來的。這也同樣能證實(shí)上面的結(jié)論膨处。(PS:居然比 NSObject alloc 出來的 16 字節(jié)要多一倍)见秤。
理解到這是因?yàn)槎褩5刂返淖兏笆撬^的“寫操作生效”,這一點(diǎn)至關(guān)重要鹃答,要不然你如何解釋下面這個(gè)現(xiàn)象:
以下代碼編譯可以通過乎澄,并且在block中成功將a的從Tom修改為Jerry。
NSMutableString*a = [NSMutableStringstringWithString:@"Tom"];NSLog(@"\n定以前:------------------------------------\n\a指向的堆中地址:%p测摔;a在棧中的指針地址:%p", a, &a);//a在棧區(qū)void(^foo)(void) = ^{? ? ? a.string=@"Jerry";NSLog(@"\nblock內(nèi)部:------------------------------------\n\a指向的堆中地址:%p置济;a在棧中的指針地址:%p", a, &a);//a在棧區(qū)a = [NSMutableStringstringWithString:@"William"];? };foo();NSLog(@"\n定以后:------------------------------------\n\a指向的堆中地址:%p;a在棧中的指針地址:%p", a, &a);//a在棧區(qū)
這里的a已經(jīng)由基本數(shù)據(jù)類型锋八,變成了對(duì)象類型浙于。block會(huì)對(duì)對(duì)象類型的指針進(jìn)行copy,copy到堆中挟纱,但并不會(huì)改變?cè)撝羔標(biāo)赶虻亩阎械牡刂沸咝铮栽谏厦娴氖纠a中,block體內(nèi)修改的實(shí)際是a指向的堆中的內(nèi)容樊销。
但如果我們嘗試像上面圖片中的65行那樣做整慎,結(jié)果會(huì)編譯不通過脏款,那是因?yàn)榇藭r(shí)你在修改的就不是堆中的內(nèi)容围苫,而是棧中的內(nèi)容。
上文已經(jīng)說過:Block不允許修改外部變量的值撤师,這里所說的外部變量的值剂府,指的是棧中指針的內(nèi)存地址。棧區(qū)是紅燈區(qū)剃盾,堆區(qū)才是綠燈區(qū)腺占。
15.使用系統(tǒng)的某些block api(如UIView的block版本寫動(dòng)畫時(shí)),是否也考慮引用循環(huán)問題痒谴?
系統(tǒng)的某些block api中衰伯,UIView的block版本寫動(dòng)畫時(shí)不需要考慮,但也有一些api 需要考慮:
所謂“引用循環(huán)”是指雙向的強(qiáng)引用积蔚,所以那些“單向的強(qiáng)引用”(block 強(qiáng)引用 self )沒有問題意鲸,比如這些:
[UIViewanimateWithDuration:durationanimations:^{ [self.superviewlayoutIfNeeded]; }];
[[NSOperationQueuemainQueue]addOperationWithBlock:^{ self.someProperty= xyz; }];
[[NSNotificationCenterdefaultCenter]addObserverForName:@"someNotification"object:nilqueue:[NSOperationQueuemainQueue]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;[strongSelfdoSomething];[strongSelfdoSomethingElse];} );
類似的:
__weak__typeof__(self) weakSelf = self; _observer = [[NSNotificationCenterdefaultCenter]addObserverForName:@"testKey"object:nilqueue:nilusingBlock:^(NSNotification*note) {__typeof__(self) strongSelf = weakSelf;? ? [strongSelfdismissModalViewControllerAnimated:YES]; }];
self --> _observer --> block --> self 顯然這也是一個(gè)循環(huán)引用槐雾。
16、GCD的隊(duì)列(dispatch_queue_t)分哪兩種類型幅狮?
串行隊(duì)列Serial Dispatch Queue
并行隊(duì)列Concurrent Dispatch Queue
17募强、如何用GCD同步若干個(gè)異步調(diào)用株灸?(如根據(jù)若干個(gè)url異步加載多張圖片,然后在都下載完成后合成一張整圖)
使用Dispatch Group追加block到Global Group Queue,這些block如果全部執(zhí)行完畢擎值,就會(huì)執(zhí)行Main Dispatch Queue中的結(jié)束處理的block蚂且。
dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);dispatch_group_tgroup = 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(), ^{//合并圖片});
18、dispatch_barrier_async的作用是什么幅恋?
在并行隊(duì)列中杏死,為了保持某些任務(wù)的順序,需要等待一些任務(wù)完成后才能繼續(xù)進(jìn)行捆交,使用 barrier 來等待之前任務(wù)完成淑翼,避免數(shù)據(jù)競(jìng)爭(zhēng)等問題。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ī)說:大家都去上廁所泞莉,速戰(zhàn)速?zèng)Q哪雕,上完廁所就上高速。超大的公共廁所鲫趁,大家同時(shí)去斯嚎,程序猿很快就結(jié)束了,但程序媛就可能會(huì)慢一些挨厚,即使你第一個(gè)回來堡僻,司機(jī)也不會(huì)出發(fā),司機(jī)要等待所有人都回來后疫剃,才能出發(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的作用一模一樣蹄溉。 )
19/蘋果為什么要廢棄dispatch_get_current_queue咨油?
dispatch_get_current_queue容易造成死鎖
20、以下代碼運(yùn)行結(jié)果如何柒爵?
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
只輸出:1 役电。發(fā)生主線程鎖死。dispatch_sync?
21棉胀、addObserver:forKeyPath:options:context:各個(gè)參數(shù)的作用分別是什么法瑟,observer中需要實(shí)現(xiàn)哪個(gè)方法才能獲得KVO回調(diào)冀膝?
//添加鍵值觀察/*1 觀察者,負(fù)責(zé)處理監(jiān)聽事件的對(duì)象2 觀察的屬性3 觀察的選項(xiàng)4 上下文*/[self.personaddObserver:selfforKeyPath:@"name"options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOldcontext:@"Person Name"];
observer中需要實(shí)現(xiàn)一下方法:
//所有的 kvo 監(jiān)聽到事件霎挟,都會(huì)調(diào)用此方法/*1. 觀察的屬性2. 觀察的對(duì)象3. change 屬性變化字典(新/舊)4. 上下文窝剖,與監(jiān)聽的時(shí)候傳遞的一致*/- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context;
22、如何手動(dòng)觸發(fā)一個(gè)value的KVO
所謂的“手動(dòng)觸發(fā)”是區(qū)別于“自動(dòng)觸發(fā)”:
自動(dòng)觸發(fā)是指類似這種場(chǎng)景:在注冊(cè) KVO 之前設(shè)置一個(gè)初始值酥夭,注冊(cè)之后赐纱,設(shè)置一個(gè)不一樣的值,就可以觸發(fā)了熬北。
想知道如何手動(dòng)觸發(fā)疙描,必須知道自動(dòng)觸發(fā) KVO 的原理:
鍵值觀察通知依賴于 NSObject 的兩個(gè)方法:willChangeValueForKey:和didChangevlueForKey:。在一個(gè)被觀察屬性發(fā)生改變之前讶隐,willChangeValueForKey:一定會(huì)被調(diào)用起胰,這就 會(huì)記錄舊的值。而當(dāng)改變發(fā)生后巫延,observeValueForKey:ofObject:change:context:會(huì)被調(diào)用效五,繼而didChangeValueForKey:也會(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瓜客,那么代碼如下:最后兩行代碼缺一不可适瓦。
相關(guān)代碼已放在倉(cāng)庫(kù)里竿开。
//.m文件//Created by https://github.com/ChenYilong//微博@iOS程序犭袁(http://weibo.com/luohanchenyilong/).//手動(dòng)觸發(fā) value 的KVO,最后兩行代碼缺一不可玻熙。//@property (nonatomic, strong) NSDate *now;- (void)viewDidLoad {? [superviewDidLoad];? _now = [NSDatedate];? [selfaddObserver:selfforKeyPath:@"now"options:NSKeyValueObservingOptionNewcontext:nil];NSLog(@"1");? [selfwillChangeValueForKey:@"now"];//“手動(dòng)觸發(fā)self.now的KVO”否彩,必寫。NSLog(@"2");? [selfdidChangeValueForKey:@"now"];//“手動(dòng)觸發(fā)self.now的KVO”嗦随,必寫列荔。NSLog(@"4");}
但是平時(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)閟etNow:是合成方法,有時(shí)候我們也能看到有人這么寫代碼:
- (void)setNow:(NSDate*)aDate {? [selfwillChangeValueForKey:@"now"];//沒有必要_now = aDate;? [selfdidChangeValueForKey:@"now"];//沒有必要}
這完全沒有必要盯质,不要這么做袁串,這樣的話概而,KVO代碼會(huì)被調(diào)用兩次。KVO在調(diào)用存取方法之前總是調(diào)用willChangeValueForKey:囱修,之后總是調(diào)用didChangeValueForkey:赎瑰。怎么做到的呢?答案是通過 isa 混寫(isa-swizzling)。
23破镰、若一個(gè)類有實(shí)例變量NSString *_foo餐曼,調(diào)用setValue:forKey:時(shí),可以以foo還是_foo作為key鲜漩?
都可以晋辆。
24、KVC的keyPath中的集合運(yùn)算符如何使用宇整?
必須用在集合對(duì)象上或普通對(duì)象的集合屬性上
簡(jiǎn)單集合運(yùn)算符有@avg瓶佳, @count , @max 鳞青, @min 霸饲,@sum,
格式 @"@sum.age"或 @"集合屬性.@max.age"
25臂拓、KVC和KVO的keyPath一定是屬性么厚脉?
KVC 支持實(shí)例變量,KVO 只能手動(dòng)支持手動(dòng)設(shè)定實(shí)例變量的KVO實(shí)現(xiàn)監(jiān)聽
26胶惰、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 并不希望過多暴露 KVO 的實(shí)現(xiàn)細(xì)節(jié)。不過孵滞,要是借助 runtime 提供的方法去深入挖掘中捆,所有被掩蓋的細(xì)節(jié)都會(huì)原形畢露:
當(dāng)你觀察一個(gè)對(duì)象時(shí),一個(gè)新的類會(huì)被動(dòng)態(tài)創(chuàng)建坊饶。這個(gè)類繼承自該對(duì)象的原本的類泄伪,并重寫了被觀察屬性的 setter 方法。重寫的 setter 方法會(huì)負(fù)責(zé)在調(diào)用原 setter 方法之前和之后匿级,通知所有觀察對(duì)象:值的更改蟋滴。最后通過isa 混寫(isa-swizzling)把這個(gè)對(duì)象的 isa 指針 ( isa 指針告訴 Runtime 系統(tǒng)這個(gè)對(duì)象的類是什么 ) 指向這個(gè)新創(chuàng)建的子類,對(duì)象就神奇的變成了新創(chuàng)建的子類的實(shí)例痘绎。我畫了一張示意圖津函,如下所示:
下面做下詳細(xì)解釋:
鍵值觀察通知依賴于 NSObject 的兩個(gè)方法:willChangeValueForKey:和didChangevlueForKey:。在一個(gè)被觀察屬性發(fā)生改變之前孤页,willChangeValueForKey:一定會(huì)被調(diào)用尔苦,這就會(huì)記錄舊的值。而當(dāng)改變發(fā)生后,observeValueForKey:ofObject:change:context:會(huì)被調(diào)用蕉堰,繼而didChangeValueForKey:也會(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)閟etNow:是合成方法,有時(shí)候我們也能看到有人這么寫代碼:
- (void)setNow:(NSDate*)aDate {? [selfwillChangeValueForKey:@"now"];//沒有必要_now = aDate;? [selfdidChangeValueForKey:@"now"];//沒有必要}
這完全沒有必要挤土,不要這么做琴庵,這樣的話,KVO代碼會(huì)被調(diào)用兩次仰美。KVO在調(diào)用存取方法之前總是調(diào)用willChangeValueForKey:迷殿,之后總是調(diào)用didChangeValueForkey:。怎么做到的呢?答案是通過 isa 混寫(isa-swizzling)咖杂。第一次對(duì)一個(gè)對(duì)象調(diào)用addObserver:forKeyPath:options:context:時(shí)庆寺,框架會(huì)創(chuàng)建這個(gè)類的新的 KVO 子類,并將被觀察對(duì)象轉(zhuǎn)換為新子類的對(duì)象诉字。在這個(gè) KVO 特殊子類中懦尝, Cocoa 創(chuàng)建觀察屬性的 setter ,大致工作原理如下:
- (void)setNow:(NSDate*)aDate {? [selfwillChangeValueForKey:@"now"];? [supersetValue:aDateforKey:@"now"];? [selfdidChangeValueForKey:@"now"];}
這種繼承和方法注入是在運(yùn)行時(shí)而不是編譯時(shí)實(shí)現(xiàn)的壤圃。這就是正確命名如此重要的原因陵霉。只有在使用KVC命名約定時(shí),KVO才能做到這一點(diǎn)埃唯。
KVO 在實(shí)現(xiàn)中通過isa 混寫(isa-swizzling)把這個(gè)對(duì)象的 isa 指針 ( isa 指針告訴 Runtime 系統(tǒng)這個(gè)對(duì)象的類是什么 ) 指向這個(gè)新創(chuàng)建的子類撩匕,對(duì)象就神奇的變成了新創(chuàng)建的子類的實(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 混寫( isa-swizzling)墨叛,這個(gè)的確不是很容易發(fā)現(xiàn):Apple 還重寫、覆蓋了-class方法并返回原來的類模蜡。 企圖欺騙我們:這個(gè)類沒有變漠趁,就是原本那個(gè)類。忍疾。闯传。
但是,假設(shè)“被監(jiān)聽的對(duì)象”的類對(duì)象是MYClass卤妒,有時(shí)候我們能看到對(duì)NSKVONotifying_MYClass的引用而不是對(duì)MYClass的引用甥绿。借此我們得以知道 Apple 使用了isa 混寫(isa-swizzling)
那么wilChangeValueForKey:字币、didChangeValueForKey:和observeValueForKeyPath:ofObject:change:context:這三個(gè)方法的執(zhí)行順序是怎樣的呢?
wilChangeValueForKey:共缕、didChangeValueForKey:很好理解洗出,observeValueForKeyPath:ofObject:change:context:的執(zhí)行時(shí)機(jī)是什么時(shí)候呢?
先看一個(gè)例子:
順序似乎是wilChangeValueForKey:图谷、observeValueForKeyPath:ofObject:change:context:翩活、didChangeValueForKey:。
其實(shí)不然便贵,這里有一個(gè)observeValueForKeyPath:ofObject:change:context:, 和didChangeValueForKey:到底誰(shuí)先調(diào)用的問題:如果observeValueForKeyPath:ofObject:change:context:是在didChangeValueForKey:內(nèi)部觸發(fā)的操作呢菠镇? 那么順序就是:wilChangeValueForKey:、didChangeValueForKey:和observeValueForKeyPath:ofObject:change:context:
不信你把didChangeValueForKey:注視掉承璃,看下observeValueForKeyPath:ofObject:change:context:會(huì)不會(huì)執(zhí)行利耍。
了解到這一點(diǎn)很重要
27、IBOutlet連出來的視圖屬性為什么可以被設(shè)置成weak?
因?yàn)榧热挥型怄溎敲匆晥D在xib或者storyboard中肯定存在盔粹,視圖已經(jīng)對(duì)它有一個(gè)強(qiáng)引用了堂竟。
不過這個(gè)回答漏了個(gè)重要知識(shí),使用storyboard(xib不行)創(chuàng)建的vc玻佩,會(huì)有一個(gè)叫_topLevelObjectsToKeepAliveFromStoryboard的私有數(shù)組強(qiáng)引用所有top level的對(duì)象出嘹,所以這時(shí)即便outlet聲明成weak也沒關(guān)系
28、IB中User Defined Runtime Attributes如何使用咬崔?
它能夠通過KVC的方式配置一些你在interface builder 中不能配置的屬性税稼。當(dāng)你希望在IB中作盡可能多得事情,這個(gè)特性能夠幫助你編寫更加輕量級(jí)的viewcontroller
29垮斯、如何調(diào)試BAD_ACCESS錯(cuò)誤
重寫object的respondsToSelector方法郎仆,現(xiàn)實(shí)出現(xiàn)EXEC_BAD_ACCESS前訪問的最后一個(gè)object
通過 Zombie
設(shè)置全局?jǐn)帱c(diǎn)快速定位問題代碼所在行
Xcode 7 已經(jīng)集成了BAD_ACCESS捕獲功能:Address Sanitizer。 用法如下:在配置中勾選?Enable Address Sanitizer
230兜蠕、lldb(gdb)常用的調(diào)試命令扰肌?
breakpoint 設(shè)置斷點(diǎn)定位到某一個(gè)函數(shù)
n 斷點(diǎn)指針下一步
po打印對(duì)象
調(diào)試快捷鍵:(Xcode常用快捷鍵)
command+shift+Y 打開調(diào)試窗口
command+Y 調(diào)試運(yùn)行程序
command+option+P 繼續(xù)
command+shift+O 跳過
command+shift+I 進(jìn)入
command+shift+T 跳出