【iOS】NSRunLoop

目錄

概念

RunLoop與線程的關系

Runloop對外的接口

CFRunLoopSourceRef

Source0

Source1

CFRunLoopTimer

CFRunLoopObserver

RunLoop的Mode

應用場景

Runloop的內部邏輯

Runloop應用

tableView延遲加載圖片,保證流暢

Timer不被ScrollView的滑動影響

AFNetworking

?編輯

PerformSelecter


概念

一般來講線程一次只能執行一個任務,執行完后就會退出,我們現在想實現一個功能:線程一直在處理事件并且不會退出,這就是我們Runloop的作用。其實很像runloop的名字所表示的,繞著一個圈圈一直跑。

要實現runloop這種模型一個關鍵點就是怎么樣去管理事件/消息,讓線程在沒有處理消息時休眠以避免資源占用、在有消息到來時立刻被喚醒

runloop就是一個可以管理需要處理的消息與事件的一個對象,CFRunLoopRef 可以理解為在 CoreFoundation 框架內的NSRunLoop,它提供了純 C 函數的 API,但是它的API都是線程安全的,而NSRunLoop卻不是線程安全的

RunLoop與線程的關系

基本上所有的線程操作的底層都是對pthread_t的封裝

而關于RunLoop和線程,蘋果不允許直接創建Runloop,它只提供了兩個自動獲取的函數:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()

CFRunLoopGetMain的實現如下:

這當中用到了函數_CFRunLoopGet0,其實現如下:

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {//如果t不存在,則標記為主線程(即默認情況,默認是主線程)if (pthread_equal(t, kNilPthreadT)) {t = pthread_main_thread_np();}__CFLock(&loopsLock);if (!__CFRunLoops) {__CFUnlock(&loopsLock);//創建全局字典,標記為kCFAllocatorSystemDefaultCFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);//通過主線程 創建主運行循環CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());//利用dict,進行key-value綁定操作,即可以說明,線程和runloop是一一對應的// dict : key valueCFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {CFRelease(dict);}CFRelease(mainLoop);__CFLock(&loopsLock);}//通過其他線程獲取runloopCFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));__CFUnlock(&loopsLock);if (!loop) {//如果沒有獲取到,則新建一個運行循環CFRunLoopRef newLoop = __CFRunLoopCreate(t);__CFLock(&loopsLock);loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));if (!loop) {//將新建的runloop 與 線程進行key-value綁定CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);loop = newLoop;}// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it__CFUnlock(&loopsLock);CFRelease(newLoop);}if (pthread_equal(t, pthread_self())) {_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);}}return loop;
}

從上面的代碼可以看出,線程和 RunLoop 之間是一一對應的,其關系是保存在一個全局的 Dictionary 里 線程剛創建時并沒有runloop,我們需要主動獲取,系統才會自動幫我們創建runloop并加到字典中

Runloop對外的接口

通過前兩個函數我們可以分別獲得當前線程的CFRunLoop對象和主線程的CFRunLoop對象,但是只是獲得了,如果想要讓Runloop運行起來,就還需要一些別的操作。首先先看一下runloop的一些具體結構

在CoreFoundation里面關于RunLoop有5個類:

  • CFRunLoopRef

  • CFRunLoopModeRef

  • CFRunLoopSourceRef

  • CFRunLoopTimerRef

  • CFRunLoopObserverRef

一個Runloop可以包含多個Mode,CFRunLoopModeRef類沒有對外暴露,只是通過CFRunLoopRef 的接口進行了封裝,他們的關系如下:

可以看到每個model中包含了Source/Timer/Observer的集合,每次調用RunLoop的主函數時,只能指定其中一個Mode,這個Mode被稱作CurrentMode,如果要切換Mode,必須退出Loop,再重新指定一個Mode進入,這樣做可以分隔開不同組的Source/TImer/Observer,避免互相影響

接下來我們分別來看看Source/Timer/Observer這三種結構,首先是Source

CFRunLoopSourceRef

CFRunLoopSourceRef 是事件產生的地方。Source有兩個版本:Source0 和 Source1。兩個的區別主要是RunLoop事件源的不同:

  • Source0:處理非基于端口的事件(如程序內部自定義的事件)

  • Source1:處理基于端口的事件(如來自內核的Mach端口信息)

Source0 需要手動標記(CFRunLoopSourceSignal)并喚醒 RunLoop 才能觸發,而 Source1 會自動喚醒 RunLoop。

需要明確一個概念,RunLoop主要用來處理異步事件,如用戶輸入、定時器觸發、網絡響應等這些事件通常被封裝成事件源,然后由RunLoop在適當的時機調度和處理

