iOS:阿里-p6-參考答案

1.介紹下內(nèi)存的幾大區(qū)域?
2.你是如何組件化解耦的?
3.runtime如何通過selector找到對應(yīng)的IMP地址
4.runloop內(nèi)部實現(xiàn)邏輯?
5.你理解的多線程?
6.GCD執(zhí)行原理?
7.怎么防止別人反編譯你的app?
8.YYAsyncLayer如何異步繪制?
9.優(yōu)化你是從哪幾方面著手?

================================================

1.介紹下內(nèi)存的幾大區(qū)域?

1.棧區(qū)(stack) 由編譯器自動分配并釋放萎馅,存放函數(shù)的參數(shù)值,局部變量等。棧是系統(tǒng)數(shù)據(jù)結(jié)構(gòu)皆撩,對應(yīng)線程/進程是唯一的。優(yōu)點是快速高效,缺點時有限制,數(shù)據(jù)不靈活寺滚。[先進后出]
棧空間分靜態(tài)分配 和動態(tài)分配兩種。



堆區(qū)(heap) 由程序員分配和釋放,如果程序員不釋放,程序結(jié)束時愕宋,可能會由操作系統(tǒng)回收 臼朗,比如在ios 中 alloc 都是存放在堆中绣否。

優(yōu)點是靈活方便,數(shù)據(jù)適應(yīng)面廣泛,但是效率有一定降低。

雖然程序結(jié)束時所有的數(shù)據(jù)空間都會被釋放回系統(tǒng),但是精確的申請內(nèi)存策幼,釋放內(nèi)存匹配是 良好程序的基本要素黍氮。

3.全局區(qū)(靜態(tài)區(qū)) (static) 全局變量和靜態(tài)變量的存儲是放在一起的捷枯,初始化的全局變量和靜態(tài)變量存放在一塊區(qū)域郁油,未初始化的全局變量和靜態(tài)變量在相鄰的另一塊區(qū)域苟径,程序結(jié)束后有系統(tǒng)釋放嚼吞。


4.文字常量區(qū) 存放常量字符串恩沽,程序結(jié)束后由系統(tǒng)釋放;
5.代碼區(qū) 存放函數(shù)的二進制代碼

大致如圖:

例子代碼:


可能被追問的問題一:
1.棧區(qū) (stack [st?k]): 由編譯器自動分配釋放

局部變量是保存在棧區(qū)的

方法調(diào)用的實參也是保存在棧區(qū)的

2.堆區(qū) (heap [hi?p]): 由程序員分配釋放渤闷,若程序員不釋放狼电,會出現(xiàn)內(nèi)存泄漏凸椿,賦值語句右側(cè) 使用 new 方法創(chuàng)建的對象咙崎,被創(chuàng)建對象的所有 成員變量!
3.BSS 段 : 程序結(jié)束后由系統(tǒng)釋放
4.數(shù)據(jù)段 : 程序結(jié)束后由系統(tǒng)釋放
5.代碼段:程序結(jié)束后由系統(tǒng)釋放
程序編譯鏈接 后的二進制可執(zhí)行代碼
可能被追問的問題二:
比如申請后的系統(tǒng)是如何響應(yīng)的跛璧?
  1. 棧:存儲每一個函數(shù)在執(zhí)行的時候都會向操作系統(tǒng)索要資源刹碾,棧區(qū)就是函數(shù)運行時的內(nèi)存色洞,棧區(qū)中的變量由編譯器負責分配和釋放锦针,內(nèi)存隨著函數(shù)的運行分配盯荤,隨著函數(shù)的結(jié)束而釋放宏粤,由系統(tǒng)自動完成芥玉。

注意:只要棧的剩余空間大于所申請空間揽涮,系統(tǒng)將為程序提供內(nèi)存,否則將報異常提示棧溢出。

  1. 堆:
    1. 首先應(yīng)該知道操作系統(tǒng)有一個記錄空閑內(nèi)存地址的鏈表。
    2.當系統(tǒng)收到程序的申請時,會遍歷該鏈表,尋找第一個空間大于所申請空間的堆結(jié)點,然后將該結(jié)點從空閑結(jié)點鏈表中刪除,并將該結(jié)點的空間分配給程序蟋字。
    3 .由于找到的堆結(jié)點的大小不一定正好等于申請的大小涂炎,系統(tǒng)會自動的將多余的那部分重新放入空閑鏈表中
可能被追問的問題三:
如:申請 的限制是怎樣的?
  1. 棧:棧是向低地址擴展的數(shù)據(jù)結(jié)構(gòu),是一塊連續(xù)的內(nèi)存的區(qū)域。是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預先規(guī)定好的烦感,棧的大小是2M(也有的說是1M肥荔,總之是一個編譯時就確定的常數(shù) ) ,如果申請的空間超過棧的剩余空間時怯晕,將提示overflow堵第。因此胀瞪,能從棧獲得的空間較小。
  2. 堆:堆是向高地址擴展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)是用鏈表來存儲的空閑內(nèi)存地址的,自然是不連續(xù)的,而鏈表的遍歷方向是由低地址向高地址橡疼。堆的大小受限于計算機系統(tǒng)中有效的虛擬內(nèi)存挪略。由此可見,堆獲得的空間比較靈活,也比較大。


棧:由系統(tǒng)自動分配,速度較快,不會產(chǎn)生內(nèi)存碎片

堆:是由alloc分配的內(nèi)存,速度比較慢,而且容易產(chǎn)生內(nèi)存碎片,不過用起來最方便

打個比喻來說:

使用棧就象我們?nèi)ワ堭^里吃飯,只管點菜(發(fā)出申請)郑口、付錢秉溉、和吃(使用)碗誉,吃飽了就走召嘶,不必理會切菜、洗菜等準備工作和洗碗哮缺、刷鍋等掃尾工作,他的好處是快捷尝苇,但是自由度小铛只。

使用堆就象是自己動手做喜歡吃的菜肴,比較麻煩糠溜,但是比較符合自己的口味格仲,而且自由度大。

2.你是如何組件化解耦的诵冒?

1.實現(xiàn)代碼的高內(nèi)聚低耦合,方便多人多團隊開發(fā)谊惭!

一般需要解耦的項目都會多多少少出現(xiàn)汽馋,一下幾個情況:

耦合比較嚴重(因為沒有明確的約束,「組件」間引用的現(xiàn)象會比較多)

2.容易出現(xiàn)沖突(尤其是使用 Xib圈盔,還有就是 Xcode Project豹芯,雖說有腳本可以改善)

3.業(yè)務(wù)方的開發(fā)效率不夠高(只關(guān)心自己的組件,卻要編譯整個項目驱敲,與其他不相干的代碼糅合在一起)

先來看下铁蹈,組件化之后的一個大概架構(gòu)


「組件化」顧名思義就是把一個大的 App 拆成一個個小的組件,相互之間不直接引用众眨。那如何做呢握牧?

組件間通信

以 iOS 為例,由于之前就是采用的 URL 跳轉(zhuǎn)模式娩梨,理論上頁面之間的跳轉(zhuǎn)只需 open 一個 URL 即可沿腰。所以對于一個組件來說,只要定義「支持哪些 URL」即可狈定,比如詳情頁颂龙,大概可以這么做的


首頁只需調(diào)用[MGJRouter openURL:@"mgj://detail?id=404"]就可以打開相應(yīng)的詳情頁习蓬。

那問題又來了,我怎么知道有哪些可用的 URL措嵌?為此躲叼,我們做了一個后臺專門來管理。


然后可以把這些短鏈生成不同平臺所需的文件企巢,iOS 平臺生成 .{h,m} 文件枫慷,Android 平臺生成 .java 文件,并注入到項目中包斑。這樣開發(fā)人員只需在項目中打開該文件就知道所有的可用 URL 了流礁。

目前還有一塊沒有做,就是參數(shù)這塊罗丰,雖然描述了短鏈神帅,但真想要生成完整的 URL,還需要知道如何傳參數(shù)萌抵,這個正在開發(fā)中找御。

