iOS底層原理 RunLoop基礎總結和爲所欲爲掌握子線程RunLoop生命週期 --(9)

上篇文章講了runtime的簡單應用,使用鉤子實現了對字典和數組的賦值的校驗,順便隨手擼了一個簡單的jsonToModel,iOS除了runtime還有一個東西的叫作runloop,各位看官老爺必定都有了解,那麼今天這篇文章初識一下runloophtml

什麼是runloop

簡單來說runloop就是一個循環,咱們寫的程序,通常沒有循環的話,執行完就結束了,那麼咱們手機上的APP是如何一直運行不中止的呢?APP就是用到了runloop,保證程序一直運行不退出,在須要處理事件的時候處理事件,不處理事件的時候進行休眠,跳出循環程序就結束。用僞代碼實現一個runloop實際上是這樣子的ios

int ret = 0;
do {
    //睡眠中等待消息
    int messgae = sleep_and_wait();
    //處理消息
    ret = process_message(messgae);
} while (ret == 0);
複製代碼

獲取runloop

iOS中有兩套能夠獲取runloop代碼,一個是Foundation、一個是Core FoundationFoundation實際上是對Core Foundation的一個封裝,c++

NSRunLoop * runloop1 = [NSRunLoop currentRunLoop];
NSRunLoop *mainloop1 = [NSRunLoop mainRunLoop];

CFRunLoopRef runloop2= CFRunLoopGetCurrent();
CFRunLoopRef mainloop2 = CFRunLoopGetMain();
NSLog(@"%p %p %p %p",runloop1,mainloop1,runloop2,mainloop2);
NSLog(@"%@",runloop1);
//打印
runlopp1:0x600001bc58c0 
mainloop1:0x600001bc58c0 
runloop2:0x6000003cc300 
mainloop1:0x6000003cc300

runloop1:<CFRunLoop 0x6000003cc300 [0x10b2e9ae8]>.....
複製代碼

runloop1mainloop1地址一致,說明當前的runloopmainrunloop,runloop1做爲對象輸出的結果其實也是runloop2的地址,證實Foundation runloop是對Core Foundation的一個封裝。git

RunLoop底層咱們猜想應該是結構體,咱們都瞭解到其實OC就是封裝了c/c++,那麼c厲害之處就是指針和結構體基本解決經常使用的全部東西。咱們窺探一下runloop的真是模樣,經過CFRunLoopRef *runloop = CFRunLoopGetMain();查看CFRunlooptypedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;,咱們經常使用的CFRunLoopRef__CFRunLoop *類型的,那麼再在源碼(能夠下載最新的源碼)中搜索一下 struct __CFRunLoop {runloop.c 637行以下所示:github

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;			/* model list 鎖 */
    __CFPort _wakeUpPort;			// 接受 CFRunLoopWakeUp的端口
    Boolean _unused;//是否使用
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread; //線程
    uint32_t _winthread;//win線程
    CFMutableSetRef _commonModes; //modes
    CFMutableSetRef _commonModeItems; //modeItems
    CFRunLoopModeRef _currentMode; //當前的mode
    CFMutableSetRef _modes; //全部的modes
    struct _block_item *_blocks_head; //待執行的block列表頭部
    struct _block_item *_blocks_tail; //待執行的block 尾部
    CFAbsoluteTime _runTime; //runtime
    CFAbsoluteTime _sleepTime; //sleeptime
    CFTypeRef _counterpart; //
};
複製代碼

通過簡化以後:web

struct __CFRunLoop {
    pthread_t _pthread; //線程
    CFMutableSetRef _commonModes; //modes
    CFMutableSetRef _commonModeItems; //modeItems
    CFRunLoopModeRef _currentMode; //當前的mode
    CFMutableSetRef _modes; //全部的modes
}
複製代碼
  1. runloop中包含一個線程_pthread,一一對應的
  2. CFMutableSetRef _modes能夠有多個mode
  3. CFRunLoopModeRef _currentMode當前mode只能有一個

那麼mode裏邊有什麼內容呢?咱們猜想他應該和runloop相似,在源碼中搜索CFRuntimeBase _base看到在runloop.c line 524看到具體的內容:json

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;
#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 */
};
複製代碼

通過簡化以後是:數組