Source0

Source0 只包含了一個回調(函數指針),它并不能主動觸發事件

使用時,先調用CFRunLoopSourceSignal(source0)將 Source 標記為待處理,然后手動調用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件

// 假設有一個方法,用于處理按鈕點擊
- (void)buttonClicked {// 手動觸發RunLoop的Source0CFRunLoopSourceSignal(source0);CFRunLoopWakeUp(CFRunLoopGetCurrent()); // 喚醒RunLoop來處理事件
}
?
// 配置Source0
- (void)setupSource0 {CFRunLoopSourceContext context = {0, (__bridge void *)(self), NULL, NULL, NULL, NULL, NULL, NULL, NULL, &callout};source0 = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);CFRunLoopAddSource(CFRunLoopGetCurrent(), source0, kCFRunLoopDefaultMode);
}
?
// Source0的回調函數
void callout(void *info) {NSLog(@"Source0 event triggered.");
}

Source1

Source1 包含了一個 mach_port 和一個回調(函數指針),被用于通過內核和其他線程相互發送消息。這種 Source 能主動喚醒 RunLoop 的線程。

// 配置Source1
- (void)setupSource1 {CFRunLoopSourceContext1 context = {0, (__bridge void *)(self), NULL, NULL, NULL, NULL, NULL, &perform, NULL};CFMessagePortRef localPort = CFMessagePortCreateLocal(kCFAllocatorDefault, CFSTR("com.example.app.port"), &callback, &context, false);CFRunLoopSourceRef source1 = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, localPort, 0);CFRunLoopAddSource(CFRunLoopGetCurrent(), source1, kCFRunLoopCommonModes);
}
?
// Source1的回調函數
CFDataRef callback(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void *info) {NSLog(@"Received message: %d", msgid);return NULL;
}
?
// Source1的事件執行
void perform(void *info) {NSLog(@"Performing work in response to external event.");
}

使用場景: ? 處理來自其他進程的數據或信號。 ? 監聽系統級事件或網絡事件。

CFRunLoopTimer

這是一個基于時間的觸發器,包含一個時間長度和一個回調,當其加入到 RunLoop 時,RunLoop會注冊對應的時間點,當時間點到時,RunLoop會被喚醒以執行那個回調

CFRunLoopObserver

這是用來監視和相應RunLoop的特定活動的一種對象,通過 CFRunLoopObserver,開發者可以在 RunLoop 的不同階段插入自定義的代碼來執行特定的任務

可以觀測的時間點有以下幾個:

我們在上面講的 Source/Timer/Observer 被統稱為 mode item,一個item被重復添加到同一個mode時不會多次執行,但是如果一個mode中一個item都沒有,runloop會自動退出,不會進出循環

RunLoop的Mode

剛才在上文講了mode item,modeitem是被加到mode中的,我們現在講一下Runloop的Mode

這里有個概念叫CommonModes,一個Mode可以把自己標記成”Common”屬性(通過將其 ModeName 添加到 RunLoop 的 “commonModes” 中),每當Runloop的內容發生變化時,RunLoop 都會自動將 _commonModeItems 里的 Source/Observer/Timer 同步到具有 Common標記的所有Mode里

應用場景

比如說之前寫項目時經常遇到的那個滑動時計數器停止計時的問題,用這個點就可以回答:

主線程的Runloop中有兩個預置的Mode:

kCFRunLoopDefaultModeUITrackingRunLoopMode

DefalutMode是App平時所處的狀態,TrackingMode是當滑動時所處的狀態,當我們創建NSTimer添加到DefalutMode中,Timer會得到重復回調,但是當我們滾動我們的TableView時,Runloop會切換Mode,由DefalutMode切換為TrackingMode,此時Timer會停止同時不會進行回調,也不會影響到滑動的操作

這時想讓滑動時NSTimer可以繼續運作的話,有兩個方法:

  • 一種方法就是將Timer分別加入到兩個Mode

  • 另一種方法就是將NSTimer加到最頂層的RunLoop 的 commonModeItems,加入后的ModeItems類型會被Runloop加到具有common屬性的Mode中去,也就是直接將Timer同時加到defaultMode與TrackMode中去

iOS中有5種Mode:

蘋果公開的三種有:

  • NSDefaultRunLoopMode(kCFRunloopDefaultMode):默認狀態,app通常在這個mode下運行

  • UITrackingRunLoopMode:界面跟蹤mode(例如滑動scrollview時不被其他mode影響)

  • NSRunLoopCommonModes(kCFRunLoopCommonModes):是 前兩個mode的集合,可以把自定義mode用CFRunLoopAddCommonMode函數加入到集合中