還有一種情況會稍微麻煩點,就是「組件A」要調(diào)用「組件B」的某個方法绍填,比如在商品詳情頁要展示購物車的商品數(shù)量霎桅,就涉及到向購物車組件拿數(shù)據(jù)。

類似這種同步調(diào)用讨永,iOS 之前采用了比較簡單的方案滔驶,還是依托于MGJRouter,不過添加了新的方法- (id)objectForURL:卿闹,注冊時也使用新的方法進行注冊


使用時NSNumber *orderCount = [MGJRouter objectForURL:@"mgj://cart/ordercount"]這樣就拿到了購物車里的商品數(shù)揭糕。

稍微復雜但更具通用性的方法是使用「協(xié)議」 <-> 「類」綁定的方式,還是以購物車為例锻霎,購物車組件可以提供這么個 Protocol


可以看到通過協(xié)議可以直接指定返回的數(shù)據(jù)類型著角。然后在購物車組件內(nèi)再新建個類實現(xiàn)這個協(xié)議,假設(shè)這個類名為MGJCartImpl旋恼,接著就可以把它與協(xié)議關(guān)聯(lián)起來[ModuleManagerregisterClass:MGJCartImplforProtocol:@protocol(MGJCart)]吏口,對于使用方來說,要拿到這個MGJCartImpl冰更,需要調(diào)用[ModuleManagerclassForProtocol:@protocol(MGJCart)]产徊。拿到之后再調(diào)用+ (NSInteger)orderCount就可以了。

那么蜀细,這個協(xié)議放在哪里比較合適呢囚痴?如果跟組件放在一起,使用時還是要先引入組件审葬,如果有多個這樣的組件就會比較麻煩了深滚。所以我們把這些公共的協(xié)議統(tǒng)一放到了PublicProtocolDomain.h下奕谭,到時只依賴這一個文件就可以了。

Android 也是采用類似的方式痴荐。

組件生命周期管理

理想中的組件可以很方便地集成到主客中血柳,并且有跟AppDelegate一致的回調(diào)方法。這也是ModuleManager做的事情生兆。

先來看看現(xiàn)在的入口方法


其中[MGJApp startApp]主要負責一些 SDK 的初始化难捌。[self trackLaunchTime]是我們打的一個點,用來監(jiān)測從main方法開始到入口方法調(diào)用結(jié)束花了多長時間鸦难。其他的都由ModuleManager搞定根吁,loadModuleFromPlist:pathForResource:方法會讀取 bundle 里的一個 plist 文件,這個文件的內(nèi)容大概是這樣的


每個Module都實現(xiàn)了ModuleProtocol合蔽,其中有一個- (BOOL)applicaiton:didFinishLaunchingWithOptions:方法击敌,如果實現(xiàn)了的話,就會被調(diào)用拴事。
還有一個問題就是沃斤,系統(tǒng)的一些事件會有通知,比如applicationDidBecomeActive會有對應(yīng)的UIApplicationDidBecomeActiveNotification刃宵,組件如果要做響應(yīng)的話衡瓶,只需監(jiān)聽這個系統(tǒng)通知即可。但也有一些事件是沒有通知的牲证,比如- application:didRegisterUserNotificationSettings:哮针,這時組件如果也要做點事情,怎么辦坦袍?
一個簡單的解決方法是在AppDelegate的各個方法里诚撵,手動調(diào)一遍組件的對應(yīng)的方法,如果有就執(zhí)行键闺。



殼工程

既然已經(jīng)拆出去了,那拆出去的組件總得有個載體澈驼,這個載體就是殼工程辛燥,殼工程主要包含一些基礎(chǔ)組件和業(yè)務(wù)SDK,這也是主工程包含的一些內(nèi)容缝其,所以如果在殼工程可以正常運行的話挎塌,到了主工程也沒什么問題。不過這里存在版本同步問題内边,之后會說到榴都。

遇到的問題

組件拆分

由于之前的代碼都是在一個工程下的,所以要單獨拿出來作為一個組件就會遇到不少問題漠其。首先是組件的劃分嘴高,當時在定義組件粒度時也花了些時間討論竿音,究竟是粒度粗點好,還是細點好拴驮。粗點的話比較有利于拆分春瞬,細點的話靈活度比較高。最終還是選擇粗一點的粒度套啤,先拆出來再說宽气。

假如要把詳情頁遷出來,就會發(fā)現(xiàn)它依賴了一些其他部分的代碼潜沦,那最快的方式就是直接把代碼拷過來萄涯,改個名使用。比較簡單暴力唆鸡。說起來比較簡單涝影,做的時候也是挺有挑戰(zhàn)的,因為正常的業(yè)務(wù)并不會因為「組件化」而停止喇闸,所以開發(fā)同學們需要同時兼顧正常的業(yè)務(wù)和組件的拆分袄琳。

版本管理
我們的組件包括第三方庫都是通過 Cocoapods 來管理的,其中組件使用了私有庫燃乍。之所以選擇 Cocoapods唆樊,一個是因為它比較方便,還有就是用戶基數(shù)比較大刻蟹,且社區(qū)也比較活躍(活躍到了會時不時地觸發(fā) Github 的 rate limit逗旁,導致長時間 clone 不下來···見此),當然也有其他的管理方式舆瘪,比如 submodule / subtree片效,在開發(fā)人員比較多的情況下,方便英古、靈活的方案容易占上風淀衣,雖然它也有自己的問題。主要有版本同步和更新/編譯慢的問題召调。
假如基礎(chǔ)組件做了個 API 接口升級膨桥,這個升級會對原有的接口做改動,自然就會升一個中位的版本號唠叛,比如原先是 1.6.19只嚣,那么現(xiàn)在就變成 1.7.0 了。而我們在 Podfile 里都是用~指定的艺沼,這樣就會出現(xiàn)主工程的 pod 版本升上去了册舞,但是殼工程沒有同步到,然后群里就會各種反饋編譯不過障般,而且這個編譯不過的長尾有時能拖上兩三天调鲸。
然后我們就想了個辦法盛杰,如果不在殼工程里指定基礎(chǔ)庫的版本,只在主工程里指定呢线得,理論上應(yīng)該可行饶唤,只要不出現(xiàn)某個基礎(chǔ)庫要同時維護多個版本的情況。但實踐中發(fā)現(xiàn)贯钩,殼工程有時會莫名其妙地升不上去募狂,在 podfile 里指定最新的版本又可以升上去,所以此路不通角雷。
還有一個問題是pod update時間過長祸穷,經(jīng)常會在Analyzing Dependency上卡 10 多分鐘,非常影響效率勺三。后來排查下來是跟組件的 Podspec 有關(guān)雷滚,配置了 subspec,且依賴比較多吗坚。
然后就是 pod update 之后的編譯祈远,由于是源碼編譯,所以這塊的時間花費也不少商源,接下去會考慮 framework 的方式车份。
持續(xù)集成
在剛開始,持續(xù)集成還不是很完善牡彻,業(yè)務(wù)方升級組件扫沼,直接把 podspec 扔到 private repo 里就完事了。這樣最簡單庄吼,但也經(jīng)常會帶來編譯通不過的問題缎除。而且這種隨意的版本升級也不太能保證質(zhì)量。于是我們就搭建了一套持續(xù)集成系統(tǒng)总寻,大概如此

每個組件升級之前都需要先通過編譯器罐,然后再決定是否升級。這套體系看起來不復雜渐行,但在實施過程中經(jīng)常會遇到后端的并發(fā)問題轰坊,導致業(yè)務(wù)方要么集成失敗,要么要等不少時間殊轴。而且也沒有一個地方可以呈現(xiàn)當前版本的組件版本信息。還有就是業(yè)務(wù)方對于這種命令行的升級方式接受度也不是很高袒炉。