struct __CFRunLoopMode {
    CFStringRef _name;//當前mode的名字
    CFMutableSetRef _sources0;//souces0
    CFMutableSetRef _sources1;//sources1
    CFMutableArrayRef _observers;//observers
    CFMutableArrayRef _timers;//timers
}
複製代碼

一個mode能夠有多個timersouces0souces1observerstimers 那麼使用圖更直觀的來表示:bash

一個 runloop包含多個 mode,可是同時只能運行一個 mode,這點和你們開車的駕駛模式相似,運動模式和環保模式同時只能開一個模式,不能又運動又環保,明顯相悖。多個 mode被隔離開有點是處理事情更專注,不會由於多個同時處理事情形成卡頓或者資源競爭致使的一系列問題。

souces0

  • 觸摸事件
  • performSelector:onThread:

測試下點擊事件處理源markdown

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
	NSLog(@"%s",__func__);//此處斷點
}

(LLDB) bt //輸出當前調用棧
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x000000010c5bb66d CFRunloop`::-[ViewController touchesBegan:withEvent:](self=0x00007fc69ec087e0, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x00006000012a01b0) at ViewController.mm:22:2
    frame #1: 0x0000000110685a09 UIKitCore`forwardTouchMethod + 353
    frame #2: 0x0000000110685897 UIKitCore`-[UIResponder touchesBegan:withEvent:] + 49
    frame #3: 0x0000000110694c48 UIKitCore`-[UIWindow _sendTouchesForEvent:] + 1869
    frame #4: 0x00000001106965d2 UIKitCore`-[UIWindow sendEvent:] + 4079
    frame #5: 0x0000000110674d16 UIKitCore`-[UIApplication sendEvent:] + 356
    frame #6: 0x0000000110745293 UIKitCore`__dispatchPreprocessedEventFromEventQueue + 3232
    frame #7: 0x0000000110747bb9 UIKitCore`__handleEventQueueInternal + 5911
    frame #8: 0x000000010d8eabe1 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    frame #9: 0x000000010d8ea463 CoreFoundation`__CFRunLoopDoSources0 + 243
    frame #10: 0x000000010d8e4b1f CoreFoundation`__CFRunLoopRun + 1231
    frame #11: 0x000000010d8e4302 CoreFoundation`CFRunLoopRunSpecific + 626
    frame #12: 0x0000000115ddc2fe GraphicsServices`GSEventRunModal + 65
    frame #13: 0x000000011065aba2 UIKitCore`UIApplicationMain + 140
    frame #14: 0x000000010c5bb760 CFRunloop`main(argc=1, argv=0x00007ffee3643f68) at main.m:14:13
    frame #15: 0x000000010f1cb541 libdyld.dylib`start + 1
    frame #16: 0x000000010f1cb541 libdyld.dylib`start + 1
複製代碼

#1看到如今是在隊列queue = 'com.apple.main-thread'中,#10 Runloop啓動,#9進入到__CFRunLoopDoSources0,最終__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__調用了__handleEventQueueInternal->[UIApplication sendEvent:]->[UIWindow sendEvent:]->[UIWindow _sendTouchesForEvent:]->[UIResponder touchesBegan:withEvent:]->-[ViewController touchesBegan:withEvent:](self=0x00007fc69ec087e0, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x00006000012a01b0) at ViewController.mm:22:2,能夠看到另一個知識點,手勢的傳遞是從上往下的,順序是UIApplication -> UIWindow -> UIResponder -> ViewController

Source1

  • 基於Port的線程間通訊
  • 系統事件捕捉

