剛剛,阿里開源 iOS 協(xié)程開發(fā)框架 coobjc!
剛剛,阿里巴巴正式對(duì)外開源了基于 Apache 2.0 協(xié)議的協(xié)程開發(fā)框架 coobjc,開發(fā)者們可以在 Github 上自主下載。
coobjc是為iOS平臺(tái)打造的開源協(xié)程開發(fā)框架,支持Objective-C和Swift,同時(shí)提供了cokit庫(kù)為Foundation和UIKit中的部分API提供了協(xié)程化支持,本文將為大家詳細(xì)介紹coobjc的設(shè)計(jì)理念及核心優(yōu)勢(shì)。
開源地址
https://github.com/alibaba/coobjc
iOS異步編程問(wèn)題
從2008年***個(gè)iOS版本發(fā)布至今的11年時(shí)間里,iOS的異步編程方式發(fā)展緩慢。
基于 Block 的異步編程回調(diào)是目前 iOS 使用最廣泛的異步編程方式,iOS 系統(tǒng)提供的 GCD 庫(kù)讓異步開發(fā)變得很簡(jiǎn)單方便,但是基于這種編程方式的缺點(diǎn)也有很多,主要有以下幾點(diǎn):
- 容易進(jìn)入"嵌套地獄"
- 錯(cuò)誤處理復(fù)雜和冗長(zhǎng)
- 容易忘記調(diào)用 completion handler
- 條件執(zhí)行變得很困難
- 從互相獨(dú)立的調(diào)用中組合返回結(jié)果變得極其困難
- 在錯(cuò)誤的線程中繼續(xù)執(zhí)行(如子線程操作UI)
- 難以定位原因的多線程崩潰(手淘中多線程crash已占比60%以上)
- 鎖和信號(hào)量濫用帶來(lái)的卡頓、卡死
針對(duì)多線程以及尤其引發(fā)的各種崩潰和性能問(wèn)題,我們制定了很多編程規(guī)范、進(jìn)行了各種新人培訓(xùn),嘗試降低問(wèn)題發(fā)生的概率,但是問(wèn)題依然很嚴(yán)峻,多線程引發(fā)的問(wèn)題占比并沒(méi)有明顯的下降,異步編程本來(lái)就是很復(fù)雜的事情,單靠規(guī)范和培訓(xùn)是難以從根本上解決問(wèn)題的,需要有更加好的編程方式來(lái)解決。
解決方案
上述問(wèn)題在很多系統(tǒng)和語(yǔ)言開發(fā)中都可能會(huì)碰到,解決問(wèn)題的標(biāo)準(zhǔn)方式就是使用協(xié)程,C#、Kotlin、Python、Javascript 等熱門語(yǔ)言均支持協(xié)程極其相關(guān)語(yǔ)法,使用這些語(yǔ)言的開發(fā)者可以很方便的使用協(xié)程及相關(guān)功能進(jìn)行異步編程。
2017 年的 C++ 標(biāo)準(zhǔn)開始支持協(xié)程,Swift5 中也包含了協(xié)程相關(guān)的標(biāo)準(zhǔn),從現(xiàn)在的發(fā)展趨勢(shì)看基于協(xié)程的全新的異步編程方式,是我們解決現(xiàn)有異步編程問(wèn)題的有效的方式,但是蘋果基本已經(jīng)不會(huì)升級(jí) Objective-C 了,因此使用Objective-C的開發(fā)者是無(wú)法使用官方的協(xié)程能力的,而*** Swift 的發(fā)布和推廣也還需要時(shí)日,為了讓廣大iOS開發(fā)者能快速享受到協(xié)程帶來(lái)的編程方式上的改變,手機(jī)淘寶架構(gòu)團(tuán)隊(duì)基于長(zhǎng)期對(duì)系統(tǒng)底層庫(kù)和匯編的研究,通過(guò)匯編和C語(yǔ)言實(shí)現(xiàn)了支持 Objective-C 和 Swift 協(xié)程的***解決方案 —— coobjc。
核心能力
- 提供了類似C#和Javascript語(yǔ)言中的Async/Await編程方式支持,在協(xié)程中通過(guò)調(diào)用await方法即可同步得到異步方法的執(zhí)行結(jié)果,非常適合IO、網(wǎng)絡(luò)等異步耗時(shí)調(diào)用的同步順序執(zhí)行改造。
- 提供了類似Kotlin中的Generator功能,用于懶計(jì)算生成序列化數(shù)據(jù),非常適合多線程可中斷的序列化數(shù)據(jù)生成和訪問(wèn)。
- 提供了Actor Model的實(shí)現(xiàn),基于Actor Model,開發(fā)者可以開發(fā)出更加線程安全的模塊,避免由于直接函數(shù)調(diào)用引發(fā)的各種多線程崩潰問(wèn)題。
- 提供了元組的支持,通過(guò)元組Objective-C開發(fā)者可以享受到類似Python語(yǔ)言中多值返回的好處。
內(nèi)置系統(tǒng)擴(kuò)展庫(kù)
- 提供了對(duì)NSArray、NSDictionary等容器庫(kù)的協(xié)程化擴(kuò)展,用于解決序列化和反序列化過(guò)程中的異步調(diào)用問(wèn)題。
- 提供了對(duì)NSData、NSString、UIImage等數(shù)據(jù)對(duì)象的協(xié)程化擴(kuò)展,用于解決讀寫IO過(guò)程中的異步調(diào)用問(wèn)題。
- 提供了對(duì)NSURLConnection和NSURLSession的協(xié)程化擴(kuò)展,用于解決網(wǎng)絡(luò)異步請(qǐng)求過(guò)程中的異步調(diào)用問(wèn)題。
- 提供了對(duì)NSKeyedArchieve、NSJSONSerialization等解析庫(kù)的擴(kuò)展,用于解決解析過(guò)程中的異步調(diào)用問(wèn)題。
coobjc設(shè)計(jì)
- ***層是協(xié)程內(nèi)核,包含了棧切換的管理、協(xié)程調(diào)度器的實(shí)現(xiàn)、協(xié)程間通信channel的實(shí)現(xiàn)等。
- 中間層是基于協(xié)程的操作符的包裝,目前支持async/await、Generator、Actor等編程模型。
- 最上層是對(duì)系統(tǒng)庫(kù)的協(xié)程化擴(kuò)展,目前基本上覆蓋了Foundation和UIKit的所有IO和耗時(shí)方法。
核心實(shí)現(xiàn)原理
協(xié)程的核心思想是控制調(diào)用棧的主動(dòng)讓出和恢復(fù)。一般的協(xié)程實(shí)現(xiàn)都會(huì)提供兩個(gè)重要的操作:
- Yield:是讓出cpu的意思,它會(huì)中斷當(dāng)前的執(zhí)行,回到上一次Resume的地方。
- Resume:繼續(xù)協(xié)程的運(yùn)行。執(zhí)行Resume后,回到上一次協(xié)程Yield的地方。
我們基于線程的代碼執(zhí)行時(shí)候,是沒(méi)法做出暫停操作的,我們現(xiàn)在要做的事情就是要代碼執(zhí)行能夠暫停,還能夠再恢復(fù)。 基本上代碼執(zhí)行都是一種基于調(diào)用棧的模型,所以如果我們能把當(dāng)前調(diào)用棧上的狀態(tài)都保存下來(lái),然后再能從緩存中恢復(fù),那我們就能夠?qū)崿F(xiàn)yield和 resume。
實(shí)現(xiàn)這樣操作有幾種方法呢?
- ***種:利用glibc 的 ucontext組件(云風(fēng)的庫(kù))。
- 第二種:使用匯編代碼來(lái)切換上下文(實(shí)現(xiàn)c協(xié)程),原理同ucontext。
- 第三種:利用C語(yǔ)言語(yǔ)法switch-case的奇淫技巧來(lái)實(shí)現(xiàn)(Protothreads)。
- 第四種:利用了 C 語(yǔ)言的 setjmp 和 longjmp。
- 第五種:利用編譯器支持語(yǔ)法糖。
上述第三種和第四種只是能過(guò)做到跳轉(zhuǎn),但是沒(méi)法保存調(diào)用棧上的狀態(tài),看起來(lái)基本上不能算是實(shí)現(xiàn)了協(xié)程,只能算做做demo,第五種除非官方支持,否則自行改寫編譯器通用性很差。而***種方案的 ucontext 在iOS上是廢棄了的,不能使用。那么我們使用的是第二種方案,自己用匯編模擬一下 ucontext。
模擬ucontext的核心是通過(guò)getContext和setContext實(shí)現(xiàn)保存和恢復(fù)調(diào)用棧。需要熟悉不同CPU架構(gòu)下的調(diào)用約定(Calling Convention). 匯編實(shí)現(xiàn)就是要針對(duì)不同cpu實(shí)現(xiàn)一套,我們目前實(shí)現(xiàn)了 armv7、arm64、i386、x86_64,支持iPhone真機(jī)和模擬器。
Show me the code
說(shuō)了這么多,還是看看代碼吧,我們從一個(gè)簡(jiǎn)單的網(wǎng)絡(luò)請(qǐng)求加載圖片功能來(lái)看看coobjc到底是如何使用的。
下面是最普通的網(wǎng)絡(luò)請(qǐng)求的寫法:
下面是使用coobjc庫(kù)協(xié)程化改造后的代碼:
原本需要20行的代碼,通過(guò)coobjc協(xié)程化改造后,減少了一半,整個(gè)代碼邏輯和可讀性都更加好,這就是coobjc強(qiáng)大的能力,能把原本很復(fù)雜的異步代碼,通過(guò)協(xié)程化改造,轉(zhuǎn)變成邏輯簡(jiǎn)潔的順序調(diào)用。
coobjc還有很多其他強(qiáng)大的能力,本文對(duì)于coobjc的實(shí)際使用就不過(guò)多介紹了,感興趣的朋友可以去官方github倉(cāng)庫(kù)自行下載查看。
性能提升
我們?cè)趇Phone7 iOS11.4.1的設(shè)備上使用協(xié)程和傳統(tǒng)多線程方式分別模擬高并發(fā)讀取數(shù)據(jù)的場(chǎng)景,下面是兩種方式得到的壓測(cè)數(shù)據(jù)。
- 測(cè)試機(jī)器:iPhone7 iOS11.4.1
- 數(shù)據(jù)文件大小:20M
- 協(xié)程最多使用線程數(shù):4
- 數(shù)據(jù)測(cè)試結(jié)果(統(tǒng)計(jì)的是所有并發(fā)訪問(wèn)結(jié)束的總耗時(shí)):
從上面的表格我們可以看到使用在并發(fā)量很小的場(chǎng)景,由于多線程可以完全使用設(shè)備的計(jì)算核心,因此coobjc總耗時(shí)要比傳統(tǒng)多線程略高,但是由于整體耗時(shí)都很小,因此差異并不明顯,但是隨著并發(fā)量的增大,coobjc的優(yōu)勢(shì)開始逐漸體現(xiàn)出來(lái),當(dāng)并發(fā)量超過(guò)1000以后,傳統(tǒng)多線程開始出現(xiàn)線程分配異常,而導(dǎo)致很多并發(fā)任務(wù)并沒(méi)有執(zhí)行,因此在上表中顯示的是大于20秒,實(shí)際是任務(wù)已經(jīng)無(wú)法正常執(zhí)行了,但是coobjc仍然可以正常運(yùn)行。
我們?cè)谑謾C(jī)淘寶這種超級(jí)App中嘗試了協(xié)程化改造,針對(duì)部分性能差的頁(yè)面,我們發(fā)現(xiàn)在滑動(dòng)過(guò)程中存在很多主線程IO調(diào)用、數(shù)據(jù)解析,導(dǎo)致幀率下降嚴(yán)重,通過(guò)引入coobjc,在不改變?cè)袠I(yè)務(wù)代碼的基礎(chǔ)上,通過(guò)全局hook部分IO、數(shù)據(jù)解析方法,即可讓原來(lái)在主線程中同步執(zhí)行的IO方法異步執(zhí)行,并且不影響原有的業(yè)務(wù)邏輯,通過(guò)測(cè)試驗(yàn)證,這樣的改造在低端機(jī)(iPhone6及以下的機(jī)器)上的幀率有20%左右的提升。
優(yōu)勢(shì)
簡(jiǎn)明
- 概念少:只有很少的幾個(gè)操作符,相比響應(yīng)式幾十個(gè)操作符,簡(jiǎn)直不能再簡(jiǎn)單了。
- 原理簡(jiǎn)單:協(xié)程的實(shí)現(xiàn)原理很簡(jiǎn)單,整個(gè)協(xié)程庫(kù)只有幾千行代碼。
易用
- 使用簡(jiǎn)單:它的使用方式比GCD還要簡(jiǎn)單,接口很少。
- 改造方便:現(xiàn)有代碼只需要進(jìn)行很少的改動(dòng)就可以協(xié)程化,同時(shí)我們針對(duì)系統(tǒng)庫(kù)提供了大量協(xié)程化接口。
清晰
- 同步寫異步邏輯:同步順序方式寫代碼是人類最容易接受的方式,這可以極大的減少出錯(cuò)的概率。
- 可讀性高:使用協(xié)程方式編寫的代碼比block嵌套寫出來(lái)的代碼可讀性要高很多。
性能
- 調(diào)度性能更快:協(xié)程本身不需要進(jìn)行內(nèi)核級(jí)線程的切換,調(diào)度性能快,即使創(chuàng)建上萬(wàn)個(gè)協(xié)程也毫無(wú)壓力。
- 減少卡頓卡死: 協(xié)程的使用以幫助開發(fā)減少鎖、信號(hào)量的濫用,通過(guò)封裝會(huì)引起阻塞的IO等協(xié)程接口,可以從根源上減少卡頓、卡死,提升應(yīng)用整體的性能。
總結(jié)
程序是寫來(lái)給人讀的,只會(huì)偶爾讓機(jī)器執(zhí)行一下。——Abelson and Sussman
基于協(xié)程實(shí)現(xiàn)的編程范式能夠幫助開發(fā)者編寫出更加優(yōu)美、健壯、可讀性更強(qiáng)的代碼。
協(xié)程可以幫助我們?cè)诰帉懖l(fā)代碼的過(guò)程中減少線程和鎖的使用,提升應(yīng)用的性能和穩(wěn)定性。
【本文為51CTO專欄作者“阿里巴巴官方技術(shù)”原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)聯(lián)系原作者】


