基于此旁理,在經(jīng)過了幾輪討論之后,有了新版的持續(xù)集成平臺我磁,升級操作通過網(wǎng)頁端來完成孽文。
大致思路是驻襟,業(yè)務(wù)方如果要升級組件,假設(shè)現(xiàn)在的版本是 0.1.7芋哭,添加了一些 feature 之后沉衣,殼工程測試通過,想集成到主工程里看看效果减牺,或者其他組件也想引用這個最新的豌习,就可以在后臺手動把版本升到 0.1.8-rc.1,這樣的話拔疚,原先依賴~> 0.1.7的組件肥隆,不會升到 0.1.8,同時想要測試這個組件的話稚失,只要手動把版本調(diào)到 0.1.8-rc.1 就可以了栋艳。這個過程不會觸發(fā) CI 的編譯檢查。
當測試通過后句各,就可以把尾部的-rc.n去掉吸占,然后點擊「集成」,就會走 CI 編譯檢查凿宾,通過的話矾屯,會在主工程的 podfile 里寫上固定的版本號 0.1.8。也就是說菌湃,podfile 里所有的組件版本號都是固定的问拘。


周邊設(shè)施
基礎(chǔ)組件及組件的文檔 / Demo / 單元測試
無線基礎(chǔ)的職能是為集團提供解決方案,只是在蘑菇街 App 里能 work 是遠遠不夠的惧所,所以就需要提供入口骤坐,知道有哪些可用組件,并且如何使用下愈,就像這樣(目前還未實現(xiàn))

這就要求組件的負責人需要及時地更新 README / CHANGELOG / API纽绍,并且當發(fā)生 API 變更時,能夠快速通知到使用方势似。
公共 UI 組件
組件化之后還有一個問題就是資源的重復性拌夏,以前在一個工程里的時候,資源都可以很方便地拿到履因,現(xiàn)在獨立出去了障簿,也不知道哪些是公用的,哪些是獨有的栅迄,索性都放到自己的組件里站故,這樣就會導致包變大。還有一個問題是每個組件可能是不同的產(chǎn)品經(jīng)理在跟,而他們很可能只關(guān)注于自己關(guān)心的頁面長什么樣西篓,而忽略了整體的樣式愈腾。公共
UI 組件就是用來解決這些問題的,這些組件甚至可以跨 App 使用岂津。(目前還未實現(xiàn))


參考答案一:http://blog.csdn.net/GGGHub/article/details/52713642
參考答案二:http://limboy.me/tech/2016/03/10/mgj-components.html

3.runtime如何通過selector找到對應(yīng)的IMP地址虱黄?
概述
類對象中有類方法和實例方法的列表,列表中記錄著方法的名詞吮成、參數(shù)和實現(xiàn)橱乱,而selector本質(zhì)就是方法名稱,runtime通過這個方法名稱就可以在列表中找到該方法對應(yīng)的實現(xiàn)赁豆。
這里聲明了一個指向struct objc_method_list指針的指針仅醇,可以包含類方法列表和實例方法列表
具體實現(xiàn)
在尋找IMP的地址時,runtime提供了兩種方法
IMP class_getMethodImplementation(Class cls, SEL name);IMP method_getImplementation(Method m)
而根據(jù)官方描述魔种,第一種方法可能會更快一些
@note \c class_getMethodImplementation may be faster than \c method_getImplementation(class_getInstanceMethod(cls, name)).
對于第一種方法而言析二,類方法和實例方法實際上都是通過調(diào)用class_getMethodImplementation()來尋找IMP地址的,不同之處在于傳入的第一個參數(shù)不同
類方法(假設(shè)有一個類A)
class_getMethodImplementation(objc_getMetaClass("A"),@selector(methodName));
實例方法
class_getMethodImplementation([A class],@selector(methodName));
通過該傳入的參數(shù)不同节预,找到不同的方法列表叶摄,方法列表中保存著下面方法的結(jié)構(gòu)體,結(jié)構(gòu)體中包含這方法的實現(xiàn)安拟,selector本質(zhì)就是方法的名稱蛤吓,通過該方法名稱,即可在結(jié)構(gòu)體中找到相應(yīng)的實現(xiàn)糠赦。
struct objc_method {SEL method_namechar *method_typesIMP method_imp}
而對于第二種方法而言会傲,傳入的參數(shù)只有method,區(qū)分類方法和實例方法在于封裝method的函數(shù)
類方法
Method class_getClassMethod(Class cls, SEL name)
實例方法
Method class_getInstanceMethod(Class cls, SEL name)
最后調(diào)用IMP method_getImplementation(Method m)獲取IMP地址
實驗

這里有一個叫Test的類拙泽,在初始化方法里淌山,調(diào)用了兩次getIMPFromSelector:方法,第一個aaa方法是不存在的顾瞻,test1和test2分別為實例方法和類方法


然后我同時實例化了兩個Test的對象泼疑,打印信息如下


大家注意圖中紅色標注的地址出現(xiàn)了8次:0x1102db280,這個是在調(diào)用class_getMethodImplementation()方法時荷荤,無法找到對應(yīng)實現(xiàn)時返回的相同的一個地址退渗,無論該方法是在實例方法或類方法,無論是否對一個實例調(diào)用該方法蕴纳,返回的地址都是相同的会油,但是每次運行該程序時返回的地址并不相同,而對于另一種方法古毛,如果找不到對應(yīng)的實現(xiàn)翻翩,則返回0,在圖中我做了藍色標記。
還有一點有趣的是class_getClassMethod()的第一個參數(shù)無論傳入objc_getClass()還是objc_getMetaClass()体斩,最終調(diào)用method_getImplementation()都可以成功的找到類方法的實現(xiàn)。
而class_getInstanceMethod()的第一個參數(shù)如果傳入objc_getMetaClass()颖低,再調(diào)用method_getImplementation()時無法找到實例方法的實現(xiàn)卻可以找到類方法的實現(xiàn)絮吵。

4.runloop內(nèi)部實現(xiàn)邏輯?

蘋果在文檔里的說明忱屑,RunLoop 內(nèi)部的邏輯大致如下:


其內(nèi)部代碼整理如下 :
可以看到蹬敲,實際上 RunLoop 就是這樣一個函數(shù),其內(nèi)部是一個 do-while 循環(huán)莺戒。當你調(diào)用 CFRunLoopRun() 時伴嗡,線程就會一直停留在這個循環(huán)里;直到超時或被手動停止从铲,該函數(shù)才會返回瘪校。
RunLoop 的底層實現(xiàn)
從上面代碼可以看到,RunLoop 的核心是基于 mach port 的名段,其進入休眠時調(diào)用的函數(shù)是 mach_msg()阱扬。為了解釋這個邏輯,下面稍微介紹一下 OSX/iOS 的系統(tǒng)架構(gòu)伸辟。

蘋果官方將整個系統(tǒng)大致劃分為上述4個層次:
應(yīng)用層包括用戶能接觸到的圖形應(yīng)用麻惶,例如 Spotlight、Aqua信夫、SpringBoard 等窃蹋。
應(yīng)用框架層即開發(fā)人員接觸到的 Cocoa 等框架。
核心框架層包括各種核心框架静稻、OpenGL 等內(nèi)容警没。
Darwin 即操作系統(tǒng)的核心,包括系統(tǒng)內(nèi)核姊扔、驅(qū)動惠奸、Shell 等內(nèi)容,這一層是開源的恰梢,其所有源碼都可以在opensource.apple.com里找到佛南。
我們在深入看一下 Darwin 這個核心的架構(gòu):