Timers

  • NSTimer
  • performSelector:withObject:afterDelay:
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
	static int count = 5;
	dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
	dispatch_source_set_event_handler(timer, ^{
		NSLog(@"-------:%d \n",count++);
	});
	dispatch_resume(timer);
	//log
	(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000101f26457 CFRunloop`::__29-[ViewController viewDidLoad]_block_invoke(.block_descriptor=0x0000000101f28100) at ViewController.mm:72:33
    frame #1: 0x0000000104ac2db5 libdispatch.dylib`_dispatch_client_callout + 8
    frame #2: 0x0000000104ac5c95 libdispatch.dylib`_dispatch_continuation_pop + 552
    frame #3: 0x0000000104ad7e93 libdispatch.dylib`_dispatch_source_invoke + 2249
    frame #4: 0x0000000104acfead libdispatch.dylib`_dispatch_main_queue_callback_4CF + 1073
    frame #5: 0x00000001032568a9 CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
    frame #6: 0x0000000103250f56 CoreFoundation`__CFRunLoopRun + 2310
    frame #7: 0x0000000103250302 CoreFoundation`CFRunLoopRunSpecific + 626
	
複製代碼

最終進入函數__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__調用了libdispatch_dispatch_main_queue_callback_4CF函數,具體實現有興趣的大佬能夠看下源碼的實現。

Observers

  • 用於監聽RunLoop的狀態
  • UI刷新(BeforeWaiting)
  • Autorelease pool(BeforeWaiting)

Mode類型都多個,系統暴露在外的就兩個,

CF_EXPORT const CFRunLoopMode kCFRunLoopDefaultMode;
CF_EXPORT const CFRunLoopMode kCFRunLoopCommonModes;
複製代碼

那麼這兩個Mode都是在什麼狀況下運行的呢?

  1. kCFRunLoopDefaultMode(NSDefaultRunLoopMode)App的默認Mode,一般主線程是在這個Mode下運行
  2. UITrackingRunLoopMode:界面跟蹤Mode,用於ScrollView 追蹤觸摸滑動,保證界面滑動時不受其餘Mode影響

進入到某個Mode,處理事情也應該有前後順序和休息的時間,那麼如今須要一個狀態來表示此時此刻的status,系統已經準備了CFRunLoopActivity來表示當前的狀態

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), //即將進入loop
    kCFRunLoopBeforeTimers = (1UL << 1),//即將處理timers
    kCFRunLoopBeforeSources = (1UL << 2), //即將處理sourcs
    kCFRunLoopBeforeWaiting = (1UL << 5),//即將進入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),//即將從休眠中喚醒
    kCFRunLoopExit = (1UL << 7),//即將退出
    kCFRunLoopAllActivities = 0x0FFFFFFFU//全部狀態
};
複製代碼

1UL表示無符號長整形數字1,再次看到這個(1UL << 1)我麼猜想用到了位域或者聯合體,達到省空間的目的。kCFRunLoopAllActivities = 0x0FFFFFFFU轉換成二進制就是28個1,再進行mask的時候,全部的值都能取出來。

如今咱們瞭解到:

  1. CFRunloopRef表明RunLoop的運行模式
  2. 一個Runloop包含若干個Mode,每一個Mode包含若干個Source0/Source1/Timer/Obser
  3. Runloop啓動只能選擇一個Mode做爲currentMode
  4. 若是須要切換Mode,只能退出當前Loop,再從新選擇一個Mode進入
  5. 不一樣組的Source0/Source1/Timer/Observer能分隔開來,互不影響
  6. 若是Mode沒有任何Source0/Source1/Timer/ObserverRunloop立馬退出。
runloop切換Mode
CFRunLoopObserverRef obs= CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    switch (activity) {
    	case kCFRunLoopEntry:{
    		CFRunLoopMode m = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
    		NSLog(@"即將進入 mode:%@",m);
    		CFRelease(m);
    		break;
    	}
    		
    	case kCFRunLoopExit:
    	{
    		CFRunLoopMode m = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
    		NSLog(@"即將退出 mode:%@",m);
    		CFRelease(m);
    		break;
    	}
    	default:
    		break;
    }
	});
	CFRunLoopAddObserver(CFRunLoopGetMain(), obs, kCFRunLoopCommonModes);
	CFRelease(obs);
	
	//當滑動tb的時候log
	
即將退出 mode:kCFRunLoopDefaultMode
即將進入 mode:UITrackingRunLoopMode
即將退出 mode:UITrackingRunLoopMode
即將進入 mode:kCFRunLoopDefaultMode
複製代碼

runloop切換mode的時候,會退出當前kCFRunLoopDefaultMode,加入到其餘的UITrackingRunLoopMode,當前UITrackingRunLoopMode完成以後再退出以後再加入到kCFRunLoopDefaultMode

咱們再探究下runloop的循環的狀態究竟是怎樣來變動的。

