iOS10定時消息的改動

姓名:雷瀟 16030110083????

轉載自:http://www.lai18.com/content/24631425.html

【嵌牛導讀】iOS10已經發布了一段時間,iOS10的各種適配相信大家已經完成。本文將講述的是關于iOS10內核的一個小改動,慣例,本文屬于進階性技術文,不會講解API的使用,要求讀者對RunLoop有一定的認知,感謝網友@送你的獨白么 提供的SDK。

【嵌牛鼻子】ios系統, runloop

【嵌牛提問】ios10怎么進行定是消息的改動?

【嵌牛正文】當我們的程序需要定時處理一些事件時,我們就會用到定時器,常用的定時器有NSTimer,CADisplayLink,GCD Timer,本文主要針對NSTimer和CADisplayLink進行講述,因為這兩者跟你的Application更為密切。

NSTimer和CADisplayLink都是建立在CFRunLoopTimer之上的抽象物,但有趣的是,蘋果只提供了NSTimer和CFRunLoopTimer互轉的Toll-Free Bridge,并沒有提供CADisplayLink和CFRunLoopTimer互轉的接口,因此一些開發者對此產生了一些猜想,有的人認為,CADisplayLink是用GCD Dispatch Source來實現的,有的人認為,CADisplayLink是用RunLoopSource來實現的,但這些猜想的依據都太容易被推翻了。如果CADisplayLink是用GCD Dispatch Source來實現的,那么CADisplayLink是怎么在你所創建的子線程中工作的呢?如果CADisplayLink是用RunLoopSource來實現的,會不會多此一舉?

CFRunLoopTimer是RunLoop的定時源,與Source1(Port)一樣,都屬于端口事件源,但不同的是,每一個Source1都有與之對應的端口,而一個RunLoopMode中的所有CFRunLoopTimer共用一個端口(Mode Timer Port),CFRunLoopTimer在RunLoop中的工作原理如下圖。

定時源工作 從定時源在RunLoop中的工作原理我們得知,只要符合條件的定時器都會被觸發,也就是說,在同一次Loop中,可能會執行幾個定時器的回調。

很多講述定時器的技術文中都有這么一個觀點,如果一個定時器錯過了本次可以觸發的時間點,那么定時器將跳過這個時間點,等待下一個時間點的到來,這個觀點似乎是從官方文檔中得來的,但這個觀點跟定時器在RunLoop中的工作原理并不符。定時消息從內核發出,消息在消息中心等待被處理,RunLoop每次Loop都會去消息中心查找相應的端口消息,若找到相應的端口消息就會進行處理,所以,即使當前RunLoop正在執行一個耗時很長的任務,當任務執行完進入下一次Loop時,那些未被處理的消息仍然會被處理。經過大量測試表明,定時消息并不會因延遲而掉失。

關于RunLoop,官方文檔在這一部份的勘誤比較多,經常會出現文檔的介紹跟源碼不同的情況,所以想學習RunLoop的同學,建議看源碼和自己做測試,特別是自己做測試。

NSTimer和CADisplayLink最大的區別在于信號的發射頻率不同,CADisplayLink的發射頻率固定在16.67ms一次,而NSTimer則可以自由定義。我在頁面間跳轉的性能優化(一)中曾經提到過,不是必要的情況下,都不要選擇使用CADisplayLink作為定時器,因為它會使目標RunLoop一直處理活躍狀態。下面通過一個例子來看看實際的效果,創建一個CADisplayLink定時器,設置為100秒后觸發,然后觀察目標RunLoop的狀態。

CADisplayLink 從實際效果我們可以看到,目標RunLoop一直處于活躍狀態,不斷地處理內核發出的信號,直到RunLoop Stop或CADisplayLink定時器被移除。同樣的條件,我們把定時器換成NSTimer來觀察實際情況。

NSTimer 與CADisplayLink的固定信號不同,NSTimer的信號間隔完全是由使用者來定義。所以,除非你需要實現定時動畫,不然都不要選擇使用CADisplayLink作為定時器,它不僅會損耗大量的CPU資源,還會響應目標RunLoop處理其它事件源。

改動

前面介紹了定時器的工作原理,現在來看看實際的改動,從一個例子入手進行講述。現在有頁面A,B,頁面A,B各有一個按鈕,頁面A的按鈕用來進入頁面B,進入頁面B后創建一個子線程,然后向子線程添加一個定時器并啟動RunLoop,頁面B的按鈕用于停止定時器,并返回頁面A,頁面B被釋放時會在dealloc方法里輸出dealloc,編譯環境是ARC,下圖為頁面B的代碼,Gif圖分別是iOS10與iOS9的實際運行效果。

頁面B代碼

iOS10

iOS9 一般情況下,從頁面B返回到頁面A后,頁面B會被釋放,頁面B的dealloc方法會輸出dealloc,但從實際的運行效果可以看到,在iOS10環境下頁面B并沒有被釋放,WTF,為什么iOS10環境下會這樣?要回答這個問題,我們需要先知道iOS10的改動是什么。

