”大師吏垮,近日我研讀線程操作之法哑姚,發(fā)現(xiàn)了一個(gè)問題倦青。我的子線程做完了任務(wù)之后就銷毀了瓮床,后續(xù)再來任務(wù),我需要重新開一個(gè)子線程产镐,但為什么我手中的這部iPhone18點(diǎn)開一個(gè)APP卻能一直運(yùn)轉(zhuǎn)呢?“
”哦踢步,少俠是說這個(gè)問題啊癣亚,簡單。為什么能一直運(yùn)轉(zhuǎn)呢获印?因?yàn)殡姵仉娏繘]用完述雾。“
”......“
“哈哈哈兼丰,少俠如此好學(xué)玻孟,老夫就授予你RunLoop使用大法和RunLoop內(nèi)功心法,融匯貫通這兩種秘籍鳍征,iPhone18的運(yùn)行機(jī)理會(huì)在你眼中變的越來越清晰黍翎。”
RunLoop使用大法
日常開發(fā)中主要使用在一下幾個(gè)方面:
NSTimer
相關(guān)使用- 子線程毖薮裕活
Perform Selector
的使用- 更深層次操作
- NSTimer在主線程時(shí)匣掸,列表滑動(dòng)時(shí)也可正常使用趟紊。
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(beginUpdateUI) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
創(chuàng)建NSTimer
時(shí),默認(rèn)是運(yùn)行在主線程的NSRunLoopDefaultMode
中,當(dāng)UIScrollView滾動(dòng)時(shí)碰酝,會(huì)切換到UITrackingRunLoopMode
霎匈,此時(shí)定時(shí)器會(huì)停止調(diào)用。
為了讓定時(shí)器正確調(diào)用送爸,可以手動(dòng)將RunLoop加入到NSRunLoopCommonModes
中铛嘱,此時(shí)定時(shí)器可以正確調(diào)用。
- 在子線程中使用NSTimer袭厂。
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在第一次獲取時(shí)創(chuàng)建弄痹,把timer加入到對(duì)應(yīng)的Mode之后,子線程中的timer正常工作嵌器。注意:如果想要取消子線程中的timer肛真,也要在對(duì)應(yīng)的子線程中取消。
- 子線程彼剑活蚓让。AFNetworking2.x中有一個(gè)示范性的應(yīng)用。
+ (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是默認(rèn)開啟的讥珍,子線程的RunLoop默認(rèn)是不開啟的历极,所以子線程執(zhí)行完一個(gè)任務(wù)之后,就會(huì)被銷毀衷佃。如果想讓子線程一直存活趟卸,就需要?jiǎng)?chuàng)建RunLoop。NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
氏义,如果只是這樣锄列,會(huì)發(fā)現(xiàn)子線程依然是做完任務(wù)就退出了。為什么出現(xiàn)這樣的情況呢惯悠?因?yàn)镽unLoop想要運(yùn)行在一個(gè)mode下邻邮,需要mode中有Source、Timer克婶、Observer筒严,當(dāng)這些都沒有時(shí)RunLoop會(huì)退出。所以添加了[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
情萤,相當(dāng)于添加了一個(gè)Source,這樣子線程的RunLoop就能一直運(yùn)行了鸭蛙,后臺(tái)線程保活成功筋岛。
-
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));
}
在主線程和子線程中分別調(diào)用performSelector:withObject:afterDelay:
娶视,發(fā)現(xiàn)主線程的正常執(zhí)行,但是子線程中調(diào)用的不能執(zhí)行泉蝌。
performSelector:withObject:afterDelay
延時(shí)操作相當(dāng)于創(chuàng)建一個(gè)Timer添加到當(dāng)前線程的RunLoop中歇万,因?yàn)橹骶€程的RunLoop一直存在揩晴,所以可以正常執(zhí)行。而子線程的RunLoop在第一次獲取時(shí)才會(huì)創(chuàng)建贪磺,因?yàn)槲覀儧]有手動(dòng)獲取子線程的RunLoop硫兰,所以不能正常執(zhí)行。
將子線程修改為如下即可正常執(zhí)行
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寒锚,證實(shí)了延時(shí)操作會(huì)加入Timer的想法劫映。
- (void)onOtherThread {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
NSLog(@"%@", runLoop);
}
結(jié)果:
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)
“少俠,想一想如果不用延時(shí)刹前,直接在主線程和子線程使用performSelector:withObject:
泳赋,會(huì)正常調(diào)用嗎?”
“額...”
"答案是會(huì)正常調(diào)用喇喉,因?yàn)橹苯邮褂?code>performSelector:withObject:祖今,相當(dāng)于是個(gè)方法調(diào)用,不涉及Runloop拣技。"
- 其他操作
當(dāng)然還有其他的應(yīng)用千诬,比如在子線程中執(zhí)行performSelector
這個(gè)方法時(shí),如果不創(chuàng)建RunLoop會(huì)發(fā)現(xiàn)方法無法調(diào)用膏斤。這個(gè)留給少俠徐绑,自己摸索了。
還有一種應(yīng)用在iOS性能優(yōu)化(中級(jí)+): 異步繪制有所體現(xiàn)莫辨,添加Observer,在RunLoop運(yùn)行中的合適時(shí)機(jī)執(zhí)行想要操作的代碼傲茄。
“少俠可看好了,以上這些使用方法涵蓋了RunLoop大部分的應(yīng)用層使用沮榜∨陶ィ”
“大師,這們武功修煉起來敞映,確實(shí)讓人經(jīng)絡(luò)通暢较曼,身心舒適≌裨福”
“但這些只是招式,少俠如果想要更好的了解iPhone的運(yùn)行機(jī)理弛饭,還需修煉對(duì)應(yīng)的內(nèi)功心法方方能發(fā)揮出本門武功的最大威力冕末。”
RunLoop內(nèi)功心法
線程與runloop一一對(duì)應(yīng)侣颂,其關(guān)系保存在一個(gè)全局的Dictionary里档桃。線程剛創(chuàng)建時(shí)沒有RunLoop,如果你不主動(dòng)獲取憔晒,那它就一直不會(huì)有藻肄。
RunLoop的創(chuàng)建是發(fā)生在第一次獲取時(shí)蔑舞,RunLoop的銷毀是發(fā)生在線程結(jié)束時(shí)。RunLoop不能直接創(chuàng)建嘹屯,只能獲取攻询。
提供了兩個(gè)自動(dòng)獲取的函數(shù):CFRunLoopGetMain()
和CFRunLoopGetCurrent()
,且只能在一個(gè)線程的內(nèi)部獲取其RunLoop(主線程除外)州弟。以上這些關(guān)系钧栖,可以通過觀察CF源碼中,CFRunLoop.c 文件里婆翔,
_CFRunLoopGet0這個(gè)方法查看拯杠。
RunLoop主要涉及這幾個(gè)類
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
,一個(gè)RunLoop包含多個(gè)Mode啃奴,每個(gè)Mode又包含多個(gè)Source/Timer/Observer潭陪。每次調(diào)用RunLoop的主函數(shù)時(shí),只能指定其中一個(gè)Mode最蕾,這個(gè)Mode被稱作CurrentMode依溯,如果需要切換Mode,只能退出Loop揖膜,再重新指定一個(gè)Mode進(jìn)入誓沸。這樣做主要是為了分隔開不同組的Source/Timer/Observer,讓其互不影響。如果一個(gè)mode中一個(gè)item都沒有壹粟,RunLoop會(huì)直接退出拜隧,不進(jìn)入循環(huán)。
附一張RunLoop運(yùn)行圖