其中,在硬件層上面的三個組成部分:Mach嵌言、BSD嗅回、IOKit (還包括一些上面沒標注的內(nèi)容),共同組成了 XNU 內(nèi)核摧茴。
XNU 內(nèi)核的內(nèi)環(huán)被稱作 Mach绵载,其作為一個微內(nèi)核,僅提供了諸如處理器調(diào)度、IPC (進程間通信)等非常少量的基礎(chǔ)服務(wù)娃豹。
BSD 層可以看作圍繞 Mach 層的一個外環(huán)焚虱,其提供了諸如進程管理、文件系統(tǒng)和網(wǎng)絡(luò)等功能懂版。
IOKit 層是為設(shè)備驅(qū)動提供了一個面向?qū)ο?C++)的一個框架鹃栽。
Mach
本身提供的 API 非常有限,而且蘋果也不鼓勵使用 Mach 的
API躯畴,但是這些API非趁窆模基礎(chǔ),如果沒有這些API的話蓬抄,其他任何工作都無法實施凉倚。在 Mach
中纺蛆,所有的東西都是通過自己的對象實現(xiàn)的,進程、線程和虛擬內(nèi)存都被稱為"對象"赖淤。和其他架構(gòu)不同苫费, Mach
的對象間不能直接調(diào)用涂身,只能通過消息傳遞的方式實現(xiàn)對象間的通信泊窘。"消息"是 Mach 中最基礎(chǔ)的概念,消息在兩個端口 (port)
之間傳遞优床,這就是 Mach 的 IPC (進程間通信) 的核心劝赔。
Mach 的消息定義是在頭文件的,很簡單:
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ù)據(jù)包 (BLOB)胆敞,其頭部定義了當前端口 local_port 和目標端口 remote_port着帽,
發(fā)送和接受消息是通過同一個 API 進行的,其 option 標記了消息傳遞的方向:
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);
為了實現(xiàn)消息的發(fā)送和接收移层,mach_msg()
函數(shù)實際上是調(diào)用了一個 Mach 陷阱 (trap)仍翰,即函數(shù)mach_msg_trap(),陷阱這個概念在 Mach
中等同于系統(tǒng)調(diào)用观话。當你在用戶態(tài)調(diào)用 mach_msg_trap() 時會觸發(fā)陷阱機制予借,切換到內(nèi)核態(tài);內(nèi)核態(tài)中內(nèi)核實現(xiàn)的 mach_msg()
函數(shù)會完成實際的工作频蛔,如下圖:


這些概念可以參考維基百科:System_call灵迫、Trap_(computing)。
RunLoop
的核心就是一個 mach_msg() (見上面代碼的第7步)晦溪,RunLoop 調(diào)用這個函數(shù)去接收消息瀑粥,如果沒有別人發(fā)送 port
消息過來,內(nèi)核會將線程置于等待狀態(tài)三圆。例如你在模擬器里跑起一個 iOS 的 App狞换,然后在 App 靜止時點擊暫停避咆,你會看到主線程調(diào)用棧是停留在
mach_msg_trap() 這個地方。
關(guān)于具體的如何利用 mach port 發(fā)送信息修噪,可以看看NSHipster 這一篇文章查库,或者這里的中文翻譯 。
關(guān)于Mach的歷史可以看看這篇很有趣的文章:Mac OS X 背后的故事(三)Mach 之父 Avie Tevanian黄琼。
蘋果用 RunLoop 實現(xiàn)的功能
首先我們可以看一下 App 啟動后 RunLoop 的狀態(tài):
可以看到膨报,系統(tǒng)默認注冊了5個Mode:

  1. kCFRunLoopDefaultMode: App的默認 Mode,通常主線程是在這個 Mode 下運行的适荣。
  2. UITrackingRunLoopMode: 界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動院领,保證界面滑動時不受其他 Mode 影響弛矛。
  3. UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用比然。
    4: GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode丈氓,通常用不到。
    5: kCFRunLoopCommonModes: 這是一個占位的 Mode强法,沒有實際作用万俗。
    你可以在這里看到更多的蘋果內(nèi)部的 Mode,但那些 Mode 在開發(fā)中就很難遇到了饮怯。

5.你理解的多線程闰歪?
1.可能會追問,每種多線程基于什么語言蓖墅?
2.生命周期是如何管理库倘?
3.你更傾向于哪種?追問至現(xiàn)在常用的兩種你的看法是论矾?
第一種:pthread
.特點:
1)一套通用的多線程API
2)適用于Unix\Linux\Windows等系統(tǒng)
3)跨平臺\可移植
4)使用難度大
b.使用語言:c語言
c.使用頻率:幾乎不用
d.線程生命周期:由程序員進行管理
第二種:NSThread
a.特點:
1)使用更加面向?qū)ο?br> 2)簡單易用教翩,可直接操作線程對象
b.使用語言:OC語言
c.使用頻率:偶爾使用
d.線程生命周期:由程序員進行管理
第三種:GCD
a.特點:
1)旨在替代NSThread等線程技術(shù)
2)充分利用設(shè)備的多核(自動)
b.使用語言:C語言
c.使用頻率:經(jīng)常使用
d.線程生命周期:自動管理
第四種:NSOperation
a.特點:
1)基于GCD(底層是GCD)
2)比GCD多了一些更簡單實用的功能
3)使用更加面向?qū)ο?br> b.使用語言:OC語言
c.使用頻率:經(jīng)常使用
d.線程生命周期:自動管理
多線程的原理
同一時間,CPU只能處理1條線程贪壳,只有1條線程在工作(執(zhí)行)
多線程并發(fā)(同時)執(zhí)行饱亿,其實是CPU快速地在多條線程之間調(diào)度(切換)
如果CPU調(diào)度線程的時間足夠快,就造成了多線程并發(fā)執(zhí)行的假象
思考:如果線程非常非常多闰靴,會發(fā)生什么情況彪笼?
CPU會在N多線程之間調(diào)度,CPU會累死蚂且,消耗大量的CPU資源
每條線程被調(diào)度執(zhí)行的頻次會降低(線程的執(zhí)行效率降低)
多線程的優(yōu)點
能適當提高程序的執(zhí)行效率
能適當提高資源利用率(CPU杰扫、內(nèi)存利用率)
多線程的缺點
開啟線程需要占用一定的內(nèi)存空間(默認情況下,主線程占用1M膘掰,子線程占用512KB)章姓,如果開啟大量的線程佳遣,會占用大量的內(nèi)存空間,降低程序的性能
線程越多凡伊,CPU在調(diào)度線程上的開銷就越大
程序設(shè)計更加復雜:比如線程之間的通信零渐、多線程的數(shù)據(jù)共享
你更傾向于哪一種?
傾向于GCD:
GCD
技術(shù)是一個輕量的系忙,底層實現(xiàn)隱藏的神奇技術(shù)诵盼,我們能夠通過GCD和block輕松實現(xiàn)多線程編程,有時候银还,GCD相比其他系統(tǒng)提供的多線程方法更加有效风宁,當然,有時候GCD不是最佳選擇蛹疯,另一個多線程編程的技術(shù)
NSOprationQueue 讓我們能夠?qū)⒑笈_線程以隊列方式依序執(zhí)行戒财,并提供更多操作的入口,這和 GCD 的實現(xiàn)有些類似捺弦。
這種類似不是一個巧合饮寞,在早期,MacOX
與 iOS 的程序都普遍采用Operation
Queue來進行編寫后臺線程代碼列吼,而之后出現(xiàn)的GCD技術(shù)大體是依照前者的原則來實現(xiàn)的幽崩,而隨著GCD的普及,在iOS 4 與 MacOS X
10.6以后寞钥,Operation Queue的底層實現(xiàn)都是用GCD來實現(xiàn)的慌申。
那這兩者直接有什么區(qū)別呢?

  1. GCD是底層的C語言構(gòu)成的API理郑,而NSOperationQueue及相關(guān)對象是Objc的對象太示。在GCD中,在隊列中執(zhí)行的是由block構(gòu)成的任務(wù)香浩,這是一個輕量級的數(shù)據(jù)結(jié)構(gòu)类缤;而Operation作為一個對象,為我們提供了更多的選擇邻吭;
  2. 在NSOperationQueue中餐弱,我們可以隨時取消已經(jīng)設(shè)定要準備執(zhí)行的任務(wù)(當然,已經(jīng)開始的任務(wù)就無法阻止了)囱晴,而GCD沒法停止已經(jīng)加入queue的block(其實是有的膏蚓,但需要許多復雜的代碼);
  3. NSOperation能夠方便地設(shè)置依賴關(guān)系畸写,我們可以讓一個Operation依賴于另一個Operation驮瞧,這樣的話盡管兩個Operation處于同一個并行隊列中,但前者會直到后者執(zhí)行完畢后再執(zhí)行枯芬;
  4. 我們能將KVO應(yīng)用在NSOperation中论笔,可以監(jiān)聽一個Operation是否完成或取消采郎,這樣子能比GCD更加有效地掌控我們執(zhí)行的后臺任務(wù);
  5. 在NSOperation中狂魔,我們能夠設(shè)置NSOperation的priority優(yōu)先級蒜埋,能夠使同一個并行隊列中的任務(wù)區(qū)分先后地執(zhí)行,而在GCD中最楷,我們只能區(qū)分不同任務(wù)隊列的優(yōu)先級整份,如果要區(qū)分block任務(wù)的優(yōu)先級,也需要大量的復雜代碼籽孙;
  6. 我們能夠?qū)SOperation進行繼承烈评,在這之上添加成員變量與成員方法,提高整個代碼的復用度犯建,這比簡單地將block任務(wù)排入執(zhí)行隊列更有自由度讲冠,能夠在其之上添加更多自定制的功能。
    總的來說胎挎,Operation
    queue
    提供了更多你在編寫多線程程序時需要的功能,并隱藏了許多線程調(diào)度忆家,線程取消與線程優(yōu)先級的復雜代碼犹菇,為我們提供簡單的API入口。從編程原則來說芽卿,一般我們需要盡可能的使用高等級揭芍、封裝完美的API,在必須時才使用底層API卸例。但是我認為當我們的需求能夠以更簡單的底層代碼完成的時候称杨,簡潔的GCD或許是個更好的選擇,而Operation
    queue 為我們提供能更多的選擇筷转。
    傾向于:NSOperation
    NSOperation相對于GCD:
    1姑原,NSOperation擁有更多的函數(shù)可用,具體查看api呜舒。NSOperationQueue 是在GCD基礎(chǔ)上實現(xiàn)的锭汛,只不過是GCD更高一層的抽象。
    2袭蝗,在NSOperationQueue中唤殴,可以建立各個NSOperation之間的依賴關(guān)系。
    3到腥,NSOperationQueue支持KVO朵逝。可以監(jiān)測operation是否正在執(zhí)行(isExecuted)乡范、是否結(jié)束(isFinished)配名,是否取消(isCanceld)
    4啤咽,GCD 只支持FIFO 的隊列,而NSOperationQueue可以調(diào)整隊列的執(zhí)行順序(通過調(diào)整權(quán)重)段誊。NSOperationQueue可以方便的管理并發(fā)闰蚕、NSOperation之間的優(yōu)先級。
    使用NSOperation的情況:各個操作之間有依賴關(guān)系连舍、操作需要取消暫停没陡、并發(fā)管理、控制操作之間優(yōu)先級索赏,限制同時能執(zhí)行的線程數(shù)量.讓線程在某時刻停止/繼續(xù)等盼玄。
    使用GCD的情況:一般的需求很簡單的多線程操作,用GCD都可以了潜腻,簡單高效埃儿。
    從編程原則來說,一般我們需要盡可能的使用高等級融涣、封裝完美的API童番,在必須時才使用底層API。
    當需求簡單威鹿,簡潔的GCD或許是個更好的選擇剃斧,而Operation queue 為我們提供能更多的選擇。