若目標RunLoop當前沒有定時源需要處理(像上面的例子那樣,子線程RunLoop只有一個定時器,該定時器移除后,則子線程RunLoop沒有定時源需要處理),則通知內核不需要再向當前Timer Port發送定時消息并移除該Timer Port。在iOS10環境下,當移除Timer Port后,內核會把消息列表中與該Timer Port相應的定時消息移除,而iOS10以前的環境下,當移除Timer Port后,內核不會把消息列表中與該Timer Port相應的定時消息移除。iOS10的處理是更為合理的,iOS10以前的處理可能是歷史遺留問題吧。

看回上面的例子,例子中遇到的問題是頁面B返回后并沒有被釋放,即頁面B的內存被強制保留了,所以我們現在需要知道的是頁面B為什么被強制保留了。在頁面B中我們創建了一個子線程,子線程的主函數是頁面B的對象函數,這可能是導致頁面B被強制保留的原因,所以,我們需要知道子線程開啟前后,頁面B對象的引用計數是否有增加。

創建并開啟子線程

頁面B的引用計數 從輸出的信息我們得知,創建子線程后,Target會被強制保留,直到子線程的主函數返回。引用計數在很多時候可以幫助我們了解內存的使用情況,但在ARC編譯環境下,我們無法直接使用retainCount方法來獲取一個對象的引用計數,所以,我們需要做額外的處理。

獲取對象的引用計數 回到例子中,我們知道了頁面B被強制保留的原因后,就知道了怎么解決,只需要退出子線程即可,子線程之所以可以一直存活,是因為啟動了RunLoop,所以,我們只需要退出RunLoop,子線程的主函數就會返回。例子中涉及到線程異步的問題,定時器是在子線程RunLoop中注冊的,但定時器的移除操作卻是在主線程,由于子線程RunLoop處理完一次定時信號后,就會進入休眠狀態。在iOS10以前的環境下,定時器被移除后,內核仍然會向對應的Timer Port發送一次信號,所以子線程RunLoop接收到信號后會被喚醒,由于沒有定時源需要處理,所以RunLoop會直接跳轉到判斷階段,判斷階段會檢測當前RunLoopMode是否有事件源需要處理,若沒有事件源需要處理,則會退出RunLoop。由于例子中子線程RunLoop的當前RunLoopMode只有一個定時器,而定時器被移除后,RunLoopMode就沒有了需要處理的事件源,所以會退出RunLoop,子線程的主函數也因此返回,頁面B對象被釋放。

但在iOS10環境下,當定時器被移除后,內核不再向對應的Timer Port發送任何信號,所以子線程RunLoop一直處于休眠狀態并沒有退出,而我們只需要手動喚醒RunLoop即可。

更改頁面B代碼

iOS10 例子中所遇到的問題已經解決,但看完這個例子,可能你會有疑問,這個例子講述的情況有實戰意義?這個例子是從一個國外成熟產品所提供的配套SDK中簡化而來,配套的SDK用于與產品進行對接。額......實話說,當我看到這個處理方式的時候,我被震驚了,沒想到一個成熟產品所提供的配套SDK會出現這樣的問題,讓我更震驚的是,隨后在其它SDK中也發現了這個問題,這......

我們回頭來看看例子中的處理方式,例子中,子線程RunLoop的退出依賴于RunLoopMode的事件源為空,這種RunLoop的退出方式是極不穩定的,因為系統有很多API會向目標RunLoopMode添加額外的事件源來處理系統事件的,所以這種方式是不能確保一定可以退出RunLoop的。正確的方式應該是配對調用CFRunLoopRun( ),CFRunLoopStop( )來啟動和退出RunLoop,需要注意的是,除非你要創建一個單例線程,不然不要使用[runloop run]方法來啟動RunLoop,因為使用run方法啟動RunLoop后,唯一退出RunLoop的方式是當前RunLoopMode的事件源為空,而我們知道這種方式本身是極不穩定的。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容

  • 前言 iOS10已經發布了一段時間,iOS10的各種適配相信大家已經完成。本文將講述的是關于iOS10內核的一...
    Delpan閱讀 3,797評論 24 37
  • 之前要做一個發送短信驗證碼的倒計時功能,打算用NSTimer來實現,做的過程中發現坑還是有不少的。 基本使用 NS...
    WeiHing閱讀 4,399評論 1 8
  • 在軟件開發過程中,我們常常需要在某個時間后執行某個方法,或者是按照某個周期一直執行某個方法。在這個時候,我們就需要...
    誰遇而安閱讀 20,947評論 2 20
  • 李雪蘭 光陰似箭,歲月如梭。40多年前,來自合陽縣的各公社、各村莊的莘莘學子,躊躇滿志,憑借優異的學習成績和個人...
    PatrickBJZP閱讀 947評論 0 0
  • 今天我和孩子互相表揚了對方。先說一下我對孩子的表揚,今天上午于老師打電話給我,孩子肚子疼,我趕快騎車把他領回...
    昊天媽媽閱讀 301評論 1 2