」大師,近日我研讀線程操做之法,發現了一個問題。個人子線程作完了任務以後就銷燬了,後續再來任務,我須要從新開一個子線程,但爲何我手中的這部iPhone18點開一個APP卻能一直運轉呢?「性能優化
」哦,少俠是說這個問題啊,簡單。爲何能一直運轉呢?由於電池電量沒用完。「bash
」......「異步
「哈哈哈,少俠如此好學,老夫就授予你RunLoop使用大法和RunLoop內功心法,融匯貫通這兩種祕籍,iPhone18的運行機理會在你眼中變的愈來愈清晰。」async
平常開發中主要使用在一下幾個方面:函數
NSTimer
相關使用- 子線程保活
Perform Selector
的使用- 更深層次操做
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(beginUpdateUI) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
複製代碼
建立NSTimer
時,默認是運行在主線程的NSRunLoopDefaultMode
中,當UIScrollView滾動時,會切換到UITrackingRunLoopMode
,此時定時器會中止調用。 爲了讓定時器正確調用,能夠手動將RunLoop加入到NSRunLoopCommonModes
中,此時定時器能夠正確調用。oop
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(beginUpdateUI) userInfo:nil repeats:YES];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addTimer:timer forMode:NSDefaultRunLoopMode];
[runloop run];
});
複製代碼
子線程中的RunLoop在第一次獲取時建立,把timer加入到對應的Mode以後,子線程中的timer正常工做。注意:若是想要取消子線程中的timer,也要在對應的子線程中取消。性能
+ (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;
}
複製代碼
主線程的RunLoop是默認開啓的,子線程的RunLoop默認是不開啓的,因此子線程執行完一個任務以後,就會被銷燬。若是想讓子線程一直存活,就須要建立RunLoop。NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
,若是隻是這樣,會發現子線程依然是作完任務就退出了。爲何出現這樣的狀況呢?由於RunLoop想要運行在一個mode下,須要mode中有Source、Timer、Observer,當這些都沒有時RunLoop會退出。因此添加了[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
,至關於添加了一個Source,這樣子線程的RunLoop就能一直運行了,後臺線程保活成功。優化
Perform Selector
的使用- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(onMainThread) withObject:nil afterDelay:3];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[NSThread currentThread] setName:@"gaga"];
[self performSelector:@selector(onOtherThread) withObject:nil afterDelay:3];
});
}
- (void)onMainThread {
NSLog(@"%@", [NSThread currentThread]);
NSLog(@"%@", NSStringFromSelector(_cmd));
}
- (void)onOtherThread {
NSLog(@"%@", [NSThread currentThread]);
NSLog(@"%@", NSStringFromSelector(_cmd));
}
複製代碼
在主線程和子線程中分別調用performSelector:withObject:afterDelay:
,發現主線程的正常執行,可是子線程中調用的不能執行。ui
performSelector:withObject:afterDelay
延時操做至關於建立一個Timer添加到當前線程的RunLoop中,由於主線程的RunLoop一直存在,因此能夠正常執行。而子線程的RunLoop在第一次獲取時纔會建立,由於咱們沒有手動獲取子線程的RunLoop,因此不能正常執行。spa
將子線程修改成以下便可正常執行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[NSThread currentThread] setName:@"gaga"];
[self performSelector:@selector(onOtherThread) withObject:nil afterDelay:3];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop run];
});
複製代碼
打印子線程的RunLoop能夠看到Timer,證明了延時操做會加入Timer的想法。
- (void)onOtherThread {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
NSLog(@"%@", runLoop);
}
結果:
timers = <CFArray 0x600001fa6d60 [0x10da31ae8]>{type = mutable-small, count = 0, values = ()},
currently 589343081 (174839084728530) / soft deadline in: 1.84465692e+10 sec (@ -1) / hard deadline in: 1.84465692e+10 sec (@ -1)
複製代碼
「少俠,想想若是不用延時,直接在主線程和子線程使用performSelector:withObject:
,會正常調用嗎?」
「額...」
"答案是會正常調用,由於直接使用performSelector:withObject:
,至關因而個方法調用,不涉及Runloop。"
固然還有其餘的應用,好比在子線程中執行performSelector
這個方法時,若是不建立RunLoop會發現方法沒法調用。這個留給少俠,本身摸索了。
還有一種應用在iOS性能優化(中級+): 異步繪製有所體現,添加Observer,在RunLoop運行中的合適時機執行想要操做的代碼。
「少俠可看好了,以上這些使用方法涵蓋了RunLoop大部分的應用層使用。」
「大師,這們武功修煉起來,確實讓人經絡通暢,身心溫馨。」
「但這些只是招式,少俠若是想要更好的瞭解iPhone的運行機理,還需修煉對應的內功心法方方能發揮出本門武功的最大威力。」
線程與runloop一一對應,其關係保存在一個全局的Dictionary裏。線程剛建立時沒有RunLoop,若是你不主動獲取,那它就一直不會有。 RunLoop的建立是發生在第一次獲取時,RunLoop的銷燬是發生在線程結束時。RunLoop不能直接建立,只能獲取。 提供了兩個自動獲取的函數:CFRunLoopGetMain()
和CFRunLoopGetCurrent()
,且只能在一個線程的內部獲取其RunLoop(主線程除外)。以上這些關係,能夠經過觀察CF源碼中,CFRunLoop.c 文件裏, _CFRunLoopGet0這個方法查看。
RunLoop主要涉及這幾個類 CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
,一個RunLoop包含多個Mode,每一個Mode又包含多個Source/Timer/Observer。每次調用RunLoop的主函數時,只能指定其中一個Mode,這個Mode被稱做CurrentMode,若是須要切換Mode,只能退出Loop,再從新指定一個Mode進入。這樣作主要是爲了分隔開不一樣組的Source/Timer/Observer,讓其互不影響。若是一個mode中一個item都沒有,RunLoop會直接退出,不進入循環。
附一張RunLoop運行圖