5.你理解的多線程忽你?
1.可能會追問幼东,每種多線程基于什么語言?
2.生命周期是如何管理科雳?
3.你更傾向于哪種根蟹?追問至現(xiàn)在常用的兩種你的看法是?
第一種:pthread
.特點:
1)一套通用的多線程API
2)適用于Unix\Linux\Windows等系統(tǒng)
3)跨平臺\可移植
4)使用難度大
b.使用語言:c語言
c.使用頻率:幾乎不用
d.線程生命周期:由程序員進行管理
第二種:NSThread
a.特點:
1)使用更加面向?qū)ο?br> 2)簡單易用糟秘,可直接操作線程對象
b.使用語言:OC語言
c.使用頻率:偶爾使用
d.線程生命周期:由程序員進行管理
第三種:GCD
a.特點:
1)旨在替代NSThread等線程技術(shù)
2)充分利用設(shè)備的多核(自動)
b.使用語言:C語言
c.使用頻率:經(jīng)常使用
d.線程生命周期:自動管理
第四種:NSOperation
a.特點:
1)基于GCD(底層是GCD)
2)比GCD多了一些更簡單實用的功能
3)使用更加面向?qū)ο?br> b.使用語言:OC語言
c.使用頻率:經(jīng)常使用
d.線程生命周期:自動管理
多線程的原理
同一時間简逮,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行)
多線程并發(fā)(同時)執(zhí)行尿赚,其實是CPU快速地在多條線程之間調(diào)度(切換)
如果CPU調(diào)度線程的時間足夠快买决,就造成了多線程并發(fā)執(zhí)行的假象
思考:如果線程非常非常多,會發(fā)生什么情況吼畏?
CPU會在N多線程之間調(diào)度督赤,CPU會累死,消耗大量的CPU資源
每條線程被調(diào)度執(zhí)行的頻次會降低(線程的執(zhí)行效率降低)
多線程的優(yōu)點
能適當提高程序的執(zhí)行效率
能適當提高資源利用率(CPU泻蚊、內(nèi)存利用率)
多線程的缺點
開啟線程需要占用一定的內(nèi)存空間(默認情況下躲舌,主線程占用1M,子線程占用512KB)性雄,如果開啟大量的線程没卸,會占用大量的內(nèi)存空間羹奉,降低程序的性能
線程越多,CPU在調(diào)度線程上的開銷就越大
程序設(shè)計更加復雜:比如線程之間的通信约计、多線程的數(shù)據(jù)共享
你更傾向于哪一種诀拭?
傾向于GCD:
GCD
技術(shù)是一個輕量的,底層實現(xiàn)隱藏的神奇技術(shù)煤蚌,我們能夠通過GCD和block輕松實現(xiàn)多線程編程耕挨,有時候,GCD相比其他系統(tǒng)提供的多線程方法更加有效尉桩,當然筒占,有時候GCD不是最佳選擇,另一個多線程編程的技術(shù)
NSOprationQueue 讓我們能夠?qū)⒑笈_線程以隊列方式依序執(zhí)行蜘犁,并提供更多操作的入口翰苫,這和 GCD 的實現(xiàn)有些類似。
這種類似不是一個巧合这橙,在早期奏窑,MacOX
與 iOS 的程序都普遍采用Operation
Queue來進行編寫后臺線程代碼,而之后出現(xiàn)的GCD技術(shù)大體是依照前者的原則來實現(xiàn)的屈扎,而隨著GCD的普及埃唯,在iOS 4 與 MacOS X
10.6以后,Operation Queue的底層實現(xiàn)都是用GCD來實現(xiàn)的助隧。
那這兩者直接有什么區(qū)別呢筑凫?

  1. GCD是底層的C語言構(gòu)成的API滑沧,而NSOperationQueue及相關(guān)對象是Objc的對象并村。在GCD中,在隊列中執(zhí)行的是由block構(gòu)成的任務(wù)滓技,這是一個輕量級的數(shù)據(jù)結(jié)構(gòu)哩牍;而Operation作為一個對象,為我們提供了更多的選擇令漂;
  2. 在NSOperationQueue中膝昆,我們可以隨時取消已經(jīng)設(shè)定要準備執(zhí)行的任務(wù)(當然,已經(jīng)開始的任務(wù)就無法阻止了)叠必,而GCD沒法停止已經(jīng)加入queue的block(其實是有的荚孵,但需要許多復雜的代碼);
  3. NSOperation能夠方便地設(shè)置依賴關(guān)系纬朝,我們可以讓一個Operation依賴于另一個Operation收叶,這樣的話盡管兩個Operation處于同一個并行隊列中,但前者會直到后者執(zhí)行完畢后再執(zhí)行共苛;
  4. 我們能將KVO應(yīng)用在NSOperation中判没,可以監(jiān)聽一個Operation是否完成或取消蜓萄,這樣子能比GCD更加有效地掌控我們執(zhí)行的后臺任務(wù);
  5. 在NSOperation中澄峰,我們能夠設(shè)置NSOperation的priority優(yōu)先級嫉沽,能夠使同一個并行隊列中的任務(wù)區(qū)分先后地執(zhí)行,而在GCD中俏竞,我們只能區(qū)分不同任務(wù)隊列的優(yōu)先級绸硕,如果要區(qū)分block任務(wù)的優(yōu)先級,也需要大量的復雜代碼胞此;
  6. 我們能夠?qū)SOperation進行繼承臣咖,在這之上添加成員變量與成員方法,提高整個代碼的復用度漱牵,這比簡單地將block任務(wù)排入執(zhí)行隊列更有自由度夺蛇,能夠在其之上添加更多自定制的功能。
    總的來說酣胀,Operation
    queue
    提供了更多你在編寫多線程程序時需要的功能刁赦,并隱藏了許多線程調(diào)度,線程取消與線程優(yōu)先級的復雜代碼闻镶,為我們提供簡單的API入口甚脉。從編程原則來說,一般我們需要盡可能的使用高等級铆农、封裝完美的API牺氨,在必須時才使用底層API。但是我認為當我們的需求能夠以更簡單的底層代碼完成的時候墩剖,簡潔的GCD或許是個更好的選擇猴凹,而Operation
    queue 為我們提供能更多的選擇。
    傾向于:NSOperation
    NSOperation相對于GCD:
    1岭皂,NSOperation擁有更多的函數(shù)可用郊霎,具體查看api。NSOperationQueue 是在GCD基礎(chǔ)上實現(xiàn)的爷绘,只不過是GCD更高一層的抽象书劝。
    2,在NSOperationQueue中土至,可以建立各個NSOperation之間的依賴關(guān)系购对。
    3,NSOperationQueue支持KVO陶因÷獍可以監(jiān)測operation是否正在執(zhí)行(isExecuted)、是否結(jié)束(isFinished),是否取消(isCanceld)
    4烙如,GCD 只支持FIFO 的隊列么抗,而NSOperationQueue可以調(diào)整隊列的執(zhí)行順序(通過調(diào)整權(quán)重)。NSOperationQueue可以方便的管理并發(fā)亚铁、NSOperation之間的優(yōu)先級蝇刀。
    使用NSOperation的情況:各個操作之間有依賴關(guān)系、操作需要取消暫停徘溢、并發(fā)管理吞琐、控制操作之間優(yōu)先級,限制同時能執(zhí)行的線程數(shù)量.讓線程在某時刻停止/繼續(xù)等然爆。
    使用GCD的情況:一般的需求很簡單的多線程操作站粟,用GCD都可以了,簡單高效曾雕。
    從編程原則來說奴烙,一般我們需要盡可能的使用高等級、封裝完美的API剖张,在必須時才使用底層API切诀。
    當需求簡單,簡潔的GCD或許是個更好的選擇搔弄,而Operation queue 為我們提供能更多的選擇幅虑。

