前言:本文簡述RunLoop相關(guān)內(nèi)容,如有錯誤請留言指正态贤。
Q:什么是RunLoop舱呻?
運行循環(huán),在程序運行過程中循環(huán)做一些事情
Q:RunLoop的應(yīng)用抵卫?
- 定時器(Timer)狮荔、PerformSelector
- GCD Async Main Queue
- 事件響應(yīng)、手勢識別介粘、界面刷新
- 網(wǎng)絡(luò)請求
- AutoreleasePool
Q:RunLoop什么作用殖氏?
- 保持程序的持續(xù)運行
- 處理App中的各種事件(比如觸摸事件、定時器事件等)
- 節(jié)省CPU資源姻采,提高程序性能:該做事時做事雅采,該休息時休息
- ......
Q:RunLoop對象?如何獲取婚瓜?
iOS中有2套API來訪問和使用RunLoop:
- Foundation:NSRunLoop宝鼓,是基于CFRunLoopRef的一層OC包裝
- Core Foundation:CFRunLoopRef,是開源的https://opensource.apple.com/tarballs/CF/
- 二者的內(nèi)存地址是不同的巴刻,可以理解為CFRunLoopRef包含NSRunLoop
獲取RunLoop對象:
1.Foundation:
[NSRunLoop currentRunLoop]; // 獲得當(dāng)前線程的RunLoop對象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象
2.Core Foundation
CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的RunLoop對象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對象
Q:RunLoop與線程之間關(guān)系愚铡?
- 每條線程都有唯一的一個與之對應(yīng)的RunLoop對象
- RunLoop保存在一個全局的Dictionary里,線程作為key胡陪,RunLoop作為value
- 線程剛創(chuàng)建時并沒有RunLoop對象沥寥,RunLoop會在第一次獲取它時創(chuàng)建
- RunLoop會在線程結(jié)束時銷毀
- 主線程的RunLoop已經(jīng)自動獲取(創(chuàng)建)柠座,子線程默認(rèn)沒有開啟RunLoop
RunLoop源碼解讀
CFRunLoop:
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;//線程
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
CFRunLoopMode:
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
}
總結(jié)為:
struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
};
CFRunLoopMode:
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
}
CFRunLoopMode
- CFRunLoopModeRef代表RunLoop的運行模式
- 一個RunLoop包含若干個Mode邑雅,每個Mode又包含若干個Source0/Source1/Timer/Observer
- RunLoop啟動時只能選擇其中一個Mode,作為currentMode
- 如果需要切換Mode妈经,只能退出當(dāng)前Loop淮野,再重新選擇一個Mode進(jìn)入
- 不同組的Source0/Source1/Timer/Observer能分隔開來,互不影響
- 如果Mode里沒有任何Source0/Source1/Timer/Observer吹泡,RunLoop會立馬退出
常見的2種Mode:
- kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默認(rèn)Mode骤星,通常主線程是在這個Mode下運行
- UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動荞胡,保證界面滑動時不受其他 Mode 影響
Q:Source妈踊、Timer、Observer簡單說明泪漂?
Source0
觸摸事件處理
performSelector:onThread:
Source1
基于Port的線程間通信
系統(tǒng)事件捕捉
Source1捕捉,封裝給Source0處理
Timers
NSTimer
performSelector:withObject:afterDelay:
Observers
用于監(jiān)聽RunLoop的狀態(tài)
UI刷新(BeforeWaiting)
Autorelease pool(BeforeWaiting)
Q:CFRunLoopObserverRef幾種狀態(tài)
如下代碼:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),//即將進(jìn)入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1),//即將處理Timer
kCFRunLoopBeforeSources = (1UL << 2),//即將處理Source
kCFRunLoopBeforeWaiting = (1UL << 5),//等待休眠
kCFRunLoopAfterWaiting = (1UL << 6),//休眠中喚醒
kCFRunLoopExit = (1UL << 7),//退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
Q:如何給RunLoop添加Observer歪泳?
{
//創(chuàng)建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 釋放
CFRelease(observer);
}
void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
default:
break;
}
}
快捷方法:
// 創(chuàng)建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry: {
//C/C++中帶有Create和Copy的方法必須釋放
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopEntry - %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopExit: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopExit - %@", mode);
CFRelease(mode);
break;
}
default:
break;
}
});
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 釋放
CFRelease(observer);
Q:RunLoop的運行邏輯萝勤?
01、通知Observers:進(jìn)入Loop
02呐伞、通知Observers:即將處理Timers
03敌卓、通知Observers:即將處理Sources
04、處理Blocks
05伶氢、處理Source0(可能會再次處理Blocks)
06趟径、如果存在Source1,就跳轉(zhuǎn)到第8步
07癣防、通知Observers:開始休眠(等待消息喚醒)
08蜗巧、通知Observers:結(jié)束休眠(被某個消息喚醒)
01> 處理Timer
02> 處理GCD Async To Main Queue
03> 處理Source1
09、處理Blocks
10蕾盯、根據(jù)前面的執(zhí)行結(jié)果幕屹,決定如何操作
01> 回到第02步
02> 退出Loop
11、通知Observers:退出Loop
Q:RunLoop休眠原理?
等待消息
- 沒有消息就讓線程休眠
- 有消息就喚醒線程
RunLoop應(yīng)用:線程蓖希活
Q:如何達(dá)到線程泵斐荆活效果?
@interface ViewController ()
@property (strong, nonatomic) NSThread *thread;
@end
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[MJThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
// 子線程需要執(zhí)行的任務(wù)
- (void)test{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
// 這個方法的目的:線程彼得簦活
- (void)run {
NSLog(@"%s %@", __func__, [NSThread currentThread]);
// 往RunLoop里面添加Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"%s ----end----", __func__);
}
上述代碼會引發(fā)的問題:因為線程不會銷毀導(dǎo)致控制器也不會銷毀鸥跟,initWithTarget方法使線程對控制器強引用了。
線程笨活锌雀、控制銷毀
@interface ViewController ()
@property (strong, nonatomic) MJThread *thread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.stopped = NO;
self.thread = [[MJThread alloc] initWithBlock:^{
NSLog(@"%@----begin----", [NSThread currentThread]);
// 往RunLoop里面添加Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (!weakSelf.isStoped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"%@----end----", [NSThread currentThread]);
// NSRunLoop的run方法是無法停止的,它專門用于開啟一個永不銷毀的線程(NSRunLoop)
// [[NSRunLoop currentRunLoop] run];
/*
it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:.
In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers
*/
}];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
// 子線程需要執(zhí)行的任務(wù)
- (void)test{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (void)stop {
// 在子線程調(diào)用stop
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}
// 用于停止子線程的RunLoop
- (void)stopThread{
// 設(shè)置標(biāo)記為NO
self.stopped = YES;
// 停止RunLoop
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}