還有兩種只需了解:

  • GSEventReceiveRunLoopMode:接收系統內部mode,通常用不到

  • UIInitializationRunLoopMode:私有,只在app啟動時使用,使用完就不在集合中了

Runloop的內部邏輯

Runloop的邏輯有一張非常經典的圖:

Runloop應用

tableView延遲加載圖片,保證流暢

在快速滑動tableView時,滑動過的圖片會一直加載,但滑動過的圖片都不是我們想要呈現的圖片,如果加載就浪費CPU資源,用RunLoop就可以避免滑動時加載圖片

給ImgaeView的加載圖片的方法指定只有在DefalutMode下才能加載,滑動時不加載圖片

[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"imgName.png"] afterDelay:0 inModes:@[NSDefaultRunLoopMode]];

Timer不被ScrollView的滑動影響

上面其實已經講過這個問題了,除了剛才提到的兩個方法,還有一種方法:

用GCD創建定時器,它不會受到RunLoop影響

AFNetworking

在多線程中,線程執行完任務就會退出,那么如果需要反復執行任務的話,就會頻繁地創建與銷毀線程,這樣不僅效率低下,還增加了系統的開銷,因此如果有一個常駐線程來處理這些任務就可以避免這種情況。

一個RunLoop中如果沒有Observer/Timer/Source等items,Runloop會自動退出,因此我們創建一個空的port發送消息給Runloop,以至于Runloop不會退出而是一直常駐

PerformSelecter

當調用 NSObject 的 performSelecter:afterDelay: 后,其內部會自動創建一個Timer加到Runloop中,當時間到了執行回調,如果當前線程沒有Runloop,此方法也會失效

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/94043.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/94043.shtml
英文地址,請注明出處:http://en.pswp.cn/web/94043.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

HTTP接口鑒權方式

幾種主流且可行的HTTP接口鑒權方式,從簡單到復雜,各有其適用場景。我將它們分為兩大類:傳統方式和現代方式。一、傳統方式這類方式簡單易用,但通常安全性較低或擴展性較差,適用于內部系統或簡單API。1. HTTP Basic Aut…

DIC技術極端環境案例分享:系泊鏈在海水環境下氫脆化性能測試

實驗結果的具體視頻可詳見以下鏈接:研索儀器DIC技術在極端條件下的應用 01 海水環境: DIC技術在海水環境下的應用核心挑戰在于惡劣的光學條件(如散射、衰減、畸變)、嚴酷的化學/生物環境(腐蝕、生物污損)…

DL00291-聯邦學習以去中心化鋰離子電池健康預測模型完整實現

聯邦學習在鋰離子電池健康預測中的應用:去中心化訓練與客戶選擇策略在鋰離子電池健康預測領域,隨著電池使用環境的多樣化以及電池狀態監測需求的不斷增長,傳統的集中式數據訓練方法逐漸顯現出局限性。為了解決數據隱私保護和大規模數據集中處…

TCP協議大全