//	//獲取loop
	CFRunLoopRef ref = CFRunLoopGetMain();
	//獲取obs
	CFRunLoopObserverRef obs = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities, YES, 0, callback, NULL);
	//添加監聽
	CFRunLoopAddObserver(ref, obs, CFRunLoopCopyCurrentMode(ref));
	CFRelease(obs);
	
	
int count = 0;//定義全局變量來計算一個mode中狀態切換的統計數據
void callback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
	printf("- ");
	count ++;
	printf("%d",count);
	switch (activity) {
		case kCFRunLoopEntry:
			printf("即將進入 \n");
			count = 0;
			break;
		case kCFRunLoopExit:
			printf("即將退出 \n");
			break;
		case kCFRunLoopAfterWaiting:
			printf("即將從休眠中喚醒 \n");
			break;
		case kCFRunLoopBeforeTimers:
			printf("即將進入處理 timers \n");
			break;
		case kCFRunLoopBeforeSources:
			printf("即將進入 sources \n");
			break;
		case kCFRunLoopBeforeWaiting:
			printf("即將進入 休眠 \n");
			count = 0;
			break;
		default:
			break;
	}
}

//點擊的時候 會出發loop來處理觸摸事件
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
	NSLog(@"%s",__func__);
}

//log

- 1即將從休眠中喚醒 
- 2即將進入處理 timers 
- 3即將進入 sources 
-[ViewController touchesBegan:withEvent:]
- 4即將進入處理 timers 
- 5即將進入 sources 
- 6即將進入處理 timers 
- 7即將進入 sources 
- 8即將進入處理 timers 
- 9即將進入 sources 
- 10即將進入 休眠 
- 1即將從休眠中喚醒 
- 2即將進入處理 timers 
- 3即將進入 sources 
- 4即將進入處理 timers 
- 5即將進入 sources 
- 6即將進入 休眠 
- 1即將從休眠中喚醒 
- 2即將進入處理 timers 
- 3即將進入 sources 
- 4即將進入 休眠 
複製代碼

runloop喚醒以後不是立馬處理事件的,而是看看timer有沒有事情,而後是sources,發現有觸摸事件就處理了,而後又循環查看timersources通常循環2次進入休眠狀態,處理source以後是循環三次。

RunLoop在不獲取的時候不存在,獲取才生成

RunLoop是在主動獲取的時候纔會生成一個,主線程是系統本身調用生成的,子線程開發者調用,咱們看下CFRunLoopGetCurrent

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}
複製代碼

看到到這裏相信你們已經對runloop有了基本的認識,那麼咱們再探究一下底層runloop是怎麼運轉的。

首先看官方給的圖:

那我又整理了一個表格來更直觀的瞭解狀態運轉

步驟 任務
1 通知Observers:進入Loop
2 通知Observers:即將處理Timers
3 通知Observers:即將處理Sources
4 處理blocks
5 處理Source0(可能再處理Blocks)
6 若是存在Source1,跳轉第8步
7 通知Observers:開始休眠
8 通知Observers:結束休眠1.處理Timer2.處理GCD Asyn To Main Queue 3.處理Source1
9 處理Blocks
10 根據前面的執行結果,決定如何操做1.返回第2步,2退出loop
11 通知Observers:退出Loop

查看runloop源碼runloop.c2333行

//入口函數
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    uint64_t startTSR = mach_absolute_time();

    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
	return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
	rlm->_stopped = false;
	return kCFRunLoopRunStopped;
    }
    
    mach_port_name_t dispatchPort = MACH_PORT_NULL;
    Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
    if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
    
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    mach_port_name_t modeQueuePort = MACH_PORT_NULL;
    if (rlm->_queue) {
        modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
        if (!modeQueuePort) {
            CRASH("Unable to get port for run loop mode queue (%d)", -1);
        }
    }