6.GCD執(zhí)行原理?
GCD有一個底層線程池顾犹,這個池中存放的是一個個的線程倒庵。之所以稱為“池”,很容易理解出這個“池”中的線程是可以重用的炫刷,當一段時間后這個線程沒有被調(diào)用胡話擎宝,這個線程就會被銷毀。注意:開多少條線程是由底層線程池決定的(線程建議控制再3~5條)柬唯,池是系統(tǒng)自動來維護认臊,不需要我們程序員來維護(看到這句話是不是很開心圃庭?)
而我們程序員需要關(guān)心的是什么呢锄奢?我們只關(guān)心的是向隊列中添加任務(wù),隊列調(diào)度即可剧腻。
? 如果隊列中存放的是同步任務(wù)拘央,則任務(wù)出隊后,底層線程池中會提供一條線程供這個任務(wù)執(zhí)行书在,任務(wù)執(zhí)行完畢后這條線程再回到線程池灰伟。這樣隊列中的任務(wù)反復調(diào)度,因為是同步的,所以當我們用currentThread打印的時候栏账,就是同一條線程帖族。
?
如果隊列中存放的是異步的任務(wù),(注意異步可以開線程)挡爵,當任務(wù)出隊后竖般,底層線程池會提供一個線程供任務(wù)執(zhí)行,因為是異步執(zhí)行茶鹃,隊列中的任務(wù)不需等待當前任務(wù)執(zhí)行完畢就可以調(diào)度下一個任務(wù)涣雕,這時底層線程池中會再次提供一個線程供第二個任務(wù)執(zhí)行,執(zhí)行完畢后再回到底層線程池中闭翩。
?
這樣就對線程完成一個復用挣郭,而不需要每一個任務(wù)執(zhí)行都開啟新的線程,也就從而節(jié)約的系統(tǒng)的開銷疗韵,提高了效率兑障。在iOS7.0的時候,使用GCD系統(tǒng)通常只能開58條線程蕉汪,iOS8.0以后旺垒,系統(tǒng)可以開啟很多條線程,但是實在開發(fā)應(yīng)用中肤无,建議開啟線程條數(shù):35條最為合理先蒋。
通過案例明白GCD的執(zhí)行原理
案例一:

分析:
首先執(zhí)行任務(wù)1,這是肯定沒問題的宛渐,只是接下來竞漾,程序遇到了同步線程,那么它會進入等待窥翩,等待任務(wù)2執(zhí)行完业岁,然后執(zhí)行任務(wù)3。但這是隊列寇蚊,有任務(wù)來笔时,當然會將任務(wù)加到隊尾,然后遵循FIFO原則執(zhí)行任務(wù)仗岸。那么允耿,現(xiàn)在任務(wù)2就會被加到最后,任務(wù)3排在了任務(wù)2前面扒怖,問題來了:
任務(wù)3要等任務(wù)2執(zhí)行完才能執(zhí)行较锡,任務(wù)2又排在任務(wù)3后面,意味著任務(wù)2要在任務(wù)3執(zhí)行完才能執(zhí)行盗痒,所以他們進入了互相等待的局面蚂蕴。【既然這樣,那干脆就卡在這里吧】這就是死鎖骡楼。

案例二:


分析:
首先執(zhí)行任務(wù)1熔号,接下來會遇到一個同步線程,程序會進入等待鸟整。等待任務(wù)2執(zhí)行完成以后跨嘉,才能繼續(xù)執(zhí)行任務(wù)3。從dispatch_get_global_queue可以看出吃嘿,任務(wù)2被加入到了全局的并行隊列中祠乃,當并行隊列執(zhí)行完任務(wù)2以后,返回到主隊列兑燥,繼續(xù)執(zhí)行任務(wù)3亮瓷。

案例三:


案例四:


分析:
首先,將【任務(wù)1降瞳、異步線程嘱支、任務(wù)5】加入Main
Queue中,異步線程中的任務(wù)是:【任務(wù)2挣饥、同步線程除师、任務(wù)4】。所以扔枫,先執(zhí)行任務(wù)1汛聚,然后將異步線程中的任務(wù)加入到Global
Queue中,因為異步線程短荐,所以任務(wù)5不用等待倚舀,結(jié)果就是2和5的輸出順序不一定。然后再看異步線程中的任務(wù)執(zhí)行順序忍宋。任務(wù)2執(zhí)行完以后痕貌,遇到同步線程。將同步線程中的任務(wù)加入到Main
Queue中糠排,這時加入的任務(wù)3在任務(wù)5的后面夸浅。當任務(wù)3執(zhí)行完以后螺垢,沒有了阻塞干像,程序繼續(xù)執(zhí)行任務(wù)4图贸。


案例五:


分析:
和上面幾個案例的分析類似,先來看看都有哪些任務(wù)加入了Main Queue:
【異步線程云石、任務(wù)4唉工、死循環(huán)研乒、任務(wù)5】汹忠。
在加入到Global Queue異步線程中的任務(wù)有:
【任務(wù)1、同步線程、任務(wù)3】宽菜。第一個就是異步線程谣膳,任務(wù)4不用等待,
所以結(jié)果任務(wù)1和任務(wù)4順序不一定铅乡。任務(wù)4完成后继谚,程序進入死循環(huán),
Main Queue阻塞阵幸。但是加入到Global Queue的異步線程不受影響花履,
繼續(xù)執(zhí)行任務(wù)1后面的同步線程。同步線程中挚赊,將任務(wù)2加入到了主線程诡壁,
并且,任務(wù)3等待任務(wù)2完成以后才能執(zhí)行荠割。這時的主線程妹卿,已經(jīng)被死循環(huán)阻塞了。
所以任務(wù)2無法執(zhí)行蔑鹦,當然任務(wù)3也無法執(zhí)行夺克,在死循環(huán)后的任務(wù)5也不會執(zhí)行。