什么是TCP?基本定義與屬性TCP(傳輸控制協議)是傳輸層的重要協議,具有面向連接(傳輸前需先建立連接,是發送方和接收方的點對點一對一連接)、基于字節流(以字節流形式傳輸數據&#xf…

當硅基生命遇見碳基萌寵:Deepoc具身智能如何重新定義“寵物監護者”

在東京某高級公寓里,一只布偶貓正優雅地踱步到智能喂食器前。令人驚訝的是,這個通體雪白的喂食器突然"活"了過來——它微微傾斜身體,用柔和的機械音發出問候,同時伸出仿生機械臂輕輕撫過貓咪的背部。這不是科幻電影場景…

線上日志排查問題

1、查異常堆棧 顯示該行及其后面的50行內容,然后通過 less 命令進行分頁查看 grep -A 50 "NullPointerException" a.log | less參數解釋: grep: 文本搜索命令-A 50: After 的意思,顯示匹配行后面的50行“NullPointerException”: 要…

LabVIEW與CAN開發燃料電池監控

?基于 LabVIEW 與 CAN 總線技術,構建了一套多組質子交換膜燃料電池(PEMFC)堆監控系統。系統采用優質硬件設備,通過 LabVIEW 的圖形化編程能力實現數據采集、實時監控與多堆切換控制,穩定可靠,為燃料電池性…

CVPR焦點 | 神經網絡新范式:輕量化與精度并行,重塑視覺任務性能天花板

關注gongzhonghao【CVPR頂會精選】神經網絡卷積想找新亮點?不妨考慮:動態結構設計。作為深度學習架構搜索與高效建模兩大熱點的結合,動態神經網絡憑借自適應推理與高效特征利用的優勢,在視覺識別、視頻理解等任務中脫穎而出&#…

機器學習之集成算法學習

一、集成學習概述集成學習(ensemble learning)通過構建并結合多個個體學習器來完成學習任務,核心思想是 “集眾家之長”—— 就像多個專家共同判斷往往比單個專家更可靠。其關鍵在于如何生成多樣化的個體學習器并設計有效的結合策略。結合策略…

Unreal Engine UE_LOG

Unreal🎮 Unreal Engine - UE_LOG📝 定義🏛 類/宏關聯? 關鍵特性🛠? 常見配置📚 使用方法🔧 基礎語法🔍 示例🪂 典型應用場景🔗 與其他組件對比?? 常見問題與注意事項…

Halcon那些事:什么是動態閾值,如何用dyn_threshold分割圖片

Halcon那些事:什么是動態閾值,如何用dyn_threshold分割圖片 一、什么是動態閾值?為什么需要它? 1. 傳統全局閾值的局限性 2. 動態閾值的核心思想 二、Halcon 中的核心算子:`dyn_threshold` 1. 算子原型 2. 參數詳解 三、工作原理(數學模型) 四、詳細使用步驟與實例 五、關…

Go初級二

Go初級入門(二):變量、常量與數據類型 大家好,歡迎來到《Go初級入門》系列的第二篇!在上一篇文章中,我們介紹了如何安裝Go環境并運行第一個“Hello, World”程序。今天,我們將深入Go語言的基礎語…

《戰神:諸神黃昏》v1.0.668中文版,索尼大作,PC平臺體驗諸神黃昏

[游戲名稱]: 《戰神:諸神黃昏》v1.0.668中文版 [軟件大小]: 175 GB [軟件大小]: 夸克網盤 游戲介紹 《戰神:諸神黃昏》是由索尼制作并發行的動作冒險游戲,作為《戰神4》的正統續作,它繼續了奎托斯與阿特柔斯的神話之旅。在諸神…

AI賦能環保精準治理:AI水質監測溯源快、空氣質量預測施策準,守護生態新效能

傳統環境保護工作長期受限于 “污染監測滯后”“溯源難度大”“治理方案針對性弱” 的問題,而 AI 技術的深度應用,正讓環保工作從 “被動應對” 轉向 “主動預判”,既能實時捕捉污染蹤跡,還能精準制定治理方案,讓生態保…

yolo訓練實例(一)

yolo官網 https://github.com/ultralytics/ultralytics?tabreadme-ov-file 下載python和解除限制 https://www.python.org/downloads/windows/ Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled"…

STM32-BKP備份寄存器與RTC實時時鐘

引言本文主要從BKP備份寄存器和RTC實時時鐘的原理,特性及應用三個方面展開討論,解析它們在STM32中的獨特價值,助力開發者更好的掌握和運用它們。BKP備份寄存器的定義STM32的BKP備份寄存器是一種特殊的存儲單元,它位于備份區域&…

Linux網絡服務(五)——FTP服務詳解與實踐操作手冊

文章目錄前言一、FTP服務概述1.1 FTP基本定義1.2 VSFTP模式分類1.3 FTP端口作用二、FTP作用與工作原理(重點)2.1 FTP的作用、模式及通信方式2.1.1 FTP核心作用2.1.2 VSFTP模式與通信協議2.2 FTP工作原理與流程2.2.1 主動模式工作原理2.2.2 被動模式工作原…

5.3 包管理工具 npm yarn pnpm 對比

基本介紹 工具發布時間開發者定位npm2010 年npm Inc / OpenJS FoundationNode.js 官方包管理器Yarn2016 年Facebook(現 Meta)更快、更可靠的替代方案pnpm2016 年Zoltan Kochan高性能、節省磁盤空間 一、核心機制與設計差異 1. npm(Node Pa…

爬蟲基礎學習-授權認證,cookie認證,異常處理

驗證: HTTPBasicAuthHandler(用戶基本的身份驗證處理) HTTPPasswordMgrWithDefaultRealm(經常和authhandler一起出現)#創建一個密碼管理器 password_mgr urllib.request.HTTPPasswordMgrWithDefaultRealm() #添加進目…

開發避坑指南(34):mysql深度分頁查詢優化方案

問題語句 SELECT* FROMt_order_log l WHERE1 1 AND l.create_time > 2024-08-28 AND l.create_time < 2024-09-04 23:59:59 LIMIT 10000,10上述查詢sql&#xff0c;即使create_time字段已建立索引&#xff0c;但偏移量達到幾十萬時候&#xff0c;查詢耗時將近1分鐘&…