#endif
    
    dispatch_source_t timeout_timer = NULL;
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    if (seconds <= 0.0) { // instant timeout
        seconds = 0.0;
        timeout_context->termTSR = 0ULL;
    } else if (seconds <= TIMER_INTERVAL_LIMIT) {
	dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
	timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_retain(timeout_timer);
	timeout_context->ds = timeout_timer;
	timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
	timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
	dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
	dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
        dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
        uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
        dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
        dispatch_resume(timeout_timer);
    } else { // infinite timeout
        seconds = 9999999999.0;
        timeout_context->termTSR = UINT64_MAX;
    }
    Boolean didDispatchPortLastTime = true;
    int32_t retVal = 0;
    do {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
        voucher_t voucherCopy = NULL;
#endif
        uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        mach_msg_header_t *msg = NULL;
        mach_port_t livePort = MACH_PORT_NULL;
#endif
	__CFPortSet waitSet = rlm->_portSet;

        __CFRunLoopUnsetIgnoreWakeUps(rl);
//通知即將處理Timers
        if (rlm->_observerMask & kCFRunLoopBeforeTimers)
			__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//通知即將處理Sources
        if (rlm->_observerMask & kCFRunLoopBeforeSources)
			__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//處理Blocks
	__CFRunLoopDoBlocks(rl, rlm);
//處理Source0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
	//處理Block
            __CFRunLoopDoBlocks(rl, rlm);
	}
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);

        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            msg = (mach_msg_header_t *)msg_buffer;
	//y判斷是否有Source1
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
	//有則去 handle_msg
                goto handle_msg;
            }
#endif
        }
        didDispatchPortLastTime = false;
//即將進入休眠
	if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
	//開始休眠
	__CFRunLoopSetSleeping(rl);

    __CFPortSetInsert(dispatchPort, waitSet);
        
	__CFRunLoopModeUnlock(rlm);
	__CFRunLoopUnlock(rl);

        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        do {
            if (kCFUseCollectableAllocator) {

                memset(msg_buffer, 0, sizeof(msg_buffer));
            }
            msg = (mach_msg_header_t *)msg_buffer;
            //等待消息來喚醒當前線程
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
			
            if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
          (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
                if (rlm->_timerFired) {

                    rlm->_timerFired = false;
                    break;
                } else {
                    if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
                }
            } else {
                // Go ahead and leave the inner loop.
                break;
            }
        } while (1);
#else
        if (kCFUseCollectableAllocator) {
            memset(msg_buffer, 0, sizeof(msg_buffer));
        }
        msg = (mach_msg_header_t *)msg_buffer;
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
#endif
        
        __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);

        rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));

        __CFPortSetRemove(dispatchPort, waitSet);
        
        __CFRunLoopSetIgnoreWakeUps(rl);

        // user callouts now OK again
	__CFRunLoopUnsetSleeping(rl);
	if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting))
	//結束休眠
		__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//標籤 handle_msg
        handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);
		
        if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
        } else if (livePort == rl->_wakeUpPort) {
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
			
        }
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
	//被timer喚醒
			CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
#if USE_MK_TIMER_TOO
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
	//被GCD換醒
        else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
	//處理GCD
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
	//處理Source1
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
			
            voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);

            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
		mach_msg_header_t *reply = NULL;
		sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
		if (NULL != reply) {
		    (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
		    CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
		}
#endif
	    }
            
            _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);
        }
        //處理bBlock
	__CFRunLoopDoBlocks(rl, rlm);
        
//設置返回值
	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;
	}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        voucher_mach_msg_revert(voucherState);
        os_release(voucherCopy);
#endif
    } while (0 == retVal);
    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }
    return retVal;
}
複製代碼

通過及進一步精簡

//入口函數
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    uint64_t startTSR = mach_absolute_time();

    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
	return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
	rlm->_stopped = false;
	return kCFRunLoopRunStopped;
    }

    Boolean didDispatchPortLastTime = true;
    int32_t retVal = 0;
    do {
        __CFRunLoopUnsetIgnoreWakeUps(rl);
//通知即將處理Timers
        if (rlm->_observerMask & kCFRunLoopBeforeTimers)
			__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//通知即將處理Sources
        if (rlm->_observerMask & kCFRunLoopBeforeSources)
			__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//處理Blocks
	__CFRunLoopDoBlocks(rl, rlm);
//處理Source0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
	//處理Block
            __CFRunLoopDoBlocks(rl, rlm);
	}
            msg = (mach_msg_header_t *)msg_buffer;
	//y判斷是否有Source1
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
	//有則去 handle_msg
                goto handle_msg;
            }