7.怎么防止別人動態(tài)在你程序生成代碼嚎朽?
(這題是聽錯了面試官的意思)
面試官意思是怎么防止別人反編譯你的app铺纽?
1.本地數(shù)據(jù)加密
iOS應(yīng)用防反編譯加密技術(shù)之一:對NSUserDefaults,sqlite存儲文件數(shù)據(jù)加密哟忍,保護帳號和關(guān)鍵信息
2.URL編碼加密
iOS應(yīng)用防反編譯加密技術(shù)之二:對程序中出現(xiàn)的URL進行編碼加密室囊,防止URL被靜態(tài)分析
3.網(wǎng)絡(luò)傳輸數(shù)據(jù)加密
iOS應(yīng)用防反編譯加密技術(shù)之三:對客戶端傳輸數(shù)據(jù)提供加密方案,有效防止通過網(wǎng)絡(luò)接口的攔截獲取數(shù)據(jù)
4.方法體魁索,方法名高級混淆
iOS應(yīng)用防反編譯加密技術(shù)之四:對應(yīng)用程序的方法名和方法體進行混淆融撞,保證源碼被逆向后無法解析代碼
5.程序結(jié)構(gòu)混排加密
iOS應(yīng)用防反編譯加密技術(shù)之五:對應(yīng)用程序邏輯結(jié)構(gòu)進行打亂混排,保證源碼可讀性降到最低
6.借助第三方APP加固粗蔚,例如:網(wǎng)易云易盾

8.YYAsyncLayer如何異步繪制尝偎?
YYAsyncLayer是異步繪制與顯示的工具。為了保證列表滾動流暢鹏控,將視圖繪制致扯、以及圖片解碼等任務(wù)放到后臺線程,
YYKitDemo
對于列表主要對兩個代理方法的優(yōu)化当辐,一個與繪制顯示有關(guān)抖僵,另一個與計算布局有關(guān):
Objective-C
1
2-(UITableViewCell)tableView:(UITableView)tableViewcellForRowAtIndexPath:(NSIndexPath)indexPath;
-(CGFloat)tableView:(UITableView
)tableViewheightForRowAtIndexPath:(NSIndexPath*)indexPath;
常規(guī)邏輯可能覺得應(yīng)該先調(diào)用tableView : cellForRowAtIndexPath :返回UITableViewCell對象,事實上調(diào)用順序是先返回UITableViewCell的高度缘揪,是因為UITableView繼承自UIScrollView耍群,滑動范圍由屬性contentSize來確定义桂,UITableView的滑動范圍需要通過每一行的UITableViewCell的高度計算確定,復雜cell如果在列表滾動過程中計算可能會造成一定程度的卡頓蹈垢。
假設(shè)有20條數(shù)據(jù)慷吊,當前屏幕顯示5條,tableView : heightForRowAtIndexPath :方法會先執(zhí)行20次返回所有高度并計算出滑動范圍曹抬,tableView : cellForRowAtIndexPath :執(zhí)行5次返回當前屏幕顯示的cell個數(shù)溉瓶。

從圖中簡單看下流程,從網(wǎng)絡(luò)請求返回JSON數(shù)據(jù)谤民,將Cell的高度以及內(nèi)部視圖的布局封裝為Layout對象堰酿,Cell顯示之前在異步線程計算好所有布局對象,并存入數(shù)組张足,每次調(diào)用tableView: heightForRowAtIndexPath :只需要從數(shù)組中取出胞锰,可避免重復的布局計算。同時在調(diào)用tableView: cellForRowAtIndexPath :對Cell內(nèi)部視圖異步繪制布局兢榨,以及圖片的異步繪制解碼嗅榕,這里就要說到今天的主角YYAsyncLayer。
YYAsyncLayer
首先介紹里面幾個類:
YYAsyncLayer:繼承自CALayer吵聪,繪制凌那、創(chuàng)建繪制線程的部分都在這個類。
YYTransaction:用于創(chuàng)建RunloopObserver監(jiān)聽MainRunloop的空閑時間吟逝,并將YYTranaction對象存放到集合中帽蝶。
YYSentinel:提供獲取當前值的value(只讀)屬性,以及- (int32_t)increase自增加的方法返回一個新的value值块攒,用于判斷異步繪制任務(wù)是否被取消的工具励稳。


