上篇文章中介紹了 pthread 和 NSThread 兩種多線程的方式,本文將繼續介紹 GCD 和 NSOperation 這兩種方式。。css
GCD 有兩個核心的概念html
GCD 的使用步驟nginx
GCD 中有兩個用來執行任務的經常使用函數程序員
同步方法執行任務web
dispatch_sync(dispatch_queue_t _Nonnull queue, ^(void)block) queue : 隊列 Block : 任務
異步方法執行任務編程
dispatch_async(dispatch_queue_t _Nonnull queue, ^(void)block)
同步和異步的區別canvas
GCD 的隊列能夠分爲 2 大類安全
注意:同步 、 異步、併發、串行的區分ruby
同步
和 異步
主要影響: 能不能開啓新的線程
併發
和 串行
主要影響: 任務的執行方式
// 1.得到全局的併發隊列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 2.將任務加入隊列 dispatch_async(queue, ^{ for (NSInteger i = 0; i<10; i++) { NSLog(@"1-----%@", [NSThread currentThread]); } }); dispatch_async(queue, ^{ for (NSInteger i = 0; i<10; i++) { NSLog(@"2-----%@", [NSThread currentThread]); } });
// 1.得到全局的併發隊列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 2.將任務加入隊列 dispatch_sync(queue, ^{ NSLog(@"1-----%@", [NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"2-----%@", [NSThread currentThread]); });
// 1.建立串行隊列 dispatch_queue_t queue = dispatch_queue_create("com.coder.queue", DISPATCH_QUEUE_SERIAL); // dispatch_queue_t queue = dispatch_queue_create("com.coder.queue", NULL); // 2.將任務加入隊列 dispatch_async(queue, ^{ NSLog(@"1-----%@", [NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"2-----%@", [NSThread currentThread]); });
// 1.建立串行隊列 dispatch_queue_t queue = dispatch_queue_create("com.coder.queue", DISPATCH_QUEUE_SERIAL); // 2.將任務加入隊列 dispatch_sync(queue, ^{ NSLog(@"1-----%@", [NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"2-----%@", [NSThread currentThread]); });
// 1.得到主隊列 dispatch_queue_t queue = dispatch_get_main_queue(); // 2.將任務加入隊列 dispatch_sync(queue, ^{ NSLog(@"1-----%@", [NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"2-----%@", [NSThread currentThread]); });
各類隊列的執行效果 :markdown
注意:
使用 sync 函數往當前串行隊列中添加任務,會卡住當前的串行隊列
一般開闢子線程是爲了執行耗時操做。以下載圖片的等,使用 GCD 進行線程間通訊很是方便,示例代碼以下:
// 子線程中下載網絡圖片 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 圖片的網絡路徑 NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"]; // 加載圖片 NSData *data = [NSData dataWithContentsOfURL:url]; // 生成圖片 UIImage *image = [UIImage imageWithData:data]; // 回到主線程 dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; }); });
dispatch_barrier_sync(dispatch_queue_t _Nonnull queue, ^(void)block) // 此函數起一個阻隔任務執行的做用, 它前面的任務執行完以後它才執行,等它執行完後面的任務才能執行
// GCD 延遲執行 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"run-----"); }); // iOS 中其餘方式的延遲執行還有 [self performSelector:@selector(run) withObject:nil afterDelay:2.0]; 和定時器 [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO]; ---------------- run 方法 ----------------- - (void)run { NSLog(@"run-----"); }
一次性函數在整個程序運行中只會執行一次 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"------run-----"); // 內部代碼默認是線程安全的 });
快速迭代行數,實際上在全局隊列中遍歷子線程執行任務,用於顯著提升執行效率。 案例:【文件假拷貝】,【App Store 全部App同時更新】讓每一個任務都開子線程去併發執行會充分利用CPU,提升效率。 // 本示例代碼是將 From 文件夾下的內容拷貝到 TO 文件夾下 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); NSString *from = @"/Users/xiaoyou/Desktop/From"; NSString *to = @"/Users/xiaoyou/Desktop/To"; NSFileManager *mgr = [NSFileManager defaultManager]; NSArray *subpaths = [mgr subpathsAtPath:from]; dispatch_apply(subpaths.count, queue, ^(size_t index) { NSString *subpath = subpaths[index]; NSString *fromFullpath = [from stringByAppendingPathComponent:subpath]; NSString *toFullpath = [to stringByAppendingPathComponent:subpath]; // 剪切 [mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil]; NSLog(@"%@---%@", [NSThread currentThread], subpath); });
隊列組中的任務執行完,組會受到一個通知,而後執行最終的操做 // 1. 建立全局隊列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 2. 建立一個隊列組 dispatch_group_t group = dispatch_group_create(); // 任務 1.下載圖片1 dispatch_group_async(group, queue, ^{ // 圖片的網絡路徑 NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"]; // 加載圖片 NSData *data = [NSData dataWithContentsOfURL:url]; // 生成圖片 self.image1 = [UIImage imageWithData:data]; }); // 任務 2.下載圖片2 dispatch_group_async(group, queue, ^{ // 圖片的網絡路徑 NSURL *url = [NSURL URLWithString:@"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"]; // 加載圖片 NSData *data = [NSData dataWithContentsOfURL:url]; // 生成圖片 self.image2 = [UIImage imageWithData:data]; }); // 任務 3.將圖片一、圖片2合成一張新的圖片 dispatch_group_notify(group, queue, ^{ // 開啓新的圖形上下文 UIGraphicsBeginImageContext(CGSizeMake(100, 100)); // 繪製圖片 [self.image1 drawInRect:CGRectMake(0, 0, 50, 100)]; [self.image2 drawInRect:CGRectMake(50, 0, 50, 100)]; // 取得上下文中的圖片 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); // 結束上下文 UIGraphicsEndImageContext(); // 回到主線程顯示圖片 dispatch_async(dispatch_get_main_queue(), ^{ // 4.將新圖片顯示出來 self.imageView.image = image; }); });
單例模式是開發過程當中長期積累的一種編程習慣。
單例模式做用以下:
單例模式使用場合:
static id _instance;
+ (instancetype)allocWithZone:(struct _NSZone *)zone { // 使用GCD一次性函數,保證線程安全 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [self allocWithZone:zone]; }); return _instance; }
+ (instancetype)shareInstance{ // 使用GCD一次性函數,保證線程安全 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [[self alloc] init]; }); return _instance; }
+ (id)copyWithZone:(struct _NSZone *)zone { return _instance; }
從上面的實現中能夠看到,單例的實現方式是同樣的,咱們能夠把它抽取成一個宏來實現,這樣更加方便使用.
以下是單例的宏實現,只需在對應的單例類中添加兩個對應的宏,就可輕鬆實現單例。
// .h文件 #define XMGSingletonH(name) + (instancetype)shared##name; // .m文件 #define XMGSingletonM(name) \ static id _instance; \ \ + (instancetype)allocWithZone:(struct _NSZone *)zone \ { \ static dispatch_once_t onceToken; \ dispatch_once(&onceToken, ^{ \ _instance = [super allocWithZone:zone]; \ }); \ return _instance; \ } \ \ + (instancetype)shared##name \ { \ static dispatch_once_t onceToken; \ dispatch_once(&onceToken, ^{ \ _instance = [[self alloc] init]; \ }); \ return _instance; \ } \ \ - (id)copyWithZone:(NSZone *)zone \ { \ return _instance; \ }
思考:爲何不使用繼承?
繼承:看似可行,實際會有問題,程序中的GCD一次性代碼只會執行一次,當第一次有子類 A 調用以後,再有子類 B 調用返回的直接是第一次調用 A 的實例,沒法返回正確類型 B 單例 也就是說若是有 static 這樣的內部類對象不能用繼承。
NSOperation 是 OS X 和 iOS 開發中最後一種多線程實現方式,它是基於 GCD 的 OC 封裝,使用更加面向對象。
NSOperation是個抽象類,並不具有封裝操做的能力,必須使用它的子類
使用NSOperation子類的方式有3種
NSInvocationOperation
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
- (void)start; 一旦執行操做,就會調用target的sel方法
注意
NSBlockOperation
+ (id)blockOperationWithBlock:(void (^)(void))block;
- (void)addExecutionBlock:(void (^)(void))block;
注意:
只要NSBlockOperation封裝的操做數 > 1,就會異步執行操做
NSOperationQueue的做用
添加操做到NSOperationQueue中
- (void)addOperation:(NSOperation *)op; - (void)addOperationWithBlock:(void (^)(void))block;
什麼是併發數?
最大併發數的相關方法
- (NSInteger)maxConcurrentOperationCount; - (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
- (void)cancelAllOperations;
提示:也能夠調用NSOperation的- (void)cancel方法取消單個操做
- (void)setSuspended:(BOOL)b; // YES表明暫停隊列,NO表明恢復隊列 - (BOOL)isSuspended;
[operationB addDependency:operationA]; // 操做B依賴於操做A
注意:
不能相互依賴,好比A依賴B,B依賴A
能夠監聽一個操做的執行完畢
- (void (^)(void))completionBlock; - (void)setCompletionBlock:(void (^)(void))block;
自定義NSOperation的步驟很簡單
- (void)main
方法,在裏面實現想執行的任務- (void)main
方法的注意點
- (BOOL)isCancelled
方法檢測操做是否被取消,對取消作出響應蘋果建議:應該對自定義的 Operation 中的執行完一個耗時操做,應該手動調用一下 isCancelled 方法查看是否是已經取消並作對應的操做 /** * 須要執行的任務 */ - (void)main { for (NSInteger i = 0; i<1000; i++) { NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]); } if (self.isCancelled) return; for (NSInteger i = 0; i<1000; i++) { NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]); } if (self.isCancelled) return; for (NSInteger i = 0; i<1000; i++) { NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]); } if (self.isCancelled) return; }
此處依舊如下載併合成一張圖片爲例,只需開啓兩個子線程分別下載image,第三個線程爲合併操做, 而後添加線程依賴。並放到隊列中
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; __block UIImage *image1 = nil; // 下載圖片1 NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{ // 圖片的網絡路徑 NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"]; // 加載圖片 NSData *data = [NSData dataWithContentsOfURL:url]; // 生成圖片 image1 = [UIImage imageWithData:data]; }]; __block UIImage *image2 = nil; // 下載圖片2 NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{ // 圖片的網絡路徑 NSURL *url = [NSURL URLWithString:@"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"]; // 加載圖片 NSData *data = [NSData dataWithContentsOfURL:url]; // 生成圖片 image2 = [UIImage imageWithData:data]; }]; // 合成圖片 NSBlockOperation *combine = [NSBlockOperation blockOperationWithBlock:^{ // 開啓新的圖形上下文 UIGraphicsBeginImageContext(CGSizeMake(100, 100)); // 繪製圖片 [image1 drawInRect:CGRectMake(0, 0, 50, 100)]; image1 = nil; [image2 drawInRect:CGRectMake(50, 0, 50, 100)]; image2 = nil; // 取得上下文中的圖片 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); // 結束上下文 UIGraphicsEndImageContext(); // 回到主線程 [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = image; }]; }]; [combine addDependency:download1]; [combine addDependency:download2]; [queue addOperation:download1]; [queue addOperation:download2]; [queue addOperation:combine];
簡單的,只有下載圖片而後放到主線程展現的線程通訊以下:
[[[NSOperationQueue alloc] init] addOperationWithBlock:^{ // 圖片的網絡路徑 NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"]; // 加載圖片 NSData *data = [NSData dataWithContentsOfURL:url]; // 生成圖片 UIImage *image = [UIImage imageWithData:data]; // 回到主線程 [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = image; }]; }];
本文主要講解了 GCD 和 NSOperation 兩種多線程的建立和使用方式。加上上篇文章 共有 pthread 、 NSThread 、 GCD 和 NSOperation 四種多線程方案,實際使用中須要根據項目需求靈活使用。