//即將進入休眠
	if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
	//開始休眠
	__CFRunLoopSetSleeping(rl);
        do {
    //等待消息來喚醒當前線程
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        } while (1);
#else
	if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting))
	//結束休眠
		__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//標籤 handle_msg
        handle_msg:;
	//被timer喚醒
			CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                __CFArmNextTimerInMode(rlm, rl);
            }

#if USE_MK_TIMER_TOO
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
	//被GCD換醒
        else if (livePort == dispatchPort) {
	//處理GCD
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else {
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
	//處理Source1
		sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
            // Restore the previous voucher
            _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);
        }
        //處理bBlock
	__CFRunLoopDoBlocks(rl, rlm);
        
    //設置返回值
	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;
}
複製代碼

精簡到這裏基本都能看懂了,還寫了不少註釋,基本和上面整理的表格一致。 這裏的線程休眠__CFRunLoopServiceMachPort是調用內核函數mach_msg()進行休眠,和咱們平時while(1)大不一樣,while(1)叫死循環,其實系統每時每刻都在判斷是否符合條件,耗費很高的CPU,內核則不一樣,Mach內核提供面向消息,基於基礎的進程間通訊。

保活機制

一個程序運行完畢結束了就死掉了,timer和變量也同樣,運行完畢就結束了,那麼咱們怎麼能夠保證timer一直活躍和線程不結束呢?

timer保活和多mode運行

timer能夠添加到self的屬性保證一直活着,只要self不死,timer就不死。timer默認是添加到NSDefaultRunLoopMode模式中,由於RunLoop同時運行只能有一個模式,那麼在滑動scroller的時候怎Timer會卡頓中止直到再次切換回來,那麼如何保證同時兩個模式均可以運行呢? Foundation提供了一個API(void)addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode添加上,mode值爲NSRunLoopCommonModes能夠保證同時兼顧2種模式。

測試代碼:

static int i = 0;
NSTimer *timer=[NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
	NSLog(@"%d",++i);
}];
//NSRunLoopCommonModes 並非一個真正的模式,它這仍是一個標記
//timer在設置爲common模式下能運行
//NSRunLoopCommonModes 能在 _commentModes中數組中的模式均可以運行
//[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];//默認的模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

//log
	
2019-07-23 15:14:31 CFRunloop[62358:34093079] 1
2019-07-23 15:14:32 CFRunloop[62358:34093079] 2
2019-07-23 15:14:33 CFRunloop[62358:34093079] 3
2019-07-23 15:14:34 CFRunloop[62358:34093079] 4
2019-07-23 15:14:35 CFRunloop[62358:34093079] 5
2019-07-23 15:14:36 CFRunloop[62358:34093079] 6
2019-07-23 15:14:37 CFRunloop[62358:34093079] 7
2019-07-23 15:14:38 CFRunloop[62358:34093079] 8
複製代碼

當滑動的時候timer的時候,timer仍是如此絲滑,沒有一點停頓。 沒有卡頓以後咱們VC -> dealloctimer仍是在執行,那麼須要在dealloc中去下和刪除觀察者

-(void)dealloc{
	NSLog(@"%s",__func__);
	CFRunLoopRemoveObserver(CFRunLoopGetMain(), obs, m);
	dispatch_source_cancel(timer);
}
複製代碼

退出vc以後dealloc照常執行,日誌只有-[ViewController dealloc],並且數字沒有繼續輸出,說明刪除觀察者和取消source都成功了。

那麼NSRunLoopCommonModes是另一種模式嗎?

經過源碼查看得知,在runloop.c line:1632 line:2608

if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
    doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
    } else {
    doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
    }
複製代碼

還有不少地方都可以看出,當是currentMode須要和_mode相等纔去執行,當是kCFRunLoopCommonModes的時候,只須要包含curMode便可執行。可見kCFRunLoopCommonModes實際上是一個集合,不是某個特定的mode

線程保活

線程爲何須要保活?性能其實很大的瓶頸是在於空間的申請和釋放,當咱們執行一個任務的時候建立了一個線程,任務結束就釋放掉該線程,若是任務頻率比較高,那麼一個一直活躍的線程來執行咱們的任務就省去申請和釋放空間的時間和性能。上邊已經講過了 runloop須要有任務才能不退出,總不可能直接讓他執行while(1)吧,這種方法明顯不對的,由源碼得知,當有監測端口的時候,也不會退出,也不會影響應能。因此在線程初始化的時候使用