AsyncDisplay.png
上圖是整體異步繪制的實現(xiàn)思路,后面一步步說明〈丫現(xiàn)在假設(shè)需要繪制Label驹尼,其實是繼承自UIView,重寫+ (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其實就是contentsNeedUpdated方法讲逛,此時并不會立即在后臺線程去更新顯示亏吝,而是將YYTransaction對象本身提交保存在transactionSet的集合中,上圖中所示盏混。
Objective-C
+(YYTransaction
)transactionWithTarget:(id)targetselector:(SEL)selector{
if(!target||!selector)returnnil;
YYTransactiont=[YYTransactionnew];
t.target=target;
t.selector=selector;
returnt;
}
-(void)commit{
if(!_target||!_selector)return;
YYTransactionSetup();
[transactionSetaddObject:self];
}
同時在YYTransaction.m中注冊一個RunloopObserver蔚鸥,監(jiān)聽MainRunloop在kCFRunLoopCommonModes(包含kCFRunLoopDefaultMode惜论、UITrackingRunLoopMode)下的kCFRunLoopBeforeWaiting和kCFRunLoopExit的狀態(tài),也就是說在一次Runloop空閑時去執(zhí)行更新顯示的操作株茶。
kCFRunLoopBeforeWaiting:Runloop將要進入休眠来涨。
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對象執(zhí)行SEL的方法,分發(fā)到每一次Runloop執(zhí)行技羔,避免一次Runloop執(zhí)行時間太長僵闯。
Objective-C
staticvoidYYRunLoopObserverCallBack(CFRunLoopObserverRefobserver,CFRunLoopActivityactivity,void
info){
if(transactionSet.count==0)return;
NSSetcurrentSet=transactionSet;
transactionSet=[NSMutableSetnew];
[currentSetenumerateObjectsUsingBlock:^(YYTransaction
transaction,BOOL*stop){
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[transaction.targetperformSelector:transaction.selector];

 #pragma clang diagnostic pop

}];
}
接下來是異步繪制,這里用了一個比較巧妙的方法處理藤滥,當使用GCD時提交大量并發(fā)任務(wù)到后臺線程導致線程被鎖住鳖粟、休眠的情況,創(chuàng)建與程序當前激活CPU數(shù)量(activeProcessorCount)相同的串行隊列拙绊,并限制MAX_QUEUE_COUNT向图,將隊列存放在數(shù)組中。
YYAsyncLayer.m有一個方法YYAsyncLayerGetDisplayQueue來獲取這個隊列用于繪制(這部分YYKit中有獨立的工具YYDispatchQueuePool)标沪。創(chuàng)建隊列中有一個參數(shù)是告訴隊列執(zhí)行任務(wù)的服務(wù)質(zhì)量quality of service榄攀,在iOS8+之后相比之前系統(tǒng)有所不同。
iOS8之前隊列優(yōu)先級:
DISPATCH_QUEUE_PRIORITY_HIGH 2高優(yōu)先級
DISPATCH_QUEUE_PRIORITY_DEFAULT 0默認優(yōu)先級
DISPATCH_QUEUE_PRIORITY_LOW (-2)低優(yōu)先級
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN后臺優(yōu)先級
iOS8+之后:
QOS_CLASS_USER_INTERACTIVE 0x21, 用戶交互(希望盡快完成金句,不要放太耗時操作)
QOS_CLASS_USER_INITIATED 0x19, 用戶期望(不要放太耗時操作)
QOS_CLASS_DEFAULT 0x15, 默認(用來重置對列使用的)
QOS_CLASS_UTILITY 0x11, 實用工具(耗時操作檩赢,可以使用這個選項)
QOS_CLASS_BACKGROUND 0x09, 后臺
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];//存放隊列的數(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
接下來是關(guān)于繪制部分的代碼,對外接口YYAsyncLayerDelegate代理中提供- (YYAsyncLayerDisplayTask )newAsyncDisplayTask方法用于回調(diào)繪制的代碼违寞,以及是否異步繪制的BOOl類型屬性displaysAsynchronously贞瞒,同時重寫CALayer的display方法來調(diào)用繪制的方法- (void)_displayAsync:(BOOL)async。
這里有必要了解關(guān)于后臺的繪制任務(wù)何時會被取消趁曼,下面兩種情況需要取消军浆,并調(diào)用了YYSentinel的increase方法,使value值增加(線程安全):
在視圖調(diào)用setNeedsDisplay時說明視圖的內(nèi)容需要被更新挡闰,將當前的繪制任務(wù)取消瘾敢,需要重新顯示。
以及視圖被釋放調(diào)用了dealloc方法尿这。
在YYAsyncLayer.h中定義了YYAsyncLayerDisplayTask類簇抵,有三個block屬性用于繪制的回調(diào)操作,從命名可以看出分別是將要繪制射众,正在繪制碟摆,以及繪制完成的回調(diào),可以從block傳入的參數(shù)BOOL(^isCancelled)(void)判斷當前繪制是否被取消叨橱。
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)(CALayerlayer,BOOLfinished);
下面是部分- (void)_displayAsync:(BOOL)async繪制的代碼典蜕,主要是一些邏輯判斷以及繪制函數(shù)断盛,在異步執(zhí)行之前通過YYAsyncLayerGetDisplayQueue創(chuàng)建的隊列,這里通過YYSentinel判斷當前的value是否等于之前的值愉舔,如果不相等钢猛,說明繪制任務(wù)被取消了,繪制過程會多次判斷是否取消轩缤,如果是則return命迈,保證被取消的任務(wù)能及時退出,如果繪制完畢則設(shè)置圖片到layer.contents火的。
Objective-C
if(async){//異步
if(task.willDisplay)task.willDisplay(self);
YYSentinel
sentinel=_sentinel;
int32_tvalue=sentinel.value;
NSLog(@" --- %d ---",value);
//判斷當前計數(shù)是否等于之前計數(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)化你是從哪幾方面著手壶愤?
一、首頁啟動速度
啟動過程中做的事情越少越好(盡可能將多個接口合并)
不在UI線程上作耗時的操作(數(shù)據(jù)的處理在子線程進行馏鹤,處理完通知主線程刷新節(jié)目)
在合適的時機開始后臺任務(wù)(例如在用戶指引節(jié)目就可以開始準備加載的數(shù)據(jù))
盡量減小包的大小
優(yōu)化方法:
量化啟動時間
啟動速度模塊化
輔助工具(友盟征椒,聽云,F(xiàn)lurry)
二湃累、頁面瀏覽速度
json的處理(iOS 自帶的NSJSONSerialization勃救,Jsonkit,SBJson)
數(shù)據(jù)的分頁(后端數(shù)據(jù)多的話治力,就要分頁返回蒙秒,例如網(wǎng)易新聞,或者 微博記錄)
數(shù)據(jù)壓縮(大數(shù)據(jù)也可以壓縮返回琴许,減少流量税肪,加快反應(yīng)速度)
內(nèi)容緩存(例如網(wǎng)易新聞的最新新聞列表都是要緩存到本地,從本地加載榜田,可以緩存到內(nèi)存益兄,或者數(shù)據(jù)庫,根據(jù)情況而定)
延時加載tab(比如app有5個tab箭券,可以先加載第一個要顯示的tab净捅,其他的在顯示時候加載,按需加載)
算法的優(yōu)化(核心算法的優(yōu)化辩块,例如有些app 有個 聯(lián)系人姓名用漢語拼音的首字母排序)
三蛔六、操作流暢度優(yōu)化:
Tableview 優(yōu)化(tableview cell的加載優(yōu)化)
ViewController加載優(yōu)化(不同view之間的跳轉(zhuǎn),可以提前準備好數(shù)據(jù))
四废亭、數(shù)據(jù)庫的優(yōu)化:
數(shù)據(jù)庫設(shè)計上面的重構(gòu)
查詢語句的優(yōu)化
分庫分表(數(shù)據(jù)太多的時候国章,可以分不同的表或者庫)
五、服務(wù)器端和客戶端的交互優(yōu)化:
客戶端盡量減少請求
服務(wù)端盡量做多的邏輯處理
服務(wù)器端和客戶端采取推拉結(jié)合的方式(可以利用一些同步機制)
通信協(xié)議的優(yōu)化豆村。(減少報文的大幸菏蕖)
電量使用優(yōu)化(盡量不要使用后臺運行)
六、非技術(shù)性能優(yōu)化
產(chǎn)品設(shè)計的邏輯性(產(chǎn)品的設(shè)計一定要符合邏輯掌动,或者邏輯盡量簡單四啰,否則會讓程序員抓狂宁玫,有時候用了好大力氣,才可以完成一個小小的邏輯設(shè)計問題)
界面交互的規(guī)范(每個模塊的界面的交互盡量統(tǒng)一柑晒,符合操作習慣)
代碼規(guī)范(這個可以隱形帶來app 性能的提高欧瘪,比如 用if else 還是switch ,或者是用匙赞!還是 ==)
code review(堅持code Review 持續(xù)重構(gòu)代碼佛掖。減少代碼的邏輯復雜度)
日常交流(經(jīng)常分享一些代碼,或者邏輯處理中的坑)
以上問題加參考答案罚屋,部分自己回答(群友回答)+網(wǎng)上博客參考苦囱,回答的不好勿噴嗅绸!
僅供學習使用脾猛! 謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鱼鸠,一起剝皮案震驚了整個濱河市猛拴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蚀狰,老刑警劉巖愉昆,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異麻蹋,居然都是意外死亡跛溉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門扮授,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芳室,“玉大人,你說我怎么就攤上這事刹勃】昂睿” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵荔仁,是天一觀的道長伍宦。 經(jīng)常有香客問我,道長乏梁,這世上最難降的妖魔是什么次洼? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮遇骑,結(jié)果婚禮上卖毁,老公的妹妹穿的比我還像新娘。我一直安慰自己质蕉,他們只是感情好势篡,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布翩肌。 她就那樣靜靜地躺著,像睡著了一般禁悠。 火紅的嫁衣襯著肌膚如雪念祭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天碍侦,我揣著相機與錄音粱坤,去河邊找鬼。 笑死瓷产,一個胖子當著我的面吹牛站玄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播濒旦,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼株旷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了尔邓?” 一聲冷哼從身側(cè)響起晾剖,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎梯嗽,沒想到半個月后齿尽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡灯节,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年循头,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片炎疆。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡卡骂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出磷雇,到底是詐尸還是另有隱情偿警,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布唯笙,位于F島的核電站螟蒸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏崩掘。R本人自食惡果不足惜七嫌,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望苞慢。 院中可真熱鬧诵原,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吗蚌,卻和暖如春腿倚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蚯妇。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工敷燎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人箩言。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓硬贯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親陨收。 傳聞我的和親對象是個殘疾皇子饭豹,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

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

  • iOS面試題:騰訊一面以及參考思路:http://www.reibang.com/p/c2048ae9d799iO...
    th先生閱讀 748評論 0 2
  • 阿里-p6-一面 1.介紹下內(nèi)存的幾大區(qū)域? 2.你是如何組件化解耦的畏吓? 3.runtime如何通過selecto...
    CaptainMi閱讀 804評論 0 1
  • 本文將從以下幾個部分來介紹多線程墨状。 第一部分介紹多線程的基本原理卫漫。 第二部分介紹Run loop菲饼。 第三部分介紹多...
    曲年閱讀 1,264評論 2 14
  • 文|@發(fā)憤的草莓 “曬”思想是啥包吝? 我所謂的“曬”思想饼煞,其實就是寫作。 這里沒有用“寫作”這個詞诗越。因為這個詞砖瞧,顯得...
    發(fā)憤的草莓閱讀 712評論 0 2
  • 吳曉波說:“人年輕的時候,如果有一次很長途的旅行嚷狞,對你的一生來講是有很深遠的意義块促。”吳曉波在大學的時候去過湖南一個...
    寶寶靈閱讀 479評論 0 1