在iOS開發中我們經常會用到多線程來處理一些業務,那麼iOS裏有哪些實現多線程的方式呢?
- NSTread:封裝程度最小、最輕量級,開銷較大。
- GCD(Grand Central Dispatch):內部效率優化,提供簡潔的C語言接口,更加簡單高效。
- NSOperation:基於GCD的一個抽象基類,不需要管理線程的生命週期和同步,比GCD可控性強。
一、NSTread
NSTread封裝程度最小、最輕量級的多線程編程接口,它使用更加靈活,但需要手動管理線程的生命週期、線程同步和線程加鎖等,開銷較大。
/** NSThread 靜態工具方法 **/
//1、是否開啓了多線程
BOOL isMultiThread = [NSThread isMultiThreaded];
//2、獲取當前線程
NSThread *currentThread = [NSThread currentThread];
//3、獲取主線程
NSThread *mainThread = [NSThread mainThread];
//4、睡眠當前線程
//4.1、線程睡眠5s
[NSThread sleepForTimeInterval:5];
//4.2、線程睡眠到指定時間,效果同上
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]];
//5、退出當前線程,注意不要在主線程調用,防止主線程被kill掉
[NSThread exit];
NSThread的使用比較簡單,可以動態創建並初始化NSThread對象,對其進行設置並啓動;也可以通過NSThread的靜態方法快速創建並啓動新線程;
/** NSTread 線程對象的基本創建,target爲入口方法所在的對象,selector爲線程入口方法 **/
//1、線程實例對象創建與設置
NSThread *newThread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
//設置線程優先級threadPriority(0~1.0),該屬性即將被拋棄,將使用qualityOfService代替
//newThread.threadPriority = 1.0;
newThread.qualityOfService = NSQualityOfServiceUserInteractive;
//開啓線程
[newThread start];
//2、靜態方法快速創建並開啓新線程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
[NSThread detachNewThreadWithBlock:^{
NSLog(@"block run");
}];
此外NSObject提供了隱式快速創建NSThread線程的performSelector系列方法。
/** NSObject 基類隱式創建線程的一些靜態工具方法 **/
//1、在當前線程上執行方法,延遲2s
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
//2、在指定線程上執行方法,不等待當前線程
[self performSelector:@selector(run) onThread:newThread withObject:nil waitUntilDone:NO];
//3、後臺異步執行方法
[self performSelectorInBackground:@selector(run) withObject:nil];
//4、在主線程上執行方法
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];
二、GCD
GCD(Grand Central Dispatch)又叫大中央調度,它對線程操作進行了封裝,加入了很多新的特性,內部進行了效率優化,提供了簡潔的C語言接口,使用更加簡單高效,也是蘋果公司推薦的方式。
GCD的用法特別靈活,在下一篇將詳細講解一下它的用法;這裏主要掌握點分爲以下幾個方面:
1、串行隊列與併發隊列dispatch_queue_t;
/*
創建隊列
DISPATCH_QUEUE_SERIAL 表示串行隊列,隊列內任務一個接一個的執行,按照先進先出(FIFO)的順序執行
DISPATCH_QUEUE_CONCURRENT 表示併發隊列,隊列內任務可同時並列執行,任務之間不會相互等待,執行順序不可預測
*/
// 串行隊列的創建方法
dispatch_queue_t queueSerial = dispatch_queue_create("com.jzsec.GCDtest", DISPATCH_QUEUE_SERIAL);
// 併發隊列的創建方法
dispatch_queue_t queueCon = dispatch_queue_create("com.jzsec.GCDtest", DISPATCH_QUEUE_CONCURRENT);
2、同步dispatch_sync與異步dispatch_async派發任務;
dispatch_sync(queue, ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
3、dispatch_once_t 只執行一次;
/**
* 一次性代碼(只執行一次)dispatch_once
能保證某段代碼在程序運行過程中只被執行1次,並且即使在多線程的環境下,dispatch_once也可以保證線程安全。
*/
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執行1次的代碼(這裏面默認是線程安全的)
NSLog(@"dispatch_once");
});
}
4、dispatch_after 延後執行;
/**
* 延時執行方法 dispatch_after
dispatch_after函數並不是在指定時間之後纔開始執行處理,而是在指定時間之後將任務追加到主隊列中。嚴格來說,這個時間並不是絕對準確的,但想要大致延遲執行任務,dispatch_after函數是很有效的。
*/
- (void)after {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"asyncMain---begin");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0秒後異步追加任務代碼到主隊列,並開始執行
NSLog(@"after---%@",[NSThread currentThread]); // 打印當前線程
});
}
5、dispatch_group_t 組調度;
/**
* 隊列組 dispatch_group_notify
當group所有任務都執行完成之後,才執行dispatch_group_notify block 中的任務。
*/
- (void)groupNotify {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的異步任務1、任務2都執行完畢後,回到主線程執行下邊任務
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
NSLog(@"group---end");
});
}
三、NSOperation
基於GCD的一個抽象基類,將線程封裝成要執行的操作,不需要管理線程的生命週期和同步,比GCD可控性強,例如加入操作依賴控制操作執行順序、設置操作隊列最大可併發執行的才做個數和取消執行等。
/** NSInvocationOperation 初始化 **/
NSInvocationOperation *invoOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
[invoOperation start];
/** NSBlockOperation 初始化 **/
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"NSBlockOperationA:%@", [NSThread currentThread]);
}];
//blockOperation可以後續繼續添加block執行塊,操作執行後會在不同的線程併發執行這些執行塊。
[blockOperation addExecutionBlock:^{
NSLog(@"NSBlockOperationB:%@", [NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"NSBlockOperationC:%@", [NSThread currentThread]);
}];
[blockOperation start];
/** 獲取主隊列(主線程) **/
NSOperationQueue *queue = [NSOperationQueue mainQueue];
//創建a、b、c操作
NSOperation *a = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"OperationA");
}];
NSOperation *b = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"OperationB");
}];
NSOperation *c = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"OperationC");
}];
//添加操作依賴,c依賴於a和b,這樣c一定會在a和b完成之後才執行,即順序爲A、B、C
[c addDependency:a];
[c addDependency:b];
//添加操作a、b、c 到操作隊列queue(特意將c在a和b之前添加)
[queue addOperation:c];
[queue addOperation:a];
[queue addOperation:b];
四、線程同步
對於UI的更新代碼,必須要寫在主線程上執行纔會及時有效;噹噹前代碼不在主線程時,需要將UI更新的部分代碼單獨同步到主線程。
同步的方法有三種:
- NSThread類的performSelectorOnMainThread方法
- NSOperation類的mainQueue主隊列
- GCD的dispatch_get_main_queue()獲取主隊列
推薦直接使用GCD的方法:
dispatch_async(dispatch_get_main_queue( ), ^{
//刷新UI的代碼
});
在iOS實際開發中,還是使用GCD的情況比較多,這裏只是簡單介紹對了三種多線程實現的方式,在下一篇將詳細介紹一下GCD的用法和注意事項。