[[NSRunLoop currentRunLoop] addPort:[NSPort port] 
                            forMode:NSRunLoopCommonModes];
複製代碼

來保活。 在主線程使用是沒有意義的,系統已經在APP啓動的時候進行了調用,則已經加入到全局的字典中了。

驗證線程保活

@property (nonatomic,strong) FYThread *thread;


- (void)viewDidLoad {
	[super viewDidLoad];
	self.thread=[[FYThread alloc]initWithTarget:self selector:@selector(test) object:nil];
	_thread.name = @"test thread";
	[_thread start];
}
- (void)test {
//添加端口
	[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
	
	NSLog(@"%@",[NSThread currentThread]);
	NSLog(@"--start--");
	[[NSRunLoop currentRunLoop] run];
	NSLog(@"--end--");
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
	NSLog(@"%s",__func__);
	[self performSelector:@selector(alive) onThread:self.thread withObject:nil waitUntilDone:NO];
	NSLog(@"執行完畢了子線程");//不執行 由於子線程保活了 不會執行完畢
}
//測試子線程是否還活着
- (void)alive{
	NSLog(@"我還活着呢->%@",[NSThread currentThread]);
}
//log
//註釋掉添加端口代碼
<FYThread: 0x6000013a9540>{number = 3, name = test thread}
--start--
--end--
-[ViewController touchesBegan:withEvent:]
執行完畢了子線程



//註釋放開的時候點擊觸發log
<FYThread: 0x6000013a9540>{number = 3, name = test thread}
--start--

-[ViewController touchesBegan:withEvent:]
執行完畢了子線程
我還活着呢-><FYThread: 0x6000017e5c80>{number = 3, name = test thread}
複製代碼

[[NSRunLoop currentRunLoop] addPort:[NSPort port]forMode:NSDefaultRunLoopMode]添加端口註釋掉,直接執行了--end--,線程雖然strong強引用,可是runloop已經退出了,因此函數alive沒有執行,不註釋的話,alive還會執行,end一直不會執行,由於進入了runloop,並且沒有退出,代碼就不會向下執行。

那咱們測試下該線程聲明週期多長?

- (void)viewDidLoad {
	[super viewDidLoad];
	self.thread=[[FYThread alloc]initWithTarget:self selector:@selector(test) object:nil];
	_thread.name = @"test thread";
	[_thread start];
}
- (void)test {
	[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
	//獲取obs
	NSLog(@"%@",[NSThread currentThread]);
	NSLog(@"--start--");
	/*
	 If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, 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.
	 */
	[[NSRunLoop currentRunLoop] run];
	NSLog(@"--end--");
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
	NSLog(@"%s",__func__);
	[self performSelector:@selector(alive) onThread:self.thread withObject:nil waitUntilDone:NO];
	NSLog(@"執行完畢了子線程");//不執行 由於子線程保活了 不會執行完畢
}
//返回上頁
- (IBAction)popVC:(id)sender {
	[self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:NO];
}
//測試子線程是否還活着
- (void)alive{
	NSLog(@"我還活着呢->%@",[NSThread currentThread]);
}
//中止子線程線程
- (void)stop{
	CFRunLoopStop(CFRunLoopGetCurrent());
	NSLog(@"%s",__func__);
}
- (void)dealloc{
	NSLog(@"%s",__func__);
}

//log

<FYThread: 0x600003394780>{number = 3, name = test thread}
--start--
-[ViewController stop]
-[ViewController stop]

複製代碼

擁有該線程的是VC,點擊pop的時候,可是VCthread沒釋放掉,好像threadVC創建的循環引用,當self.thread=[[FYThread alloc]initWithTarget:self selector:@selector(test) object:nil];註釋了,則VC能夠進行正常釋放。

經過測試瞭解到 這個線程達到了永生,就是你殺不死他,簡直了死待。查找了很多資料才發現官方文檔纔是最穩的。有對這句[[NSRunLoop currentRunLoop] run]的解釋

If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, 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.

就是系統寫了以一個死循環可是沒有阻止他的參數,至關於一直在循環調用 runMode:beforeDate:,那麼該怎麼辦呢? 官方文檔給出瞭解決方案

BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
複製代碼

將代碼改爲下面的成功將死待殺死了。

- (void)test {
	[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
	//獲取obs
	NSLog(@"%@",[NSThread currentThread]);
	NSLog(@"--start--");
	self.shouldKeepRunning = YES;//默認運行
	NSRunLoop *theRL = [NSRunLoop currentRunLoop];
	while (_shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
	NSLog(@"--end--");
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
	NSLog(@"%s",__func__);
	[self performSelector:@selector(alive) onThread:self.thread withObject:nil waitUntilDone:NO];
	NSLog(@"執行完畢了子線程");//不執行 由於子線程保活了 不會執行完畢
}
//返回上頁
- (IBAction)popVC:(id)sender {
	self.shouldKeepRunning = NO;
	[self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:NO];
}
//測試子線程是否還活着
- (void)alive{
	NSLog(@"我還活着呢->%@",[NSThread currentThread]);
}
//中止子線程線程
- (void)stop{
	CFRunLoopStop(CFRunLoopGetCurrent());
	NSLog(@"%s",__func__);
	[self performSelectorOnMainThread:@selector(pop) withObject:nil waitUntilDone:NO];
}
- (void)pop{
	[self.navigationController popViewControllerAnimated:YES];

}
- (void)dealloc{
	NSLog(@"%s",__func__);
}

//log

<FYThread: 0x600002699fc0>{number = 3, name = test thread}
--start--
-[ViewController stop]
--end--
-[ViewController dealloc]
-[FYThread dealloc]
複製代碼

點擊popVC:首先將self.shouldKeepRunning = NO,而後子線程執行CFRunLoopStop(CFRunLoopGetCurrent()),而後在主線程執行pop函數,最終返回上級頁面並且成功殺死VC死待。 固然這個死待其實也是有用處的,當使用單例模式做爲下載器的時候使用死待也沒問題。這樣子處理比較複雜,咱們能夠放在VCdealloc看看是否能成功。 關鍵函數稍微更改:

//中止子線程線程
- (void)stop{
    if (self.thread == nil) {
        return;
    }
	NSLog(@"%s",__func__);
        [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)stopThread{
    self.shouldKeepRunning = NO;
    CFRunLoopStop(CFRunLoopGetCurrent());
}

- (void)dealloc{
    [self stop];
	NSLog(@"%s",__func__);
}
複製代碼

當點擊返回按鈕VC和線程都沒死,原來他們造成了強引用沒法釋放,就是VC始終沒法執行dealloc。將函數改爲block實現

__weak typeof(self) __weakSelf = self;
    self.thread = [[FYThread alloc]initWithBlock:^{
        [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
        NSLog(@"%@",[NSThread currentThread]);
        NSLog(@"--start--");
        __weakSelf.shouldKeepRunning = YES;//默認運行
        NSRunLoop *theRL = [NSRunLoop currentRunLoop];
        while (__weakSelf && __weakSelf.shouldKeepRunning  ){
            [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        };
        NSLog(@"--end--");
    }];
複製代碼

測試下崩潰了,崩潰到了:

while (__weakSelf.shouldKeepRunning  ){
        [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];//崩潰的地方
    };
複製代碼

怎麼想感受不對勁啊,怎麼會不行呢?VC銷燬的時候調用子線程stop,最後打斷點發現到了崩潰的地方self已經不存在了,說明是異步執行的,往前查找使用異步的函數最後出如今了[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];,表示不用等待stopThread函數執行時間,直接向前繼續執行,因此VC釋放掉了,while (__weakSelf.shouldKeepRunning )true,還真進去了,訪問了exe_bad_access,因此改爲while (__weakSelf&&__weakSelf.shouldKeepRunning )再跑一下

//log

--start--
-[ViewController stop]
-[ViewController dealloc]
--end--
-[FYThread dealloc]
複製代碼

如牛奶般絲滑,解決了釋放問題,也解決了複雜操做。本文章全部代碼均在底部連接能夠下載。 使用這個思路本身封裝了一個簡單的功能,你們能夠本身封裝一下而後對比一下個人思路,說不定有驚喜!

資料參考

資料下載


最怕一輩子碌碌無爲,還安慰本身平凡難得。

廣告時間

相關文章
相關標籤/搜索