今天博主有一個RunLoop的需求,遇到了一些困難點,在此和大家分享,希望能夠共同進步.
RunLoop是什么?
從字面上理解,Runloop指的就是運行循環,iOS中,只要程序啟動, 就會創建Runloop,用來處理各種事件(比如定時器事件, 觸摸事件等).
它的主要作用有以下幾個:
1.保持程序的運行:如果沒有它, 程序一啟動就over了;
2.事件的處理: 包括按鈕點擊事件, 屏幕的點擊事件等;
3.定時器處理: NSTimer的處理;
4.節約CPU, 提高性能:有任務的時候干活,沒有任務時候休息.
在iOS中, 可以通過兩套API來訪問Runloop:
1.Foundation框架: OC中的框架, 直接訪問NSRunLoop對象.
2.Core Foundation框架: c語言的框架, 通過CFRunLooPRef訪問.
實際上, NSRunLoop和CFRunLoopRef是同一個對象, 只不過通過不同的API來訪問的,NSRunLoop是對CFRunLoopRef的封裝.
OC中, 使用下面代碼就可以獲得Runloop對象:
// Foundation
[NSRunLoop currentRunLoop]; // 獲得當前線程的RunLoop對象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象
// Core Foundation
CFRunLoopGetCurrent(); // 獲得當前線程的RunLoop對象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對象
Nslog打印Runloop對象, 里面的內容如下:(由于內容比較多, 只將重點內容列出)
// runloop對象 <CFRunLoop 0x7fb13172abc0 [0x10a5aa180]>{wakeup port = 0x1003, stopped = false, ignoreWakeUps = true,// 當前模式current mode = UIInitializationRunLoopMode,common modes = <CFBasicHash 0x7fb13172ac80// common modes標記了哪兩個模式(后面會講為啥是標記?) 0 : <CFString 0x10b27ce50 [0x10a5aa180]>{contents = "UITrackingRunLoopMode"} 2 : <CFString 0x10a586080 [0x10a5aa180]>{contents = "kCFRunLoopDefaultMode"}}// source: 源6 : <CFRunLoopSource 0x7fb131415300 [0x10a5aa180]>// observers: 觀察者observers = <CFArray 0x7fb13141d300 [0x10a5aa180]>// timer: 定時器timers = (null),// 兩種不同的模式3 : <CFRunLoopMode 0x7fb13172dbb0 [0x10a5aa180]>{name = GSEventReceiveRunLoopMode, port set = 0x2003, timer port = 0x2103, sources0 = <CFBasicHash 0x7fb13172d260 [0x10a5aa180]>{type = mutable set, count = 1,entries => 0 : <CFRunLoopSource 0x7fb13172da30 [0x10a5aa180]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10d8236c8)}}}
要想知道上面內容是什么意思, 先看下面的內容:
Core Foundation中關于RunLoop的5個類1.CFRunLoopRef2.CFRunLoopModeRef3.CFRunLoopSourceRef4.CFRunLoopTimerRef5.CFRunLoopObserverRef
可以這理解為, 一個Runloop對象里面, 必不可少的是CFRunLoopModeRef, Mode里面可以包含timer/source/oberver(只有包含了其中一個, Runloop才能在啟動后保證不死)
層級結構如下圖:
接下來, 對里面的內容說明一下:
一.CFRunLoopModeRef:代表RunLoop的運行模式 1.一個RunLoop包含若干個Mode,每個Mode又包含若干個Source/Timer/Observer 2.每次RunLoop啟動時,只能指定其中一個 Mode,這個Mode被稱作CurrentMode 3.這樣做主要是為了分隔開不同組的Source/Timer/Observer,讓其互不影響系統默認注冊了5個Mode: 1.kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行 2.UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響 3.UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用 4.GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,通常用不到 5.kCFRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode, 被標記為kCFRunLoopCommonModes格式的都可以成為commonMode, 其中包含1.2兩種模式.二.CFRunLoopSourceRef:是事件源(輸入源) 現在主要分為兩種: Source0:非基于Port的 Source1:基于Port的(如GCD底層實現也是通過Port的, 主要用于線程之間的通信)三.CFRunLoopTimerRef:是基于時間的觸發器, 基本上說的就是NSTimer四.CFRunLoopObserverRef:觀察者, 可以用來監聽Runloop的狀態, 下面列出了Runloop的狀態typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), // 即將進入 kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理timer kCFRunLoopBeforeSources = (1UL << 2), // 即將處理事件源 kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進入休眠 kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠中喚醒 kCFRunLoopExit = (1UL << 7), // 即將退出Runloop};
下面圖片描述了Runloop的運行和狀態:
Runloop的寄生于線程:一個線程只能有唯一對應的runloop;但這個根runloop里可以嵌套子runloops;
自動釋放池寄生于Runloop:程序啟動后,主線程注冊了兩個Observer監聽runloop的進出與睡覺。一個最高優先級OB監測Entry狀態;一個最低優先級OB監聽BeforeWaiting狀態和Exit狀態。
線程(創建)-->runloop將進入-->最高優先級OB創建釋放池-->runloop將睡-->最低優先級OB銷毀舊池創建新池-->runloop將退出-->最低優先級OB銷毀新池-->線程(銷毀)
Mach是XNU的內核,進程、線程和虛擬內存等對象通過端口發消息進行通信,Runloop通過mach_msg()函數發送消息,如果沒有port 消息,內核會將線程置于等待狀態 mach_msg_trap() 。如果有消息,判斷消息類型處理事件,并通過modeItem的callback回調。
Runloop有兩個關鍵判斷點,一個是通過msg決定Runloop是否等待,一個是通過判斷退出條件來決定Runloop是否循環。
應用1:滑動與圖片刷新:
當tableview的cell上有需要從網絡獲取的圖片的時候,滾動tableView,異步線程會去加載圖片,加載完成后主線程就會設置cell的圖片,但是會造成卡頓??梢宰屧O置圖片的任務在CFRunLoopDefaultMode下進行,當滾動tableView的時候,RunLoop是在 UITrackingRunLoopMode 下進行,不去設置圖片,而是當停止的時候,再去設置圖片。
- (void)viewDidLoad {
[super viewDidLoad];
// 只在NSDefaultRunLoopMode下執行(刷新圖片)
[self.myImageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@""] afterDelay:ti inModes:@[NSDefaultRunLoopMode]];
}
應用2:常駐子線程,保持子線程一直處理事件
為了保證線程長期運轉,可以在子線程中加入RunLoop,并且給Runloop設置item,防止Runloop自動退出。
+ (void)networkRequestThreadEntryPoint:(id)__unused object { @autoreleasepool { [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; }}+ (NSThread *)networkRequestThread { static NSThread *_networkRequestThread = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; [_networkRequestThread start]; }); return _networkRequestThread;}- (void)start { [self.lock lock]; if ([self isCancelled]) { [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } else if ([self isReady]) { self.state = AFOperationExecutingState; [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } [self.lock unlock];}
http://www.49028c.com/66it/p/4719701.html?utm_source=tuicool
http://blog.ibireme.com/2015/05/18/runloop/
新聞熱點
疑難解答