歡迎加入iOS交流群2466454,大家互相交流學(xué)習(xí)蚓再!
阿里-p6-一面
1.介紹下內(nèi)存的幾大區(qū)域?
2.你是如何組件化解耦的呆盖?
3.runtime如何通過(guò)selector找到對(duì)應(yīng)的IMP地址
4.runloop內(nèi)部實(shí)現(xiàn)邏輯?
5.你理解的多線程贷笛?
6.GCD執(zhí)行原理应又?
7.怎么防止別人反編譯你的app?
8.YYAsyncLayer如何異步繪制乏苦?
9.優(yōu)化你是從哪幾方面著手株扛?
1.介紹下內(nèi)存的幾大區(qū)域?
1.棧區(qū)(stack) 由編譯器自動(dòng)分配并釋放汇荐,存放函數(shù)的參數(shù)值洞就,局部變量等。棧是系統(tǒng)數(shù)據(jù)結(jié)構(gòu)掀淘,對(duì)應(yīng)線程/進(jìn)程是唯一的旬蟋。優(yōu)點(diǎn)是快速高效,缺點(diǎn)時(shí)有限制革娄,數(shù)據(jù)不靈活倾贰。[先進(jìn)后出]
棧空間分靜態(tài)分配 和動(dòng)態(tài)分配兩種拦惋。
堆區(qū)(heap) 由程序員分配和釋放匆浙,如果程序員不釋放,程序結(jié)束時(shí)厕妖,可能會(huì)由操作系統(tǒng)回收 吞彤,比如在ios 中 alloc 都是存放在堆中。
優(yōu)點(diǎn)是靈活方便叹放,數(shù)據(jù)適應(yīng)面廣泛饰恕,但是效率有一定降低。
雖然程序結(jié)束時(shí)所有的數(shù)據(jù)空間都會(huì)被釋放回系統(tǒng)井仰,但是精確的申請(qǐng)內(nèi)存埋嵌,釋放內(nèi)存匹配是良好程序的基本要素。
3.全局區(qū)(靜態(tài)區(qū)) (static) 全局變量和靜態(tài)變量的存儲(chǔ)是放在一起的俱恶,初始化的全局變量和靜態(tài)變量存放在一塊區(qū)域雹嗦,未初始化的全局變量和靜態(tài)變量在相鄰的另一塊區(qū)域,程序結(jié)束后有系統(tǒng)釋放合是。
4.文字常量區(qū) 存放常量字符串了罪,程序結(jié)束后由系統(tǒng)釋放;
5.代碼區(qū) 存放函數(shù)的二進(jìn)制代碼
大致如圖:
例子代碼:
可能被追問(wèn)的問(wèn)題一:
1.棧區(qū) (stack [st?k]): 由編譯器自動(dòng)分配釋放
局部變量是保存在棧區(qū)的
方法調(diào)用的實(shí)參也是保存在棧區(qū)的
2.堆區(qū) (heap [hi?p]): 由程序員分配釋放聪全,若程序員不釋放泊藕,會(huì)出現(xiàn)內(nèi)存泄漏,賦值語(yǔ)句右側(cè) 使用 new 方法創(chuàng)建的對(duì)象难礼,被創(chuàng)建對(duì)象的所有 成員變量娃圆!
3.BSS 段 : 程序結(jié)束后由系統(tǒng)釋放
4.數(shù)據(jù)段 : 程序結(jié)束后由系統(tǒng)釋放
5.代碼段:程序結(jié)束后由系統(tǒng)釋放
程序編譯鏈接 后的二進(jìn)制可執(zhí)行代碼
可能被追問(wèn)的問(wèn)題二:
比如申請(qǐng)后的系統(tǒng)是如何響應(yīng)的玫锋?
棧:存儲(chǔ)每一個(gè)函數(shù)在執(zhí)行的時(shí)候都會(huì)向操作系統(tǒng)索要資源,棧區(qū)就是函數(shù)運(yùn)行時(shí)的內(nèi)存讼呢,棧區(qū)中的變量由編譯器負(fù)責(zé)分配和釋放撩鹿,內(nèi)存隨著函數(shù)的運(yùn)行分配,隨著函數(shù)的結(jié)束而釋放悦屏,由系統(tǒng)自動(dòng)完成节沦。
注意:只要棧的剩余空間大于所申請(qǐng)空間,系統(tǒng)將為程序提供內(nèi)存础爬,否則將報(bào)異常提示棧溢出甫贯。
堆:
1.首先應(yīng)該知道操作系統(tǒng)有一個(gè)記錄空閑內(nèi)存地址的鏈表。
2.當(dāng)系統(tǒng)收到程序的申請(qǐng)時(shí)幕帆,會(huì)遍歷該鏈表获搏,尋找第一個(gè)空間大于所申請(qǐng)空間的堆結(jié)點(diǎn)赖条,然后將該結(jié)點(diǎn)從空閑結(jié)點(diǎn)鏈表中刪除失乾,并將該結(jié)點(diǎn)的空間分配給程序。
3 .由于找到的堆結(jié)點(diǎn)的大小不一定正好等于申請(qǐng)的大小纬乍,系統(tǒng)會(huì)自動(dòng)的將多余的那部分重新放入空閑鏈表中
可能被追問(wèn)的問(wèn)題三:
比如:申請(qǐng)大小的限制是怎樣的碱茁?
棧:棧是向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是一塊連續(xù)的內(nèi)存的區(qū)域仿贬。是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的纽竣,棧的大小是2M(也有的說(shuō)是1M,總之是一個(gè)編譯時(shí)就確定的常數(shù) ) ,如果申請(qǐng)的空間超過(guò)棧的剩余空間時(shí)茧泪,將提示overflow蜓氨。因此,能從棧獲得的空間較小队伟。
堆:堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu)穴吹,是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)是用鏈表來(lái)存儲(chǔ)的空閑內(nèi)存地址的嗜侮,自然是不連續(xù)的港令,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計(jì)算機(jī)系統(tǒng)中有效的虛擬內(nèi)存锈颗。由此可見(jiàn)顷霹,堆獲得的空間比較靈活,也比較大击吱。
棧:由系統(tǒng)自動(dòng)分配淋淀,速度較快,不會(huì)產(chǎn)生內(nèi)存碎片
堆:是由alloc分配的內(nèi)存覆醇,速度比較慢绅喉,而且容易產(chǎn)生內(nèi)存碎片渠鸽,不過(guò)用起來(lái)最方便
打個(gè)比喻來(lái)說(shuō):
使用棧就象我們?nèi)ワ堭^里吃飯,只管點(diǎn)菜(發(fā)出申請(qǐng))柴罐、付錢(qián)徽缚、和吃(使用),吃飽了就走革屠,不必理會(huì)切菜凿试、洗菜等準(zhǔn)備工作和洗碗、刷鍋等掃尾工作似芝,他的好處是快捷那婉,但是自由度小。
使用堆就象是自己動(dòng)手做喜歡吃的菜肴党瓮,比較麻煩详炬,但是比較符合自己的口味,而且自由度大寞奸。
2.你是如何組件化解耦的呛谜?
實(shí)現(xiàn)代碼的高內(nèi)聚低耦合,方便多人多團(tuán)隊(duì)開(kāi)發(fā)枪萄!
一般需要解耦的項(xiàng)目都會(huì)多多少少出現(xiàn)隐岛,一下幾個(gè)情況:
耦合比較嚴(yán)重(因?yàn)闆](méi)有明確的約束,「組件」間引用的現(xiàn)象會(huì)比較多)
2.容易出現(xiàn)沖突(尤其是使用 Xib瓷翻,還有就是 Xcode Project聚凹,雖說(shuō)有腳本可以改善)
3.業(yè)務(wù)方的開(kāi)發(fā)效率不夠高(只關(guān)心自己的組件,卻要編譯整個(gè)項(xiàng)目齐帚,與其他不相干的代碼糅合在一起)
先來(lái)看下妒牙,組件化之后的一個(gè)大概架構(gòu)
「組件化」顧名思義就是把一個(gè)大的 App 拆成一個(gè)個(gè)小的組件,相互之間不直接引用对妄。那如何做呢湘今?
組件間通信
以 iOS 為例,由于之前就是采用的 URL 跳轉(zhuǎn)模式饥伊,理論上頁(yè)面之間的跳轉(zhuǎn)只需 open 一個(gè) URL 即可象浑。所以對(duì)于一個(gè)組件來(lái)說(shuō),只要定義「支持哪些 URL」即可琅豆,比如詳情頁(yè)愉豺,大概可以這么做的
首頁(yè)只需調(diào)用[MGJRouter openURL:@"mgj://detail?id=404"]就可以打開(kāi)相應(yīng)的詳情頁(yè)。
那問(wèn)題又來(lái)了茫因,我怎么知道有哪些可用的 URL蚪拦?為此粹懒,我們做了一個(gè)后臺(tái)專(zhuān)門(mén)來(lái)管理藻三。
然后可以把這些短鏈生成不同平臺(tái)所需的文件鬓椭,iOS 平臺(tái)生成 .{h,m} 文件昨寞,Android 平臺(tái)生成 .java 文件,并注入到項(xiàng)目中括袒。這樣開(kāi)發(fā)人員只需在項(xiàng)目中打開(kāi)該文件就知道所有的可用 URL 了次兆。
目前還有一塊沒(méi)有做,就是參數(shù)這塊锹锰,雖然描述了短鏈芥炭,但真想要生成完整的 URL,還需要知道如何傳參數(shù)恃慧,這個(gè)正在開(kāi)發(fā)中园蝠。
還有一種情況會(huì)稍微麻煩點(diǎn),就是「組件A」要調(diào)用「組件B」的某個(gè)方法痢士,比如在商品詳情頁(yè)要展示購(gòu)物車(chē)的商品數(shù)量彪薛,就涉及到向購(gòu)物車(chē)組件拿數(shù)據(jù)。
類(lèi)似這種同步調(diào)用怠蹂,iOS 之前采用了比較簡(jiǎn)單的方案善延,還是依托于MGJRouter,不過(guò)添加了新的方法- (id)objectForURL:褥蚯,注冊(cè)時(shí)也使用新的方法進(jìn)行注冊(cè)
使用時(shí)NSNumber *orderCount = [MGJRouter objectForURL:@"mgj://cart/ordercount"]這樣就拿到了購(gòu)物車(chē)?yán)锏纳唐窋?shù)挚冤。
稍微復(fù)雜但更具通用性的方法是使用「協(xié)議」 <-> 「類(lèi)」綁定的方式况增,還是以購(gòu)物車(chē)為例赞庶,購(gòu)物車(chē)組件可以提供這么個(gè) Protocol
可以看到通過(guò)協(xié)議可以直接指定返回的數(shù)據(jù)類(lèi)型。然后在購(gòu)物車(chē)組件內(nèi)再新建個(gè)類(lèi)實(shí)現(xiàn)這個(gè)協(xié)議澳骤,假設(shè)這個(gè)類(lèi)名為MGJCartImpl歧强,接著就可以把它與協(xié)議關(guān)聯(lián)起來(lái)[ModuleManagerregisterClass:MGJCartImplforProtocol:@protocol(MGJCart)]为肮,對(duì)于使用方來(lái)說(shuō)重斑,要拿到這個(gè)MGJCartImpl假颇,需要調(diào)用[ModuleManagerclassForProtocol:@protocol(MGJCart)]泰讽。拿到之后再調(diào)用+ (NSInteger)orderCount就可以了愧哟。
那么甘改,這個(gè)協(xié)議放在哪里比較合適呢榄融?如果跟組件放在一起,使用時(shí)還是要先引入組件,如果有多個(gè)這樣的組件就會(huì)比較麻煩了。所以我們把這些公共的協(xié)議統(tǒng)一放到了PublicProtocolDomain.h下忌怎,到時(shí)只依賴(lài)這一個(gè)文件就可以了酪夷。
Android 也是采用類(lèi)似的方式榴啸。
組件生命周期管理
理想中的組件可以很方便地集成到主客中,并且有跟AppDelegate一致的回調(diào)方法晚岭。這也是ModuleManager做的事情鸥印。
先來(lái)看看現(xiàn)在的入口方法
其中[MGJApp startApp]主要負(fù)責(zé)一些 SDK 的初始化。[self trackLaunchTime]是我們打的一個(gè)點(diǎn),用來(lái)監(jiān)測(cè)從main方法開(kāi)始到入口方法調(diào)用結(jié)束花了多長(zhǎng)時(shí)間库说。其他的都由ModuleManager搞定狂鞋,loadModuleFromPlist:pathForResource:方法會(huì)讀取 bundle 里的一個(gè) plist 文件,這個(gè)文件的內(nèi)容大概是這樣的
每個(gè)Module都實(shí)現(xiàn)了ModuleProtocol潜的,其中有一個(gè)- (BOOL)applicaiton:didFinishLaunchingWithOptions:方法骚揍,如果實(shí)現(xiàn)了的話,就會(huì)被調(diào)用啰挪。
還有一個(gè)問(wèn)題就是信不,系統(tǒng)的一些事件會(huì)有通知,比如applicationDidBecomeActive會(huì)有對(duì)應(yīng)的UIApplicationDidBecomeActiveNotification亡呵,組件如果要做響應(yīng)的話抽活,只需監(jiān)聽(tīng)這個(gè)系統(tǒng)通知即可。但也有一些事件是沒(méi)有通知的锰什,比如- application:didRegisterUserNotificationSettings:酌壕,這時(shí)組件如果也要做點(diǎn)事情,怎么辦歇由?
一個(gè)簡(jiǎn)單的解決方法是在AppDelegate的各個(gè)方法里卵牍,手動(dòng)調(diào)一遍組件的對(duì)應(yīng)的方法,如果有就執(zhí)行沦泌。
殼工程
既然已經(jīng)拆出去了糊昙,那拆出去的組件總得有個(gè)載體,這個(gè)載體就是殼工程谢谦,殼工程主要包含一些基礎(chǔ)組件和業(yè)務(wù)SDK释牺,這也是主工程包含的一些內(nèi)容,所以如果在殼工程可以正常運(yùn)行的話回挽,到了主工程也沒(méi)什么問(wèn)題没咙。不過(guò)這里存在版本同步問(wèn)題,之后會(huì)說(shuō)到千劈。
遇到的問(wèn)題
組件拆分
由于之前的代碼都是在一個(gè)工程下的祭刚,所以要單獨(dú)拿出來(lái)作為一個(gè)組件就會(huì)遇到不少問(wèn)題。首先是組件的劃分墙牌,當(dāng)時(shí)在定義組件粒度時(shí)也花了些時(shí)間討論涡驮,究竟是粒度粗點(diǎn)好,還是細(xì)點(diǎn)好喜滨。粗點(diǎn)的話比較有利于拆分捉捅,細(xì)點(diǎn)的話靈活度比較高。最終還是選擇粗一點(diǎn)的粒度虽风,先拆出來(lái)再說(shuō)棒口。
假如要把詳情頁(yè)遷出來(lái)寄月,就會(huì)發(fā)現(xiàn)它依賴(lài)了一些其他部分的代碼,那最快的方式就是直接把代碼拷過(guò)來(lái)无牵,改個(gè)名使用剥懒。比較簡(jiǎn)單暴力。說(shuō)起來(lái)比較簡(jiǎn)單合敦,做的時(shí)候也是挺有挑戰(zhàn)的初橘,因?yàn)檎5臉I(yè)務(wù)并不會(huì)因?yàn)椤附M件化」而停止,所以開(kāi)發(fā)同學(xué)們需要同時(shí)兼顧正常的業(yè)務(wù)和組件的拆分充岛。
版本管理
我們的組件包括第三方庫(kù)都是通過(guò) Cocoapods 來(lái)管理的保檐,其中組件使用了私有庫(kù)。之所以選擇 Cocoapods崔梗,一個(gè)是因?yàn)樗容^方便夜只,還有就是用戶基數(shù)比較大,且社區(qū)也比較活躍(活躍到了會(huì)時(shí)不時(shí)地觸發(fā) Github 的 rate limit蒜魄,導(dǎo)致長(zhǎng)時(shí)間 clone 不下來(lái)···見(jiàn)此)扔亥,當(dāng)然也有其他的管理方式,比如 submodule / subtree谈为,在開(kāi)發(fā)人員比較多的情況下旅挤,方便、靈活的方案容易占上風(fēng)伞鲫,雖然它也有自己的問(wèn)題粘茄。主要有版本同步和更新/編譯慢的問(wèn)題。
假如基礎(chǔ)組件做了個(gè) API 接口升級(jí)秕脓,這個(gè)升級(jí)會(huì)對(duì)原有的接口做改動(dòng)柒瓣,自然就會(huì)升一個(gè)中位的版本號(hào),比如原先是 1.6.19吠架,那么現(xiàn)在就變成 1.7.0 了芙贫。而我們?cè)?Podfile 里都是用~指定的,這樣就會(huì)出現(xiàn)主工程的 pod 版本升上去了傍药,但是殼工程沒(méi)有同步到磺平,然后群里就會(huì)各種反饋編譯不過(guò),而且這個(gè)編譯不過(guò)的長(zhǎng)尾有時(shí)能拖上兩三天怔檩。
然后我們就想了個(gè)辦法褪秀,如果不在殼工程里指定基礎(chǔ)庫(kù)的版本,只在主工程里指定呢薛训,理論上應(yīng)該可行,只要不出現(xiàn)某個(gè)基礎(chǔ)庫(kù)要同時(shí)維護(hù)多個(gè)版本的情況仑氛。但實(shí)踐中發(fā)現(xiàn)乙埃,殼工程有時(shí)會(huì)莫名其妙地升不上去闸英,在 podfile 里指定最新的版本又可以升上去,所以此路不通介袜。
還有一個(gè)問(wèn)題是pod update時(shí)間過(guò)長(zhǎng)甫何,經(jīng)常會(huì)在Analyzing Dependency上卡 10 多分鐘,非常影響效率遇伞。后來(lái)排查下來(lái)是跟組件的 Podspec 有關(guān)辙喂,配置了 subspec,且依賴(lài)比較多鸠珠。
然后就是 pod update 之后的編譯巍耗,由于是源碼編譯,所以這塊的時(shí)間花費(fèi)也不少渐排,接下去會(huì)考慮 framework 的方式炬太。
持續(xù)集成
在剛開(kāi)始,持續(xù)集成還不是很完善驯耻,業(yè)務(wù)方升級(jí)組件亲族,直接把 podspec 扔到 private repo 里就完事了。這樣最簡(jiǎn)單可缚,但也經(jīng)常會(huì)帶來(lái)編譯通不過(guò)的問(wèn)題霎迫。而且這種隨意的版本升級(jí)也不太能保證質(zhì)量。于是我們就搭建了一套持續(xù)集成系統(tǒng)帘靡,大概如此
每個(gè)組件升級(jí)之前都需要先通過(guò)編譯女气,然后再?zèng)Q定是否升級(jí)。這套體系看起來(lái)不復(fù)雜测柠,但在實(shí)施過(guò)程中經(jīng)常會(huì)遇到后端的并發(fā)問(wèn)題炼鞠,導(dǎo)致業(yè)務(wù)方要么集成失敗,要么要等不少時(shí)間轰胁。而且也沒(méi)有一個(gè)地方可以呈現(xiàn)當(dāng)前版本的組件版本信息谒主。還有就是業(yè)務(wù)方對(duì)于這種命令行的升級(jí)方式接受度也不是很高。
基于此赃阀,在經(jīng)過(guò)了幾輪討論之后霎肯,有了新版的持續(xù)集成平臺(tái),升級(jí)操作通過(guò)網(wǎng)頁(yè)端來(lái)完成榛斯。
大致思路是观游,業(yè)務(wù)方如果要升級(jí)組件,假設(shè)現(xiàn)在的版本是 0.1.7驮俗,添加了一些 feature 之后懂缕,殼工程測(cè)試通過(guò),想集成到主工程里看看效果王凑,或者其他組件也想引用這個(gè)最新的搪柑,就可以在后臺(tái)手動(dòng)把版本升到 0.1.8-rc.1聋丝,這樣的話,原先依賴(lài)~> 0.1.7的組件工碾,不會(huì)升到 0.1.8弱睦,同時(shí)想要測(cè)試這個(gè)組件的話,只要手動(dòng)把版本調(diào)到 0.1.8-rc.1 就可以了渊额。這個(gè)過(guò)程不會(huì)觸發(fā) CI 的編譯檢查况木。
當(dāng)測(cè)試通過(guò)后,就可以把尾部的-rc.n去掉旬迹,然后點(diǎn)擊「集成」火惊,就會(huì)走 CI 編譯檢查,通過(guò)的話舱权,會(huì)在主工程的 podfile 里寫(xiě)上固定的版本號(hào) 0.1.8矗晃。也就是說(shuō),podfile 里所有的組件版本號(hào)都是固定的宴倍。
周邊設(shè)施
基礎(chǔ)組件及組件的文檔 / Demo / 單元測(cè)試
無(wú)線基礎(chǔ)的職能是為集團(tuán)提供解決方案张症,只是在蘑菇街 App 里能 work 是遠(yuǎn)遠(yuǎn)不夠的,所以就需要提供入口鸵贬,知道有哪些可用組件俗他,并且如何使用,就像這樣(目前還未實(shí)現(xiàn))
這就要求組件的負(fù)責(zé)人需要及時(shí)地更新 README / CHANGELOG / API阔逼,并且當(dāng)發(fā)生 API 變更時(shí)兆衅,能夠快速通知到使用方。
公共 UI 組件
組件化之后還有一個(gè)問(wèn)題就是資源的重復(fù)性嗜浮,以前在一個(gè)工程里的時(shí)候羡亩,資源都可以很方便地拿到,現(xiàn)在獨(dú)立出去了危融,也不知道哪些是公用的畏铆,哪些是獨(dú)有的,索性都放到自己的組件里吉殃,這樣就會(huì)導(dǎo)致包變大辞居。還有一個(gè)問(wèn)題是每個(gè)組件可能是不同的產(chǎn)品經(jīng)理在跟,而他們很可能只關(guān)注于自己關(guān)心的頁(yè)面長(zhǎng)什么樣蛋勺,而忽略了整體的樣式瓦灶。公共
UI 組件就是用來(lái)解決這些問(wèn)題的,這些組件甚至可以跨 App 使用抱完。(目前還未實(shí)現(xiàn))
參考答案一:http://blog.csdn.net/GGGHub/article/details/52713642
參考答案二:http://limboy.me/tech/2016/03/10/mgj-components.html
3.runtime如何通過(guò)selector找到對(duì)應(yīng)的IMP地址贼陶?
概述
類(lèi)對(duì)象中有類(lèi)方法和實(shí)例方法的列表,列表中記錄著方法的名詞、參數(shù)和實(shí)現(xiàn)每界,而selector本質(zhì)就是方法名稱(chēng)捅僵,runtime通過(guò)這個(gè)方法名稱(chēng)就可以在列表中找到該方法對(duì)應(yīng)的實(shí)現(xiàn)家卖。
這里聲明了一個(gè)指向struct objc_method_list指針的指針眨层,可以包含類(lèi)方法列表和實(shí)例方法列表
具體實(shí)現(xiàn)
在尋找IMP的地址時(shí),runtime提供了兩種方法
IMP class_getMethodImplementation(Class cls, SEL name);IMP method_getImplementation(Method m)
而根據(jù)官方描述上荡,第一種方法可能會(huì)更快一些
@note \c class_getMethodImplementation may be faster than \c method_getImplementation(class_getInstanceMethod(cls, name)).
對(duì)于第一種方法而言趴樱,類(lèi)方法和實(shí)例方法實(shí)際上都是通過(guò)調(diào)用class_getMethodImplementation()來(lái)尋找IMP地址的,不同之處在于傳入的第一個(gè)參數(shù)不同
類(lèi)方法(假設(shè)有一個(gè)類(lèi)A)
class_getMethodImplementation(objc_getMetaClass("A"),@selector(methodName));
實(shí)例方法
class_getMethodImplementation([A class],@selector(methodName));
通過(guò)該傳入的參數(shù)不同酪捡,找到不同的方法列表叁征,方法列表中保存著下面方法的結(jié)構(gòu)體,結(jié)構(gòu)體中包含這方法的實(shí)現(xiàn)逛薇,selector本質(zhì)就是方法的名稱(chēng)捺疼,通過(guò)該方法名稱(chēng),即可在結(jié)構(gòu)體中找到相應(yīng)的實(shí)現(xiàn)永罚。
struct objc_method {SEL method_namechar *method_typesIMP method_imp}
而對(duì)于第二種方法而言啤呼,傳入的參數(shù)只有method,區(qū)分類(lèi)方法和實(shí)例方法在于封裝method的函數(shù)
類(lèi)方法
Method class_getClassMethod(Class cls, SEL name)
實(shí)例方法
Method class_getInstanceMethod(Class cls, SEL name)
最后調(diào)用IMP method_getImplementation(Method m)獲取IMP地址
實(shí)驗(yàn)
這里有一個(gè)叫Test的類(lèi)呢袱,在初始化方法里官扣,調(diào)用了兩次getIMPFromSelector:方法,第一個(gè)aaa方法是不存在的羞福,test1和test2分別為實(shí)例方法和類(lèi)方法
然后我同時(shí)實(shí)例化了兩個(gè)Test的對(duì)象惕蹄,打印信息如下
大家注意圖中紅色標(biāo)注的地址出現(xiàn)了8次:0x1102db280,這個(gè)是在調(diào)用class_getMethodImplementation()方法時(shí)治专,無(wú)法找到對(duì)應(yīng)實(shí)現(xiàn)時(shí)返回的相同的一個(gè)地址卖陵,無(wú)論該方法是在實(shí)例方法或類(lèi)方法,無(wú)論是否對(duì)一個(gè)實(shí)例調(diào)用該方法张峰,返回的地址都是相同的泪蔫,但是每次運(yùn)行該程序時(shí)返回的地址并不相同,而對(duì)于另一種方法挟炬,如果找不到對(duì)應(yīng)的實(shí)現(xiàn)鸥滨,則返回0,在圖中我做了藍(lán)色標(biāo)記谤祖。
還有一點(diǎn)有趣的是class_getClassMethod()的第一個(gè)參數(shù)無(wú)論傳入objc_getClass()還是objc_getMetaClass()婿滓,最終調(diào)用method_getImplementation()都可以成功的找到類(lèi)方法的實(shí)現(xiàn)。
而class_getInstanceMethod()的第一個(gè)參數(shù)如果傳入objc_getMetaClass()粥喜,再調(diào)用method_getImplementation()時(shí)無(wú)法找到實(shí)例方法的實(shí)現(xiàn)卻可以找到類(lèi)方法的實(shí)現(xiàn)凸主。
4.runloop內(nèi)部實(shí)現(xiàn)邏輯?
蘋(píng)果在文檔里的說(shuō)明额湘,RunLoop 內(nèi)部的邏輯大致如下:
其內(nèi)部代碼整理如下 :
可以看到卿吐,實(shí)際上 RunLoop 就是這樣一個(gè)函數(shù)旁舰,其內(nèi)部是一個(gè) do-while 循環(huán)。當(dāng)你調(diào)用 CFRunLoopRun() 時(shí)嗡官,線程就會(huì)一直停留在這個(gè)循環(huán)里箭窜;直到超時(shí)或被手動(dòng)停止,該函數(shù)才會(huì)返回衍腥。
RunLoop 的底層實(shí)現(xiàn)
從上面代碼可以看到磺樱,RunLoop 的核心是基于 mach port 的,其進(jìn)入休眠時(shí)調(diào)用的函數(shù)是 mach_msg()婆咸。為了解釋這個(gè)邏輯竹捉,下面稍微介紹一下 OSX/iOS 的系統(tǒng)架構(gòu)。
蘋(píng)果官方將整個(gè)系統(tǒng)大致劃分為上述4個(gè)層次:
應(yīng)用層包括用戶能接觸到的圖形應(yīng)用尚骄,例如 Spotlight块差、Aqua、SpringBoard 等倔丈。
應(yīng)用框架層即開(kāi)發(fā)人員接觸到的 Cocoa 等框架憨闰。
核心框架層包括各種核心框架、OpenGL 等內(nèi)容乃沙。
Darwin 即操作系統(tǒng)的核心起趾,包括系統(tǒng)內(nèi)核、驅(qū)動(dòng)警儒、Shell 等內(nèi)容训裆,這一層是開(kāi)源的,其所有源碼都可以在opensource.apple.com里找到蜀铲。
我們?cè)谏钊肟匆幌?Darwin 這個(gè)核心的架構(gòu):
其中边琉,在硬件層上面的三個(gè)組成部分:Mach、BSD记劝、IOKit (還包括一些上面沒(méi)標(biāo)注的內(nèi)容)变姨,共同組成了 XNU 內(nèi)核。
XNU 內(nèi)核的內(nèi)環(huán)被稱(chēng)作 Mach厌丑,其作為一個(gè)微內(nèi)核定欧,僅提供了諸如處理器調(diào)度、IPC (進(jìn)程間通信)等非常少量的基礎(chǔ)服務(wù)怒竿。
BSD 層可以看作圍繞 Mach 層的一個(gè)外環(huán)砍鸠,其提供了諸如進(jìn)程管理、文件系統(tǒng)和網(wǎng)絡(luò)等功能耕驰。
IOKit 層是為設(shè)備驅(qū)動(dòng)提供了一個(gè)面向?qū)ο?C++)的一個(gè)框架爷辱。
Mach
本身提供的 API 非常有限,而且蘋(píng)果也不鼓勵(lì)使用 Mach 的
API,但是這些API非撤构基礎(chǔ)双饥,如果沒(méi)有這些API的話,其他任何工作都無(wú)法實(shí)施弟断。在 Mach
中咏花,所有的東西都是通過(guò)自己的對(duì)象實(shí)現(xiàn)的,進(jìn)程夫嗓、線程和虛擬內(nèi)存都被稱(chēng)為"對(duì)象"迟螺。和其他架構(gòu)不同冲秽, Mach
的對(duì)象間不能直接調(diào)用舍咖,只能通過(guò)消息傳遞的方式實(shí)現(xiàn)對(duì)象間的通信。"消息"是 Mach 中最基礎(chǔ)的概念锉桑,消息在兩個(gè)端口 (port)
之間傳遞排霉,這就是 Mach 的 IPC (進(jìn)程間通信) 的核心。
Mach 的消息定義是在頭文件的民轴,很簡(jiǎn)單:
typedef struct {
mach_msg_header_t header;
mach_msg_body_t body;
} mach_msg_base_t;
typedef struct {
mach_msg_bits_t msgh_bits;
mach_msg_size_t msgh_size;
mach_port_t msgh_remote_port;
mach_port_t msgh_local_port;
mach_port_name_t msgh_voucher_port;
mach_msg_id_t msgh_id;
} mach_msg_header_t;
一條 Mach 消息實(shí)際上就是一個(gè)二進(jìn)制數(shù)據(jù)包 (BLOB)攻柠,其頭部定義了當(dāng)前端口 local_port 和目標(biāo)端口 remote_port,
發(fā)送和接受消息是通過(guò)同一個(gè) API 進(jìn)行的后裸,其 option 標(biāo)記了消息傳遞的方向:
mach_msg_return_t mach_msg(
mach_msg_header_t *msg,
mach_msg_option_t option,
mach_msg_size_t send_size,
mach_msg_size_t rcv_size,
mach_port_name_t rcv_name,
mach_msg_timeout_t timeout,
mach_port_name_t notify);
為了實(shí)現(xiàn)消息的發(fā)送和接收瑰钮,mach_msg()
函數(shù)實(shí)際上是調(diào)用了一個(gè) Mach 陷阱 (trap),即函數(shù)mach_msg_trap()微驶,陷阱這個(gè)概念在 Mach
中等同于系統(tǒng)調(diào)用浪谴。當(dāng)你在用戶態(tài)調(diào)用 mach_msg_trap() 時(shí)會(huì)觸發(fā)陷阱機(jī)制,切換到內(nèi)核態(tài)因苹;內(nèi)核態(tài)中內(nèi)核實(shí)現(xiàn)的 mach_msg()
函數(shù)會(huì)完成實(shí)際的工作苟耻,如下圖:
這些概念可以參考維基百科:System_call、Trap_(computing)扶檐。
RunLoop
的核心就是一個(gè) mach_msg() (見(jiàn)上面代碼的第7步)凶杖,RunLoop 調(diào)用這個(gè)函數(shù)去接收消息,如果沒(méi)有別人發(fā)送 port
消息過(guò)來(lái)款筑,內(nèi)核會(huì)將線程置于等待狀態(tài)智蝠。例如你在模擬器里跑起一個(gè) iOS 的 App,然后在 App 靜止時(shí)點(diǎn)擊暫停奈梳,你會(huì)看到主線程調(diào)用棧是停留在
mach_msg_trap() 這個(gè)地方杈湾。
關(guān)于具體的如何利用 mach port 發(fā)送信息,可以看看NSHipster 這一篇文章颈嚼,或者這里的中文翻譯 毛秘。
關(guān)于Mach的歷史可以看看這篇很有趣的文章:Mac OS X 背后的故事(三)Mach 之父 Avie Tevanian。
蘋(píng)果用 RunLoop 實(shí)現(xiàn)的功能
首先我們可以看一下 App 啟動(dòng)后 RunLoop 的狀態(tài):
可以看到,系統(tǒng)默認(rèn)注冊(cè)了5個(gè)Mode:
1. kCFRunLoopDefaultMode: App的默認(rèn) Mode叫挟,通常主線程是在這個(gè) Mode 下運(yùn)行的艰匙。
2. UITrackingRunLoopMode: 界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng)抹恳,保證界面滑動(dòng)時(shí)不受其他 Mode 影響员凝。
3. UIInitializationRunLoopMode: 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動(dòng)完成后就不再使用奋献。
4: GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode健霹,通常用不到。
5: kCFRunLoopCommonModes: 這是一個(gè)占位的 Mode瓶蚂,沒(méi)有實(shí)際作用糖埋。
你可以在這里看到更多的蘋(píng)果內(nèi)部的 Mode,但那些 Mode 在開(kāi)發(fā)中就很難遇到了窃这。
5.你理解的多線程瞳别?
1.可能會(huì)追問(wèn),每種多線程基于什么語(yǔ)言杭攻?
2.生命周期是如何管理祟敛?
3.你更傾向于哪種?追問(wèn)至現(xiàn)在常用的兩種你的看法是兆解?
第一種:pthread
.特點(diǎn):
1)一套通用的多線程API
2)適用于Unix\Linux\Windows等系統(tǒng)
3)跨平臺(tái)\可移植
4)使用難度大
b.使用語(yǔ)言:c語(yǔ)言
c.使用頻率:幾乎不用
d.線程生命周期:由程序員進(jìn)行管理
第二種:NSThread
a.特點(diǎn):
1)使用更加面向?qū)ο?/p>
2)簡(jiǎn)單易用馆铁,可直接操作線程對(duì)象
b.使用語(yǔ)言:OC語(yǔ)言
c.使用頻率:偶爾使用
d.線程生命周期:由程序員進(jìn)行管理
第三種:GCD
a.特點(diǎn):
1)旨在替代NSThread等線程技術(shù)
2)充分利用設(shè)備的多核(自動(dòng))
b.使用語(yǔ)言:C語(yǔ)言
c.使用頻率:經(jīng)常使用
d.線程生命周期:自動(dòng)管理
第四種:NSOperation
a.特點(diǎn):
1)基于GCD(底層是GCD)
2)比GCD多了一些更簡(jiǎn)單實(shí)用的功能
3)使用更加面向?qū)ο?/p>
b.使用語(yǔ)言:OC語(yǔ)言
c.使用頻率:經(jīng)常使用
d.線程生命周期:自動(dòng)管理
多線程的原理
同一時(shí)間,CPU只能處理1條線程锅睛,只有1條線程在工作(執(zhí)行)
多線程并發(fā)(同時(shí))執(zhí)行埠巨,其實(shí)是CPU快速地在多條線程之間調(diào)度(切換)
如果CPU調(diào)度線程的時(shí)間足夠快,就造成了多線程并發(fā)執(zhí)行的假象
思考:如果線程非常非常多衣撬,會(huì)發(fā)生什么情況乖订?
CPU會(huì)在N多線程之間調(diào)度,CPU會(huì)累死具练,消耗大量的CPU資源
每條線程被調(diào)度執(zhí)行的頻次會(huì)降低(線程的執(zhí)行效率降低)
多線程的優(yōu)點(diǎn)
能適當(dāng)提高程序的執(zhí)行效率
能適當(dāng)提高資源利用率(CPU乍构、內(nèi)存利用率)
多線程的缺點(diǎn)
開(kāi)啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,主線程占用1M扛点,子線程占用512KB)哥遮,如果開(kāi)啟大量的線程,會(huì)占用大量的內(nèi)存空間陵究,降低程序的性能
線程越多眠饮,CPU在調(diào)度線程上的開(kāi)銷(xiāo)就越大
程序設(shè)計(jì)更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享
你更傾向于哪一種铜邮?
傾向于GCD:
GCD
技術(shù)是一個(gè)輕量的仪召,底層實(shí)現(xiàn)隱藏的神奇技術(shù)寨蹋,我們能夠通過(guò)GCD和block輕松實(shí)現(xiàn)多線程編程,有時(shí)候扔茅,GCD相比其他系統(tǒng)提供的多線程方法更加有效已旧,當(dāng)然此再,有時(shí)候GCD不是最佳選擇遮斥,另一個(gè)多線程編程的技術(shù)
NSOprationQueue 讓我們能夠?qū)⒑笈_(tái)線程以隊(duì)列方式依序執(zhí)行催束,并提供更多操作的入口校哎,這和 GCD 的實(shí)現(xiàn)有些類(lèi)似。
這種類(lèi)似不是一個(gè)巧合践樱,在早期庆猫,MacOX
與 iOS 的程序都普遍采用Operation
Queue來(lái)進(jìn)行編寫(xiě)后臺(tái)線程代碼棋蚌,而之后出現(xiàn)的GCD技術(shù)大體是依照前者的原則來(lái)實(shí)現(xiàn)的雅倒,而隨著GCD的普及璃诀,在iOS 4 與 MacOS X
10.6以后,Operation Queue的底層實(shí)現(xiàn)都是用GCD來(lái)實(shí)現(xiàn)的屯断。
那這兩者直接有什么區(qū)別呢文虏?
GCD是底層的C語(yǔ)言構(gòu)成的API,而NSOperationQueue及相關(guān)對(duì)象是Objc的對(duì)象殖演。在GCD中,在隊(duì)列中執(zhí)行的是由block構(gòu)成的任務(wù)年鸳,這是一個(gè)輕量級(jí)的數(shù)據(jù)結(jié)構(gòu)趴久;而Operation作為一個(gè)對(duì)象,為我們提供了更多的選擇搔确;
在NSOperationQueue中彼棍,我們可以隨時(shí)取消已經(jīng)設(shè)定要準(zhǔn)備執(zhí)行的任務(wù)(當(dāng)然,已經(jīng)開(kāi)始的任務(wù)就無(wú)法阻止了)膳算,而GCD沒(méi)法停止已經(jīng)加入queue的block(其實(shí)是有的座硕,但需要許多復(fù)雜的代碼);
NSOperation能夠方便地設(shè)置依賴(lài)關(guān)系涕蜂,我們可以讓一個(gè)Operation依賴(lài)于另一個(gè)Operation华匾,這樣的話盡管兩個(gè)Operation處于同一個(gè)并行隊(duì)列中,但前者會(huì)直到后者執(zhí)行完畢后再執(zhí)行机隙;
我們能將KVO應(yīng)用在NSOperation中蜘拉,可以監(jiān)聽(tīng)一個(gè)Operation是否完成或取消,這樣子能比GCD更加有效地掌控我們執(zhí)行的后臺(tái)任務(wù)有鹿;
在NSOperation中旭旭,我們能夠設(shè)置NSOperation的priority優(yōu)先級(jí),能夠使同一個(gè)并行隊(duì)列中的任務(wù)區(qū)分先后地執(zhí)行葱跋,而在GCD中持寄,我們只能區(qū)分不同任務(wù)隊(duì)列的優(yōu)先級(jí)源梭,如果要區(qū)分block任務(wù)的優(yōu)先級(jí),也需要大量的復(fù)雜代碼稍味;
我們能夠?qū)SOperation進(jìn)行繼承咸产,在這之上添加成員變量與成員方法,提高整個(gè)代碼的復(fù)用度仲闽,這比簡(jiǎn)單地將block任務(wù)排入執(zhí)行隊(duì)列更有自由度脑溢,能夠在其之上添加更多自定制的功能。
總的來(lái)說(shuō)赖欣,Operation
queue
提供了更多你在編寫(xiě)多線程程序時(shí)需要的功能屑彻,并隱藏了許多線程調(diào)度,線程取消與線程優(yōu)先級(jí)的復(fù)雜代碼顶吮,為我們提供簡(jiǎn)單的API入口社牲。從編程原則來(lái)說(shuō),一般我們需要盡可能的使用高等級(jí)悴了、封裝完美的API搏恤,在必須時(shí)才使用底層API。但是我認(rèn)為當(dāng)我們的需求能夠以更簡(jiǎn)單的底層代碼完成的時(shí)候湃交,簡(jiǎn)潔的GCD或許是個(gè)更好的選擇熟空,而Operation
queue 為我們提供能更多的選擇。
傾向于:NSOperation
NSOperation相對(duì)于GCD:
1搞莺,NSOperation擁有更多的函數(shù)可用息罗,具體查看api。NSOperationQueue 是在GCD基礎(chǔ)上實(shí)現(xiàn)的才沧,只不過(guò)是GCD更高一層的抽象迈喉。
2,在NSOperationQueue中温圆,可以建立各個(gè)NSOperation之間的依賴(lài)關(guān)系挨摸。
3,NSOperationQueue支持KVO岁歉〉迷耍可以監(jiān)測(cè)operation是否正在執(zhí)行(isExecuted)、是否結(jié)束(isFinished)刨裆,是否取消(isCanceld)
4澈圈,GCD 只支持FIFO 的隊(duì)列,而NSOperationQueue可以調(diào)整隊(duì)列的執(zhí)行順序(通過(guò)調(diào)整權(quán)重)帆啃。NSOperationQueue可以方便的管理并發(fā)瞬女、NSOperation之間的優(yōu)先級(jí)。
使用NSOperation的情況:各個(gè)操作之間有依賴(lài)關(guān)系努潘、操作需要取消暫停诽偷、并發(fā)管理坤学、控制操作之間優(yōu)先級(jí),限制同時(shí)能執(zhí)行的線程數(shù)量.讓線程在某時(shí)刻停止/繼續(xù)等报慕。
使用GCD的情況:一般的需求很簡(jiǎn)單的多線程操作深浮,用GCD都可以了,簡(jiǎn)單高效眠冈。
從編程原則來(lái)說(shuō)飞苇,一般我們需要盡可能的使用高等級(jí)、封裝完美的API蜗顽,在必須時(shí)才使用底層API布卡。
當(dāng)需求簡(jiǎn)單,簡(jiǎn)潔的GCD或許是個(gè)更好的選擇雇盖,而Operation queue 為我們提供能更多的選擇忿等。
6.GCD執(zhí)行原理?
GCD有一個(gè)底層線程池崔挖,這個(gè)池中存放的是一個(gè)個(gè)的線程贸街。之所以稱(chēng)為“池”,很容易理解出這個(gè)“池”中的線程是可以重用的狸相,當(dāng)一段時(shí)間后這個(gè)線程沒(méi)有被調(diào)用胡話薛匪,這個(gè)線程就會(huì)被銷(xiāo)毀。注意:開(kāi)多少條線程是由底層線程池決定的(線程建議控制再3~5條)卷哩,池是系統(tǒng)自動(dòng)來(lái)維護(hù)蛋辈,不需要我們程序員來(lái)維護(hù)(看到這句話是不是很開(kāi)心?)
而我們程序員需要關(guān)心的是什么呢将谊?我們只關(guān)心的是向隊(duì)列中添加任務(wù),隊(duì)列調(diào)度即可渐白。
? 如果隊(duì)列中存放的是同步任務(wù),則任務(wù)出隊(duì)后栋齿,底層線程池中會(huì)提供一條線程供這個(gè)任務(wù)執(zhí)行歌亲,任務(wù)執(zhí)行完畢后這條線程再回到線程池杂穷。這樣隊(duì)列中的任務(wù)反復(fù)調(diào)度,因?yàn)槭峭降呢孕澹援?dāng)我們用currentThread打印的時(shí)候耐量,就是同一條線程。
?
如果隊(duì)列中存放的是異步的任務(wù)滤港,(注意異步可以開(kāi)線程)廊蜒,當(dāng)任務(wù)出隊(duì)后,底層線程池會(huì)提供一個(gè)線程供任務(wù)執(zhí)行溅漾,因?yàn)槭钱惒綀?zhí)行山叮,隊(duì)列中的任務(wù)不需等待當(dāng)前任務(wù)執(zhí)行完畢就可以調(diào)度下一個(gè)任務(wù),這時(shí)底層線程池中會(huì)再次提供一個(gè)線程供第二個(gè)任務(wù)執(zhí)行樟凄,執(zhí)行完畢后再回到底層線程池中聘芜。
?
這樣就對(duì)線程完成一個(gè)復(fù)用,而不需要每一個(gè)任務(wù)執(zhí)行都開(kāi)啟新的線程缝龄,也就從而節(jié)約的系統(tǒng)的開(kāi)銷(xiāo)汰现,提高了效率。在iOS7.0的時(shí)候叔壤,使用GCD系統(tǒng)通常只能開(kāi)58條線程瞎饲,iOS8.0以后,系統(tǒng)可以開(kāi)啟很多條線程炼绘,但是實(shí)在開(kāi)發(fā)應(yīng)用中嗅战,建議開(kāi)啟線程條數(shù):35條最為合理。
通過(guò)案例明白GCD的執(zhí)行原理
案例一:
分析:
首先執(zhí)行任務(wù)1俺亮,這是肯定沒(méi)問(wèn)題的驮捍,只是接下來(lái),程序遇到了同步線程脚曾,那么它會(huì)進(jìn)入等待东且,等待任務(wù)2執(zhí)行完,然后執(zhí)行任務(wù)3本讥。但這是隊(duì)列珊泳,有任務(wù)來(lái),當(dāng)然會(huì)將任務(wù)加到隊(duì)尾拷沸,然后遵循FIFO原則執(zhí)行任務(wù)色查。那么,現(xiàn)在任務(wù)2就會(huì)被加到最后撞芍,任務(wù)3排在了任務(wù)2前面秧了,問(wèn)題來(lái)了:
任務(wù)3要等任務(wù)2執(zhí)行完才能執(zhí)行,任務(wù)2又排在任務(wù)3后面勤庐,意味著任務(wù)2要在任務(wù)3執(zhí)行完才能執(zhí)行示惊,所以他們進(jìn)入了互相等待的局面好港。【既然這樣米罚,那干脆就卡在這里吧】這就是死鎖钧汹。
案例二:
分析:
首先執(zhí)行任務(wù)1,接下來(lái)會(huì)遇到一個(gè)同步線程录择,程序會(huì)進(jìn)入等待拔莱。等待任務(wù)2執(zhí)行完成以后,才能繼續(xù)執(zhí)行任務(wù)3隘竭。從dispatch_get_global_queue可以看出塘秦,任務(wù)2被加入到了全局的并行隊(duì)列中,當(dāng)并行隊(duì)列執(zhí)行完任務(wù)2以后动看,返回到主隊(duì)列尊剔,繼續(xù)執(zhí)行任務(wù)3。
案例三:
案例四:
分析:
首先菱皆,將【任務(wù)1须误、異步線程、任務(wù)5】加入Main
Queue中仇轻,異步線程中的任務(wù)是:【任務(wù)2京痢、同步線程、任務(wù)4】篷店。所以祭椰,先執(zhí)行任務(wù)1,然后將異步線程中的任務(wù)加入到Global
Queue中疲陕,因?yàn)楫惒骄€程方淤,所以任務(wù)5不用等待,結(jié)果就是2和5的輸出順序不一定蹄殃。然后再看異步線程中的任務(wù)執(zhí)行順序臣淤。任務(wù)2執(zhí)行完以后,遇到同步線程窃爷。將同步線程中的任務(wù)加入到Main
Queue中,這時(shí)加入的任務(wù)3在任務(wù)5的后面姓蜂。當(dāng)任務(wù)3執(zhí)行完以后按厘,沒(méi)有了阻塞,程序繼續(xù)執(zhí)行任務(wù)4钱慢。
案例五:
分析:
和上面幾個(gè)案例的分析類(lèi)似逮京,先來(lái)看看都有哪些任務(wù)加入了Main Queue:
【異步線程、任務(wù)4束莫、死循環(huán)懒棉、任務(wù)5】草描。
在加入到Global Queue異步線程中的任務(wù)有:
【任務(wù)1、同步線程策严、任務(wù)3】穗慕。第一個(gè)就是異步線程,任務(wù)4不用等待妻导,
所以結(jié)果任務(wù)1和任務(wù)4順序不一定逛绵。任務(wù)4完成后,程序進(jìn)入死循環(huán)倔韭,
Main Queue阻塞术浪。但是加入到Global Queue的異步線程不受影響,
繼續(xù)執(zhí)行任務(wù)1后面的同步線程寿酌。同步線程中胰苏,將任務(wù)2加入到了主線程,
并且醇疼,任務(wù)3等待任務(wù)2完成以后才能執(zhí)行硕并。這時(shí)的主線程,已經(jīng)被死循環(huán)阻塞了僵腺。
所以任務(wù)2無(wú)法執(zhí)行鲤孵,當(dāng)然任務(wù)3也無(wú)法執(zhí)行,在死循環(huán)后的任務(wù)5也不會(huì)執(zhí)行辰如。
7.怎么防止別人動(dòng)態(tài)在你程序生成代碼普监?
(這題是聽(tīng)錯(cuò)了面試官的意思)
面試官意思是怎么防止別人反編譯你的app?
1.本地?cái)?shù)據(jù)加密
iOS應(yīng)用防反編譯加密技術(shù)之一:對(duì)NSUserDefaults琉兜,sqlite存儲(chǔ)文件數(shù)據(jù)加密凯正,保護(hù)帳號(hào)和關(guān)鍵信息
2.URL編碼加密
iOS應(yīng)用防反編譯加密技術(shù)之二:對(duì)程序中出現(xiàn)的URL進(jìn)行編碼加密,防止URL被靜態(tài)分析
3.網(wǎng)絡(luò)傳輸數(shù)據(jù)加密
iOS應(yīng)用防反編譯加密技術(shù)之三:對(duì)客戶端傳輸數(shù)據(jù)提供加密方案豌蟋,有效防止通過(guò)網(wǎng)絡(luò)接口的攔截獲取數(shù)據(jù)
4.方法體廊散,方法名高級(jí)混淆
iOS應(yīng)用防反編譯加密技術(shù)之四:對(duì)應(yīng)用程序的方法名和方法體進(jìn)行混淆,保證源碼被逆向后無(wú)法解析代碼
5.程序結(jié)構(gòu)混排加密
iOS應(yīng)用防反編譯加密技術(shù)之五:對(duì)應(yīng)用程序邏輯結(jié)構(gòu)進(jìn)行打亂混排梧疲,保證源碼可讀性降到最低
6.借助第三方APP加固允睹,例如:網(wǎng)易云易盾
8.YYAsyncLayer如何異步繪制?
YYAsyncLayer是異步繪制與顯示的工具幌氮。為了保證列表滾動(dòng)流暢缭受,將視圖繪制、以及圖片解碼等任務(wù)放到后臺(tái)線程该互,
YYKitDemo
對(duì)于列表主要對(duì)兩個(gè)代理方法的優(yōu)化米者,一個(gè)與繪制顯示有關(guān),另一個(gè)與計(jì)算布局有關(guān):
Objective-C
1
2-(UITableViewCell)tableView:(UITableView)tableViewcellForRowAtIndexPath:(NSIndexPath*)indexPath;
-(CGFloat)tableView:(UITableView)tableViewheightForRowAtIndexPath:(NSIndexPath)indexPath;
常規(guī)邏輯可能覺(jué)得應(yīng)該先調(diào)用tableView : cellForRowAtIndexPath :返回UITableViewCell對(duì)象,事實(shí)上調(diào)用順序是先返回UITableViewCell的高度蔓搞,是因?yàn)閁ITableView繼承自UIScrollView胰丁,滑動(dòng)范圍由屬性contentSize來(lái)確定,UITableView的滑動(dòng)范圍需要通過(guò)每一行的UITableViewCell的高度計(jì)算確定喂分,復(fù)雜cell如果在列表滾動(dòng)過(guò)程中計(jì)算可能會(huì)造成一定程度的卡頓锦庸。
假設(shè)有20條數(shù)據(jù),當(dāng)前屏幕顯示5條妻顶,tableView : heightForRowAtIndexPath :方法會(huì)先執(zhí)行20次返回所有高度并計(jì)算出滑動(dòng)范圍酸员,tableView : cellForRowAtIndexPath :執(zhí)行5次返回當(dāng)前屏幕顯示的cell個(gè)數(shù)。
從圖中簡(jiǎn)單看下流程讳嘱,從網(wǎng)絡(luò)請(qǐng)求返回JSON數(shù)據(jù)幔嗦,將Cell的高度以及內(nèi)部視圖的布局封裝為L(zhǎng)ayout對(duì)象,Cell顯示之前在異步線程計(jì)算好所有布局對(duì)象沥潭,并存入數(shù)組邀泉,每次調(diào)用tableView: heightForRowAtIndexPath :只需要從數(shù)組中取出,可避免重復(fù)的布局計(jì)算钝鸽。同時(shí)在調(diào)用tableView: cellForRowAtIndexPath :對(duì)Cell內(nèi)部視圖異步繪制布局汇恤,以及圖片的異步繪制解碼,這里就要說(shuō)到今天的主角YYAsyncLayer拔恰。
YYAsyncLayer
首先介紹里面幾個(gè)類(lèi):
YYAsyncLayer:繼承自CALayer因谎,繪制、創(chuàng)建繪制線程的部分都在這個(gè)類(lèi)颜懊。
YYTransaction:用于創(chuàng)建RunloopObserver監(jiān)聽(tīng)MainRunloop的空閑時(shí)間财岔,并將YYTranaction對(duì)象存放到集合中。
YYSentinel:提供獲取當(dāng)前值的value(只讀)屬性河爹,以及- (int32_t)increase自增加的方法返回一個(gè)新的value值匠璧,用于判斷異步繪制任務(wù)是否被取消的工具。
AsyncDisplay.png
上圖是整體異步繪制的實(shí)現(xiàn)思路咸这,后面一步步說(shuō)明∫幕校現(xiàn)在假設(shè)需要繪制Label,其實(shí)是繼承自UIView媳维,重寫(xiě)+ (Class)layerClass酿雪,在需要重新繪制的地方調(diào)用下面方法,比如setter侄刽,layoutSubviews执虹。
Objective-C
+(Class)layerClass{
returnYYAsyncLayer.class;
}
-(void)setText:(NSString*)text{
_text=text.copy;
[[YYTransactiontransactionWithTarget:selfselector:@selector(contentsNeedUpdated)]commit];
}
-(void)layoutSubviews{
[superlayoutSubviews];
[[YYTransactiontransactionWithTarget:selfselector:@selector(contentsNeedUpdated)]commit];
}
YYTransaction有selector、target的屬性唠梨,selector其實(shí)就是contentsNeedUpdated方法,此時(shí)并不會(huì)立即在后臺(tái)線程去更新顯示侥啤,而是將YYTransaction對(duì)象本身提交保存在transactionSet的集合中当叭,上圖中所示茬故。
Objective-C
+(YYTransaction*)transactionWithTarget:(id)targetselector:(SEL)selector{
if(!target||!selector)returnnil;
YYTransaction*t=[YYTransactionnew];
t.target=target;
t.selector=selector;
returnt;
}
-(void)commit{
if(!_target||!_selector)return;
YYTransactionSetup();
[transactionSetaddObject:self];
}
同時(shí)在YYTransaction.m中注冊(cè)一個(gè)RunloopObserver,監(jiān)聽(tīng)MainRunloop在kCFRunLoopCommonModes(包含kCFRunLoopDefaultMode蚁鳖、UITrackingRunLoopMode)下的kCFRunLoopBeforeWaiting和kCFRunLoopExit的狀態(tài)磺芭,也就是說(shuō)在一次Runloop空閑時(shí)去執(zhí)行更新顯示的操作。
kCFRunLoopBeforeWaiting:Runloop將要進(jìn)入休眠醉箕。
kCFRunLoopExit:即將退出本次Runloop钾腺。
Objective-C
staticvoidYYTransactionSetup(){
staticdispatch_once_tonceToken;
dispatch_once(&onceToken,^{
transactionSet=[NSMutableSetnew];
CFRunLoopRefrunloop=CFRunLoopGetMain();
CFRunLoopObserverRefobserver;
observer=CFRunLoopObserverCreate(CFAllocatorGetDefault(),
kCFRunLoopBeforeWaiting|kCFRunLoopExit,
true,// repeat
0xFFFFFF,// after CATransaction(2000000)
YYRunLoopObserverCallBack,NULL);
CFRunLoopAddObserver(runloop,observer,kCFRunLoopCommonModes);
CFRelease(observer);
});
}
下面是RunloopObserver的回調(diào)方法,從transactionSet取出transaction對(duì)象執(zhí)行SEL的方法讥裤,分發(fā)到每一次Runloop執(zhí)行放棒,避免一次Runloop執(zhí)行時(shí)間太長(zhǎng)。
Objective-C
staticvoidYYRunLoopObserverCallBack(CFRunLoopObserverRefobserver,CFRunLoopActivityactivity,void*info){
if(transactionSet.count==0)return;
NSSet*currentSet=transactionSet;
transactionSet=[NSMutableSetnew];
[currentSetenumerateObjectsUsingBlock:^(YYTransactiontransaction,BOOLstop){
pragma clang diagnostic push
pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[transaction.targetperformSelector:transaction.selector];
pragma clang diagnostic pop
}];
}
接下來(lái)是異步繪制己英,這里用了一個(gè)比較巧妙的方法處理间螟,當(dāng)使用GCD時(shí)提交大量并發(fā)任務(wù)到后臺(tái)線程導(dǎo)致線程被鎖住、休眠的情況损肛,創(chuàng)建與程序當(dāng)前激活CPU數(shù)量(activeProcessorCount)相同的串行隊(duì)列厢破,并限制MAX_QUEUE_COUNT,將隊(duì)列存放在數(shù)組中治拿。
YYAsyncLayer.m有一個(gè)方法YYAsyncLayerGetDisplayQueue來(lái)獲取這個(gè)隊(duì)列用于繪制(這部分YYKit中有獨(dú)立的工具YYDispatchQueuePool)摩泪。創(chuàng)建隊(duì)列中有一個(gè)參數(shù)是告訴隊(duì)列執(zhí)行任務(wù)的服務(wù)質(zhì)量quality of service,在iOS8+之后相比之前系統(tǒng)有所不同劫谅。
iOS8之前隊(duì)列優(yōu)先級(jí):
DISPATCH_QUEUE_PRIORITY_HIGH 2高優(yōu)先級(jí)
DISPATCH_QUEUE_PRIORITY_DEFAULT 0默認(rèn)優(yōu)先級(jí)
DISPATCH_QUEUE_PRIORITY_LOW (-2)低優(yōu)先級(jí)
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN后臺(tái)優(yōu)先級(jí)
iOS8+之后:
QOS_CLASS_USER_INTERACTIVE 0x21, 用戶交互(希望盡快完成见坑,不要放太耗時(shí)操作)
QOS_CLASS_USER_INITIATED 0x19, 用戶期望(不要放太耗時(shí)操作)
QOS_CLASS_DEFAULT 0x15, 默認(rèn)(用來(lái)重置對(duì)列使用的)
QOS_CLASS_UTILITY 0x11, 實(shí)用工具(耗時(shí)操作,可以使用這個(gè)選項(xiàng))
QOS_CLASS_BACKGROUND 0x09, 后臺(tái)
QOS_CLASS_UNSPECIFIED 0x00, 未指定
Objective-C
/// Global display queue, used for content rendering.
staticdispatch_queue_tYYAsyncLayerGetDisplayQueue(){
ifdef YYDispatchQueuePool_h
returnYYDispatchQueueGetForQOS(NSQualityOfServiceUserInitiated);
else
define MAX_QUEUE_COUNT 16
staticintqueueCount;
staticdispatch_queue_tqueues[MAX_QUEUE_COUNT];//存放隊(duì)列的數(shù)組
staticdispatch_once_tonceToken;
staticint32_tcounter=0;
dispatch_once(&onceToken,^{
//程序激活的處理器數(shù)量
queueCount=(int)[NSProcessInfoprocessInfo].activeProcessorCount;
queueCount=queueCountMAX_QUEUE_COUNT?MAX_QUEUE_COUNT: queueCount);
if([UIDevicecurrentDevice].systemVersion.floatValue>=8.0){
for(NSUIntegeri=0;i
接下來(lái)是關(guān)于繪制部分的代碼同波,對(duì)外接口YYAsyncLayerDelegate代理中提供- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask方法用于回調(diào)繪制的代碼鳄梅,以及是否異步繪制的BOOl類(lèi)型屬性displaysAsynchronously,同時(shí)重寫(xiě)CALayer的display方法來(lái)調(diào)用繪制的方法- (void)_displayAsync:(BOOL)async未檩。
這里有必要了解關(guān)于后臺(tái)的繪制任務(wù)何時(shí)會(huì)被取消戴尸,下面兩種情況需要取消,并調(diào)用了YYSentinel的increase方法冤狡,使value值增加(線程安全):
在視圖調(diào)用setNeedsDisplay時(shí)說(shuō)明視圖的內(nèi)容需要被更新孙蒙,將當(dāng)前的繪制任務(wù)取消,需要重新顯示悲雳。
以及視圖被釋放調(diào)用了dealloc方法挎峦。
在YYAsyncLayer.h中定義了YYAsyncLayerDisplayTask類(lèi),有三個(gè)block屬性用于繪制的回調(diào)操作合瓢,從命名可以看出分別是將要繪制坦胶,正在繪制,以及繪制完成的回調(diào),可以從block傳入的參數(shù)BOOL(^isCancelled)(void)判斷當(dāng)前繪制是否被取消顿苇。
Objective-C
@property(nullable,nonatomic,copy)void(^willDisplay)(CALayer*layer);
@property(nullable,nonatomic,copy)void(display)(CGContextRefcontext,CGSizesize,BOOL(isCancelled)(void));
@property(nullable,nonatomic,copy)void(^didDisplay)(CALayer*layer,BOOLfinished);
下面是部分- (void)_displayAsync:(BOOL)async繪制的代碼峭咒,主要是一些邏輯判斷以及繪制函數(shù),在異步執(zhí)行之前通過(guò)YYAsyncLayerGetDisplayQueue創(chuàng)建的隊(duì)列纪岁,這里通過(guò)YYSentinel判斷當(dāng)前的value是否等于之前的值凑队,如果不相等,說(shuō)明繪制任務(wù)被取消了幔翰,繪制過(guò)程會(huì)多次判斷是否取消漩氨,如果是則return,保證被取消的任務(wù)能及時(shí)退出遗增,如果繪制完畢則設(shè)置圖片到layer.contents叫惊。
Objective-C
if(async){//異步
if(task.willDisplay)task.willDisplay(self);
YYSentinel*sentinel=_sentinel;
int32_tvalue=sentinel.value;
NSLog(@" --- %d ---",value);
//判斷當(dāng)前計(jì)數(shù)是否等于之前計(jì)數(shù)
BOOL(isCancelled)()=BOOL(){
returnvalue!=sentinel.value;
};
CGSizesize=self.bounds.size;
BOOLopaque=self.opaque;
CGFloatscale=self.contentsScale;
CGColorRefbackgroundColor=(opaque&&self.backgroundColor)?CGColorRetain(self.backgroundColor): NULL;
if(size.width
9.優(yōu)化你是從哪幾方面著手?
一贡定、首頁(yè)啟動(dòng)速度
啟動(dòng)過(guò)程中做的事情越少越好(盡可能將多個(gè)接口合并)
不在UI線程上作耗時(shí)的操作(數(shù)據(jù)的處理在子線程進(jìn)行赋访,處理完通知主線程刷新節(jié)目)
在合適的時(shí)機(jī)開(kāi)始后臺(tái)任務(wù)(例如在用戶指引節(jié)目就可以開(kāi)始準(zhǔn)備加載的數(shù)據(jù))
盡量減小包的大小
優(yōu)化方法:
量化啟動(dòng)時(shí)間
啟動(dòng)速度模塊化
輔助工具(友盟,聽(tīng)云缓待,F(xiàn)lurry)
二蚓耽、頁(yè)面瀏覽速度
json的處理(iOS 自帶的NSJSONSerialization,Jsonkit旋炒,SBJson)
數(shù)據(jù)的分頁(yè)(后端數(shù)據(jù)多的話步悠,就要分頁(yè)返回,例如網(wǎng)易新聞瘫镇,或者 微博記錄)
數(shù)據(jù)壓縮(大數(shù)據(jù)也可以壓縮返回鼎兽,減少流量,加快反應(yīng)速度)
內(nèi)容緩存(例如網(wǎng)易新聞的最新新聞列表都是要緩存到本地铣除,從本地加載谚咬,可以緩存到內(nèi)存,或者數(shù)據(jù)庫(kù)尚粘,根據(jù)情況而定)
延時(shí)加載tab(比如app有5個(gè)tab择卦,可以先加載第一個(gè)要顯示的tab,其他的在顯示時(shí)候加載郎嫁,按需加載)
算法的優(yōu)化(核心算法的優(yōu)化秉继,例如有些app 有個(gè) 聯(lián)系人姓名用漢語(yǔ)拼音的首字母排序)
三、操作流暢度優(yōu)化:
Tableview 優(yōu)化(tableview cell的加載優(yōu)化)
ViewController加載優(yōu)化(不同view之間的跳轉(zhuǎn)泽铛,可以提前準(zhǔn)備好數(shù)據(jù))
四尚辑、數(shù)據(jù)庫(kù)的優(yōu)化:
數(shù)據(jù)庫(kù)設(shè)計(jì)上面的重構(gòu)
查詢語(yǔ)句的優(yōu)化
分庫(kù)分表(數(shù)據(jù)太多的時(shí)候,可以分不同的表或者庫(kù))
五盔腔、服務(wù)器端和客戶端的交互優(yōu)化:
客戶端盡量減少請(qǐng)求
服務(wù)端盡量做多的邏輯處理
服務(wù)器端和客戶端采取推拉結(jié)合的方式(可以利用一些同步機(jī)制)
通信協(xié)議的優(yōu)化杠茬。(減少報(bào)文的大性氯臁)
電量使用優(yōu)化(盡量不要使用后臺(tái)運(yùn)行)
六、非技術(shù)性能優(yōu)化
產(chǎn)品設(shè)計(jì)的邏輯性(產(chǎn)品的設(shè)計(jì)一定要符合邏輯澈蝙,或者邏輯盡量簡(jiǎn)單吓坚,否則會(huì)讓程序員抓狂,有時(shí)候用了好大力氣灯荧,才可以完成一個(gè)小小的邏輯設(shè)計(jì)問(wèn)題)
界面交互的規(guī)范(每個(gè)模塊的界面的交互盡量統(tǒng)一,符合操作習(xí)慣)
代碼規(guī)范(這個(gè)可以隱形帶來(lái)app 性能的提高盐杂,比如 用if else 還是switch 逗载,或者是用!還是 ==)
code review(堅(jiān)持code Review 持續(xù)重構(gòu)代碼链烈。減少代碼的邏輯復(fù)雜度)
日常交流(經(jīng)常分享一些代碼厉斟,或者邏輯處理中的坑)
以上問(wèn)題加參考答案,部分自己回答(群友回答)+網(wǎng)上博客參考强衡,回答的不好勿噴擦秽!
僅供學(xué)習(xí)使用! 謝謝漩勤!