一、什么是RunLoop
基本作用:
保持程序的持續(xù)運行蒿柳;
處理App中的各種事件(比如觸摸事件饶套、定時器事件、Selector事件)
節(jié)省CPU資源垒探,提高程序性能:該做事時候做事妓蛮,該休息時候休息。
main函數(shù)中的RunLoop:
UIApplicationMain函數(shù)內部就啟動了一個RunLoop圾叼;
所以UIApplicationMain函數(shù)一直沒有返回蛤克,保持了程序的持續(xù)運行扔仓;
這個默認啟動的RunLoop是跟主線程相關聯(lián)的。
RunLoop對象
-
iOS中有2套API來訪問和使用RunLoop
- Foundation中:
NSRunLoop
咖耘。 - Core Foundation中:CFRunLoopRef。
- Foundation中:
NSRunLoop
和CFRunLoopRef都代表著RunLoop對象撬码;NSRunLoop
是基于CFRunLoopRef的一層OC包裝儿倒,所以要了解RunLoop內部結構,需要多研究CFRunLoopRef層面的API(Core Foundation層面)RunLoop與線程:
每條線程都有唯一的一個與之對應的RunLoop對象呜笑;
主線程的RunLoop已經(jīng)自動創(chuàng)建好了夫否,子線程的RunLoop需要主動創(chuàng)建;
RunLoop在第一次獲取時創(chuàng)建叫胁,在線程結束時銷毀凰慈。
獲取RunLoop對象:
Foundation:
[NSRunLoop currentRunLoop] //獲得當前線程的NSRunLoop對象
[NSRunLoop mainRunLoop] //獲得主線程的NSRunLoop對象
- Core Foundation
CFRunLoopGetCurrent() //獲得當前線程的RunLoop對象
CFRunLoopGetMain() //獲得主線程的RunLoop對象
二、RunLoop相關類:
Core Foundation中關于RunLoop的5個類:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
-
CFRunLoopObserverRef
CFRunLoopModeRef:
CFRunLoopModeRef代表RunLoop的運行模式驼鹅;
一個RunLoop包含若干個Mode微谓,每個Mode又包含若干個Source/Timer/Observer;
每次RunLoop啟動時,只能指定其中一個Mode输钩,這個Mode被稱作CurrentMode豺型;
如果需要切換Mode,只能退出Loop买乃,再重新指定一個Mode進入姻氨。
這樣做主要是為了分割開不同組的Source/Timer/Observer,讓其互不影響剪验。
-
系統(tǒng)默認注冊了5個Mode:
-
kCFRunLoopDefaultMode
:App默認的Mode,通常主線程是在這個Mode下運行肴焊; -
UITrackingRunLoopMode
:界面跟蹤Mode,用于ScrollView追蹤觸摸滑動功戚,保證界面滑動時不受其他Mode影響 - UIInitializationRunLoopMode:在剛啟動App時進入的第一個Mode娶眷,啟動完成后就不再使用
- GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內部Mode,通常用不到
-
kCFRunLoopCommonModes
:這是一個占位用的Mode疫铜,不是一種真正的Mode
-
CFRunLoopTimerRef:
CFRunLoopTimerRef是基于時間的觸發(fā)器茂浮;
基本上說就是NSTimer,它受RunLoop的Mode影響壳咕;
GCD的定時器不受RunLoop的Mode影響席揽。
//調用scheduledTimer返回的定時器,已經(jīng)自動被添加到當前的runloop中谓厘,而且模式為NSDefaultRunLoopMode幌羞,一旦RunLoop進入其他模式,這個定時器就不會工作竟稳∈翳耄可以通過返回值進行模式修改
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//以下兩行代碼和上一句代碼效果相同熊痴,當發(fā)生拖拽行為時,就停止調用run方法
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//只有在拖拽情況下才調用run方法聂宾,定時器只在UITrackingRunLoopMode模式下工作
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//定時器跑在標記為CommonModes的模式下果善,CommonModes包含:UITrackingRunLoopMode、kCFRunLoopDefaultMode
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
CFRunLoopSourceRef:一般都由系統(tǒng)確定系谐,了解
CFRunLoopSourceRef是事件源(輸入源)
-
按照官方文檔巾陕,Source的分類
- Port-Based Sources
- Custom Input Source
- Cocoa Perform Selector Sources
-
按照函數(shù)調用棧,Source的分類:
- Source0:非基于Port的
- Source1:基于Port的纪他,通過內核和其他線程通信鄙煤,接受、分發(fā)系統(tǒng)事件
CFRunLoopObserverRef:
CFRunLoopObserverRef是觀察者茶袒,能夠監(jiān)聽RunLoop的狀態(tài)改變梯刚;
可以監(jiān)聽的時間點有以下幾個:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
//創(chuàng)建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"observer:監(jiān)聽到RunLoop狀態(tài)發(fā)生改變-----%lu",activity);
});
//添加觀察者:監(jiān)聽RunLoop的狀態(tài)
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode
//釋放observer
CFRelease(observer);
- CF的內存管理(Core Foundation)
- 凡是帶有Create、Copy薪寓、Retain等字眼的函數(shù)亡资,創(chuàng)建出來的對象,都需要在最后做一次release操作向叉,比如:CFStringCreate沟于、CFURLCopy等;
- release函數(shù):CFRelease(對象)植康。
三旷太、RunLoop處理邏輯
-
RunLoop處理邏輯官方版
-
首先判斷Mode是否為空,如果不為空就按照下圖的邏輯執(zhí)行:
四销睁、RunLoop應用
NSTimer
ImageView顯示
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"place"] afterDelay:2.0 inModes:@[NSRunLoopCommonModes]];
PerformSelector
常駐線程
在線程里面添加runloop(保證線程不死thread->runloop->mode->source(performSelector))
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
- 在子線程保留常駐線程定時做一些操作
- 在主線程創(chuàng)建timer供璧,會自動將timer添加主runloop;
- 方法一:
-(void)viewDidLoad {
[super viewDidLoad];
_thread = [[NSThread alloc] initWithTarget:self selector:@selector(excute) object:nil];
[_thread start];
}
-(void)excute
{
//首先創(chuàng)建一個timer冻记,然后將timer添加到runloop睡毒,再啟動runloop
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}
- 方法二:通過調用scheduledTimerWithTimeInterval方法,會自動將timer添加到當前runloop中冗栗,然后再啟動runloop即可:
-(void)viewDidLoad {
[super viewDidLoad];
_thread = [[NSThread alloc] initWithTarget:self selector:@selector(excute) object:nil];
[_thread start];
}
-(void)excute
{
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
- 自動釋放池
- 將一些對象放到釋放池演顾,等釋放池清理時會讓池子里面所有的對象調一下release方法;
- 在beforeWait(休眠)之前釋放池子隅居。
RunLoop常見問題
什么是RunLoop钠至?
從字面意思看:運行循環(huán)、跑圈胎源;
其實它內部就是do-while循環(huán)棉钧,在這個循環(huán)內部不斷地處理各種任務(比如Source、Timer涕蚤、Observer)宪卿;
一個線程對應一個RunLoop的诵,主線程的RunLoop默認已經(jīng)啟動,子線程的RunLoop得手動啟動(調用run方法)佑钾;
RunLoop只能選擇一個Mode啟動西疤,如果當前Mode中沒有任何Source、Timer休溶、Observer瘪阁,那么就直接退出RunLoop。
你在開發(fā)過程中怎么使用RunLoop邮偎?什么應用場景?
-
開啟一個常駐線程(讓一個子線程不進入消亡狀態(tài)义黎,等待其他線程發(fā)來消息禾进,處理其他時間):
- 在子線程中開啟一個定時器;
- 在子線程中進行一些長期監(jiān)控廉涕。
可以控制定時器在特定模式下執(zhí)行泻云;
可以讓某些事件(行為、任務)在特定模式下執(zhí)行狐蜕;
可以添加Observer監(jiān)聽RunLoop的狀態(tài)宠纯,比如監(jiān)聽點擊事件的處理(在所有點擊事件之前做一些事情);
還可以自定義源层释。
自動釋放池是什么婆瓜?
在RunLoop睡眠之前釋放(kCFRunLoopBeforeWaiting)