一 什么是Runloop
二 Runloop的運行邏輯
三 Runloop在實際開發(fā)中的應(yīng)用
一 什么是Runloop
1.1 Runloop的基本認識
RunLoop是一個接收處理異步消息事件的循環(huán)悄晃,一個循環(huán)中:等待事件發(fā)生最蕾,然后將這個事件送到能處理它的地方辈双。
1.1.1 它的定義有兩個點:
1 它是一個運行循環(huán)赎懦,like while(1),它是保證程序一直運行不退出的基本條件
2 在這個循環(huán)里面有事情時它就會處理,沒事情時它就休息累魔,既保證了一些有些操作它能及時相應(yīng)屎飘,又保證了沒活干時不浪費系統(tǒng)資源。
1.1.2 那么Runloop它在app中都有哪些作用秒裕?
1 保持程序的持續(xù)運行
2 事件響應(yīng)袱蚓、手勢識別、界面刷新
3 AutoreleasePool自動釋放池
4 定時器(Timer)几蜻、PerformSelector 定時器喇潘,延遲執(zhí)行操作
5 GCD Async Main Queue 線程主隊列里的操作
6節(jié)省CPU資源,提高程序性能:該做事時做事梭稚,該休息時休息
1.2 runloop底層相關(guān)對象介紹
iOS中有2套API來訪問和使用RunLoop
Foundation:NSRunLoop
Core Foundation:CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表著RunLoop對象
NSRunLoop是基于CFRunLoopRef的一層OC包裝
CFRunLoopRef是開源的
https://opensource.apple.com/tarballs/CF/
Core Foundation中關(guān)于RunLoop的有5個類
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
下面我們來逐一介紹
1.2.1 CFRunLoopRef RunLoop對象
CFRunLoop.h
typedef struct __CFRunLoop * CFRunLoopRef;
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; //通用模式標(biāo)記的任務(wù)
CFRunLoopModeRef _currentMode; //當(dāng)前模式
CFMutableSetRef _modes; //所有模式列表
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
下面我們介紹一下RunLoop對象里幾個主要的字段:
1.2.2 pthread_t _pthread
線程對象颖低,一個runloop對象里面就有一個線程對象,RunLoop是來管理線程的,當(dāng)線程的RunLoop被開啟后弧烤,線程會在執(zhí)行完任務(wù)后進入休眠狀態(tài)忱屑,有了任務(wù)就會被喚醒去執(zhí)行任務(wù).稍后我們再詳細講解線程方面的東西
1.2.3 CFMutableSetRef _commonModes
通用模式,首先什么是模式,就是一種運行環(huán)境莺戒,就像聽歌伴嗡,有單曲模式,循環(huán)模式从铲,隨機模式瘪校,runloop也有運行模式,界面靜止處于一種模式名段,界面滑動又處于另一種模式阱扬,模式之間相互獨立互不影響,runloop 同時只能在一種模式下運行伸辟,這就保證了界面滾動滑動時麻惶,就處于一種滑動模式,專門干這件事信夫,其他啥也不干窃蹋,界面就會滑動很流暢。
1.2.4 CFMutableSetRef _commonModeItems;
如果一個任務(wù)運行在_commonModes下運行那么它就會被加入到_commonModeItems里面比如一個定時器為了保證滑動不卡住静稻,我們會讓它在UITrackingRunLoopMode也能運行脐彩,這就需要它在_commonModes下運行,那么這個定時器就會被加入到_commonModeItems里姊扔,runloop在處理這個定時器任務(wù)時,它就會從_commonModeItems里找出這個任務(wù)去執(zhí)行
1.2.5 CFRunLoopModeRef _currentMode
runloop當(dāng)前所處的模式
1.2.6 CFMutableSetRef _modes
RunLoop所有運行模式列表梅誓,它主要有以下5個:
kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
UITrackingRunLoopMode:界面跟蹤Mode,用于ScrollView追蹤觸摸滑動恰梢,保證界面滑動時不受其他Mode影響
kCFRunLoopCommonModes:這是一個占位用的Mode,不是一種真正的Mode梗掰,它是kCFRunLoopDefaultMode,UITrackingRunLoopMode 并集的標(biāo)識嵌言。
UIInitializationRunLoopMode:在剛啟動App時進入的第一個Mode,啟動完成后不再使用
GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內(nèi)部Mode及穗,通常用不到
下面讓我們來看看具體的模式是什么樣的
1.2.7 RunLoop通用模式有兩種:
1 KCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默認Mode摧茴,通常主線程是在這個Mode下運行
2 UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動埂陆,保證界面滑動時不受其他 Mode 影響
接下來讓我們看看模式對象里面都有些什么
CFRunLoopModeRef
CFRunLoop.c
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
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; // 事件源0
CFMutableSetRef _sources1; // 事件源1
CFMutableArrayRef _observers; //觀察者
CFMutableArrayRef _timers; //定時器
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
一個RunLoop包含若干個Mode苛白,每個Mode又包含若干個Source0/Source1/Timer/Observer下面我們解釋下這幾個字段具體作用
1 Source0
觸摸事件處理
performSelector:onThread:
2 Source1
基于Port的線程間通信
系統(tǒng)事件捕捉
3 Timers
NSTimer
performSelector:withObject:afterDelay:
4 Observers
用于監(jiān)聽RunLoop的狀態(tài)
UI刷新(BeforeWaiting)
Autorelease pool(BeforeWaiting)
下面我們總結(jié)下模式結(jié)構(gòu),如圖:
- 1 RunLoop啟動時只能選擇其中一個Mode焚虱,作為currentMode
- 2 如果需要切換Mode购裙,只能退出當(dāng)前Loop,再重新選擇一個Mode進入
- 3 不同組的Source0/Source1/Timer/Observer能分隔開來鹃栽,互不影響
- 4 如果Mode里沒有任何Source0/Source1/Timer/Observer躏率,RunLoop會立馬退出
1.3 CFRunLoopSourceRef
runloop.c
typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
事件源對象
1 Source0
觸摸事件處理,點擊滑動等
performSelector:onThread: // 在指定一個線程執(zhí)行任務(wù)
2 Source1
基于Port的線程間通信
系統(tǒng)事件捕捉
1.4 CFRunLoopTimerRef
runloop.c
__CFRunLoopTimer * CFRunLoopTimerRef;
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFMutableSetRef _rlModes;
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval; /* immutable */
CFTimeInterval _tolerance; /* mutable */
uint64_t _fireTSR; /* TSR units */
CFIndex _order; /* immutable */
CFRunLoopTimerCallBack _callout; /* immutable */
CFRunLoopTimerContext _context; /* immutable, except invalidation */
};
定時器,就是定時執(zhí)行一個任務(wù)薇芝,也是在runloop中處理的
1.5 CFRunLoopObserverRef
runloop.c
typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */
CFIndex _order; /* immutable */
CFRunLoopObserverCallBack _callout; /* immutable */
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};
CFRunLoopObserverRef是觀察者蓬抄,能夠監(jiān)聽RunLoop的狀態(tài)改變,如下:
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即將進入runloop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即將處理 Timer");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即將處理 Sources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即將進入休眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"從休眠中喚醒loop");
break;
case kCFRunLoopExit:
NSLog(@"即將退出runloop");
break;
default:
break;
}
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(),observer,kCFRunLoopDefaultMode);
/// 這里可以自己寫一個NSTimer實驗一下
// 釋放runloop
CFRelease(observer);
runloop 狀態(tài)主要又如下幾種:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即將進入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer
kCFRunLoopBeforeSources = (1UL << 2),//即將處理source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進入休眠
kCFRunLoopAfterWaiting = (1UL << 6), //剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), //即將退出Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU// 包含所有以上狀態(tài)
};
以上就是RunLoop相關(guān)對象的介紹夯到,下面我們來講一下RunLoop的運行邏輯嚷缭。
二 Runloop的運行邏輯
官網(wǎng)上有一張圖是這樣的:
這張圖講了RunLoop 循環(huán)處理source0 source1 timer事件,他并沒有講清楚具體的執(zhí)行步驟黄娘,以及線程休眠的細節(jié)峭状,接下來看一張詳細的執(zhí)行圖:
它的具體執(zhí)行步驟如下:
01、通知Observers:進入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
下面讓我們看一下越平,簡化的源碼執(zhí)行流程
// 共外部調(diào)用的公開的CFRunLoopRun方法频蛔,其內(nèi)部會調(diào)用CFRunLoopRunSpecific
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
// 經(jīng)過精簡的 CFRunLoopRunSpecific 函數(shù)代碼,其內(nèi)部會調(diào)用__CFRunLoopRun函數(shù)
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
// 通知Observers : 進入Loop
// __CFRunLoopDoObservers內(nèi)部會調(diào)用 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
函數(shù)
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 核心的Loop邏輯
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知Observers : 退出Loop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
// 精簡后的 __CFRunLoopRun函數(shù)秦叛,保留了主要代碼
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
do {
// 通知Observers:即將處理Timers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知Observers:即將處理Sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 處理Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 處理Sources0
if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
// 處理Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
// 如果有Sources1晦溪,就跳轉(zhuǎn)到handle_msg標(biāo)記處
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
// 通知Observers:即將休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 進入休眠,等待其他消息喚醒
__CFRunLoopSetSleeping(rl);
__CFPortSetInsert(dispatchPort, waitSet);
do {
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
} while (1);
// 醒來
__CFPortSetRemove(dispatchPort, waitSet);
__CFRunLoopUnsetSleeping(rl);
// 通知Observers:已經(jīng)喚醒
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg: // 看看是誰喚醒了RunLoop挣跋,進行相應(yīng)的處理
if (被Timer喚醒的) {
// 處理Timer
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
}
else if (被GCD喚醒的) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else { // 被Sources1喚醒的
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
}
// 執(zhí)行Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 根據(jù)之前的執(zhí)行結(jié)果三圆,來決定怎么做,為retVal賦相應(yīng)的值
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
return retVal;
}
這里面有幾個細節(jié)我們講一下:
1 線程休眠
Runloop的本質(zhì)就是一個do,while循環(huán)避咆,當(dāng)有事做時做事舟肉,沒事做時休眠。怎么休眠查库,這個是由系統(tǒng)內(nèi)核來調(diào)度的度气,是真的休眠了cup不占用資源了。
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout, voucher_mach_msg_state_t *voucherState, voucher_t *voucherCopy) {
Boolean originalBuffer = true;
kern_return_t ret = KERN_SUCCESS;
for (;;) { /* In that sleep of death what nightmares may come ... */
mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
msg->msgh_bits = 0;
msg->msgh_local_port = port;
msg->msgh_remote_port = MACH_PORT_NULL;
msg->msgh_size = buffer_size;
msg->msgh_id = 0;
if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);
// Take care of all voucher-related work right after mach_msg.
// If we don't release the previous voucher we're going to leak it.
voucher_mach_msg_revert(*voucherState);
// Someone will be responsible for calling voucher_mach_msg_revert. This call makes the received voucher the current one.
*voucherState = voucher_mach_msg_adopt(msg);
if (voucherCopy) {
if (*voucherState != VOUCHER_MACH_MSG_STATE_UNCHANGED) {
// Caller requested a copy of the voucher at this point. By doing this right next to mach_msg we make sure that no voucher has been set in between the return of mach_msg and the use of the voucher copy.
// CFMachPortBoost uses the voucher to drop importance explicitly. However, we want to make sure we only drop importance for a new voucher (not unchanged), so we only set the TSD when the voucher is not state_unchanged.
*voucherCopy = voucher_copy();
} else {
*voucherCopy = NULL;
}
}
CFRUNLOOP_WAKEUP(ret);
if (MACH_MSG_SUCCESS == ret) {
*livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
return true;
}
if (MACH_RCV_TIMED_OUT == ret) {
if (!originalBuffer) free(msg);
*buffer = NULL;
*livePort = MACH_PORT_NULL;
return false;
}
if (MACH_RCV_TOO_LARGE != ret) break;
buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
if (originalBuffer) *buffer = NULL;
originalBuffer = false;
*buffer = realloc(*buffer, buffer_size);
}
HALT;
return false;
}
ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);
2 runloop和線程的關(guān)系
1 每條線程都有唯一的一個與之對應(yīng)的RunLoop對象
2 RunLoop保存在一個全局的Dictionary里膨报,線程作為key磷籍,RunLoop作為value
3 線程剛創(chuàng)建時并沒有RunLoop對象适荣,RunLoop會在第一次獲取它時創(chuàng)建
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
// CFRunLoopRef runloop2 = CFRunLoopGetCurrent();
4 RunLoop會在線程結(jié)束時銷毀
5 主線程的RunLoop已經(jīng)自動獲取(創(chuàng)建)院领,子線程默認沒有開啟RunLoop
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); //里面會創(chuàng)建主線程弛矛,并開啟RunLoop
}
}
我們講完了runloop具體執(zhí)行流程,那么在平時開發(fā)中有哪些應(yīng)用呢比然,我們簡單的來看一下
三 Runloop在實際開發(fā)中的應(yīng)用
- 控制線程生命周期(線程闭擅ィ活)
- 解決NSTimer在滑動時停止工作的問題
- 監(jiān)控應(yīng)用卡頓
- 性能優(yōu)化
下面我們簡單的舉兩個例子
3.1 控制線程生命周期(線程保活)
#import <Foundation/Foundation.h>
typedef void (^MyPermenantThreadTask)(void);
@interface MyPermenantThread : NSObject
/**
開啟線程
*/
- (void)run;
/**
在當(dāng)前子線程執(zhí)行一個任務(wù)
*/
- (void)executeTask:(MyPermenantThreadTask)task;
/**
結(jié)束線程
*/
- (void)stop;
@end
#import "MyPermenantThread.h"
/** MJThread **/
@interface MyThread : NSThread
@end
@implementation MyThread
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
/** MJPermenantThread **/
@interface MyPermenantThread()
@property (strong, nonatomic) MyThread *innerThread;
@property (assign, nonatomic, getter=isStopped) BOOL stopped;
@end
@implementation MyPermenantThread
#pragma mark - public methods
- (instancetype)init
{
if (self = [super init]) {
self.stopped = NO;
__weak typeof(self) weakSelf = self;
self.innerThread = [[MyThread alloc] initWithBlock:^{
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; //如果Mode里沒有任何Source0/Source1/Timer/Observer强法,RunLoop會立馬退出 所以為了不讓它退出我們加了個source1 你也可以加其他的
while (weakSelf && !weakSelf.isStopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}];
[self.innerThread start];
}
return self;
}
- (void)run
{
if (!self.innerThread) return;
[self.innerThread start];
}
- (void)executeTask:(MyPermenantThreadTask)task
{
if (!self.innerThread || !task) return;
[self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}
- (void)stop
{
if (!self.innerThread) return;
[self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self stop];
}
#pragma mark - private methods
- (void)__stop
{
self.stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
self.innerThread = nil;
}
- (void)__executeTask:(MyPermenantThreadTask)task
{
task();
}
@end
#import "ViewController.h"
#import "MyPermenantThread.h"
@interface ViewController ()
@property (strong, nonatomic) MJPermenantThread *thread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[MJPermenantThread alloc] init];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self.thread executeTask:^{
NSLog(@"執(zhí)行任務(wù) - %@", [NSThread currentThread]);
}];
}
- (IBAction)stop {
[self.thread stop];
}
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
我們利用RunLoop的線程休眠機制實現(xiàn)了一個沒任務(wù)就會休眠的線程MyPermenantThread万俗, 只要你不調(diào)用stop 你就可以重復(fù)的使用這個線程來執(zhí)行其他任務(wù),很適合做大量串行執(zhí)行的子任務(wù)饮怯,比如說有依賴的大量網(wǎng)絡(luò)請求等闰歪。
3.2 解決NSTimer在滑動時停止工作的問題
static int number = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", ++number);
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
通常我們寫一個timer
[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", ++count);
}];
這個時候timer默認處于NSDefaultRunLoopMode模式當(dāng)我們滑動頁面的時候RunLoop 會切換到UITrackingRunLoopMode模式,這樣我們的timer就停止工作了蓖墅,就像商城秒殺倒計時库倘,滑動頁面時倒計時就停止了,為了解決這個問題我們要讓timer在UITrackingRunLoopMode下也能工作论矾,所以我們給它指定了NSRunLoopCommonModes模式教翩,這個模式標(biāo)記這個timer可以在NSDefaultRunLoopMode、UITrackingRunLoopMode模式下運行贪壳。
3.3 監(jiān)控應(yīng)用卡頓
通過對 RunLoop 原理的分析饱亿,我們可以看出在整個過程中,loop 的狀態(tài)包括 6 個闰靴,其代碼定義 如下:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry , // 進入 loop
kCFRunLoopBeforeTimers , // 觸發(fā) Timer 回調(diào)
kCFRunLoopBeforeSources , // 觸發(fā) Source0 回調(diào)
kCFRunLoopBeforeWaiting , // 等待 mach_port 消息
kCFRunLoopAfterWaiting ), // 接收 mach_port 消息
kCFRunLoopExit , // 退出 loop
kCFRunLoopAllActivities // loop 所有狀態(tài)改變
}
如果 RunLoop 的線程路捧,進入睡眠前方法的執(zhí)行時間過長而導(dǎo)致無法進入睡眠,或者線程喚醒后 接收消息時間過長而無法進入下一步的話传黄,就可以認為是線程受阻了。如果這個線程是主線程的話队寇,表現(xiàn)出來的就是出現(xiàn)了卡頓膘掰。
所以,如果我們要利用 RunLoop 原理來監(jiān)控卡頓的話佳遣,就是要關(guān)注這兩個階段识埋。RunLoop 在進 入睡眠之前和喚醒后的兩個 loop 狀態(tài)定義的值分別是 kCFRunLoopBeforeSources 和
kCFRunLoopAfterWaiting ,也就是要觸發(fā) Source0 回調(diào)和接收 mach_port 消息兩個狀態(tài)零渐。
具體實現(xiàn)代碼如下:
實現(xiàn)代碼
好了我就講這個兩個例子至于監(jiān)控應(yīng)用卡頓窒舟,性能優(yōu)化的應(yīng)用,網(wǎng)上資料很多的诵盼,可自行查閱惠豺。