前序:按順序閱讀更好編程
iOS 多線程之GCDmarkdown
NSOperation是對GCD的包裝,GCD只支持FIFO的隊列,而NSOpration能夠設置最大併發數、設置優先級、添加依賴關係等調整執行順序,NSOpration甚至能夠跨隊列設置依賴關係多線程
NSOperatio有2個核心概念:NSOperation(操做)和NSOperationQueue(隊列). NSOperation是個抽象類,依賴於子類NSInvocationOperation、NSBlockOperation去實現,另外還能夠自定義NSOperation.併發
① 基本使用異步
- (void)invocationOperation {
// 處理事務
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(handleInvocation:) object:@"a"];
// 建立隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 操做加入隊列
[queue addOperation:op];
}
複製代碼
② 直接處理事務,不添加隱性隊列函數式編程
- (void)invocationOperation {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"a"];
[op start];
}
複製代碼
③ 錯誤使用函數
- (void)invocationOperation {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"a"];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:op];
[op start];
}
--------------------錯誤日誌:-------------------
something is trying to start the receiver simultaneously from more than one thread'
--------------------錯誤日誌:-------------------
複製代碼
上述代碼之因此會崩潰,是由於線程生命週期:post
[queue addOperation:op]
已經將處理事務的操做任務加入到隊列中,並讓線程運行 [op start]
將已經運行的線程再次運行會形成線程混亂
NSInvocationOperation和NSBlockOperation二者的區別在於:
前者相似target形式; 後者相似block形式——函數式編程,業務邏輯代碼可讀性更高
- (void)blockOperation {
// 初始化添加事務
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務1————%@",[NSThread currentThread]);
}];
// 添加事務1
[bo addExecutionBlock:^{
NSLog(@"任務2————%@",[NSThread currentThread]);
}];
// 添加事務2
[bo addExecutionBlock:^{
NSLog(@"任務3————%@",[NSThread currentThread]);
}];
// 添加事務3
[bo addExecutionBlock:^{
NSLog(@"任務4————%@",[NSThread currentThread]);
}];
// 添加事務4
[bo addExecutionBlock:^{
NSLog(@"任務5————%@",[NSThread currentThread]);
}];
// 回調監聽
bo.completionBlock = ^{
NSLog(@"completionBlock執行,任務所有完成");
};
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:bo];
NSLog(@"事務添加進了NSOperationQueue");
}
--------------------輸出結果:-------------------
事務添加進了NSOperationQueue
任務1————<NSThread: 0x280be7d00>{number = 3, name = (null)}
任務4————<NSThread: 0x280be7d00>{number = 3, name = (null)}
任務3————<NSThread: 0x280bc0340>{number = 6, name = (null)}
任務2————<NSThread: 0x280bd8600>{number = 5, name = (null)}
任務5————<NSThread: 0x280be7d00>{number = 3, name = (null)}
completionBlock執行,任務所有完成
--------------------輸出結果:-------------------
複製代碼
NSOperationQueue是異步執行的,因此任務12345的完成順序不肯定,可是completionBlock回調會在任務所有完成後執行
還能夠這樣用
- (void)blockOperation {
// 初始化添加事務
NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務1————%@",[NSThread currentThread]);
}];
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務2————%@",[NSThread currentThread]);
}];
NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務3————%@",[NSThread currentThread]);
}];
NSBlockOperation *bo4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務4————%@",[NSThread currentThread]);
}];
// bo1的回調監聽
bo1.completionBlock = ^{
NSLog(@"bo1 的completionBlock執行,任務完成");
};
// bo2的回調監聽
bo2.completionBlock = ^{
NSLog(@"bo2 的completionBlock執行,任務完成");
};
// bo3的回調監聽
bo3.completionBlock = ^{
NSLog(@"bo3 的completionBlock執行,任務完成");
};
// bo4的回調監聽
bo4.completionBlock = ^{
NSLog(@"bo4 的completionBlock執行,任務完成");
};
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:bo1];
[queue addOperation:bo2];
[queue addOperation:bo3];
[queue addOperation:bo4];
NSLog(@"事務添加進了NSOperationQueue");
}
--------------------輸出結果:-------------------
事務添加進了NSOperationQueue
任務1————<NSThread: 0x2834dd200>{number = 7, name = (null)}
任務4————<NSThread: 0x2834d62c0>{number = 4, name = (null)}
任務2————<NSThread: 0x2834dd7c0>{number = 6, name = (null)}
任務3————<NSThread: 0x2834d6fc0>{number = 5, name = (null)}
bo4 的completionBlock執行,任務完成
bo1 的completionBlock執行,任務完成
bo3 的completionBlock執行,任務完成
bo2 的completionBlock執行,任務完成
--------------------輸出結果:-------------------
複製代碼
- (void)blockOperation {
NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++) {
//sleep(1);
NSLog(@"第一個操做 %d --- %@", i, [NSThread currentThread]);
}
}];
// 設置最高優先級
bo1.qualityOfService = NSQualityOfServiceUserInteractive;
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++) {
NSLog(@"第二個操做 %d --- %@", i, [NSThread currentThread]);
}
}];
// 設置最低優先級
bo2.qualityOfService = NSQualityOfServiceBackground;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:bo1];
[queue addOperation:bo2];
}
複製代碼
NSOperation設置優先級只會讓CPU有更高的概率調用,不是說設置高就必定所有先完成
在GCD中使用異步進行網絡請求,而後回到主線程刷新UI.NSOperation中也有相似在線程間通信的操做
- (void)operationQueue {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"Felix";
[queue addOperationWithBlock:^{
NSLog(@"請求網絡%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"刷新UI%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);
}];
}];
}
複製代碼
在GCD中只能使用信號量來設置併發數,而NSOperation能夠直接經過設置maxConcurrentOperationCount
來設置併發數
- (void)operationQueue {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"a";
queue.maxConcurrentOperationCount = 2;
for (int i = 0; i < 5; i++) {
[queue addOperationWithBlock:^{ // 一個任務
[NSThread sleepForTimeInterval:2];
NSLog(@"%d-%@",i,[NSThread currentThread]);
}];
}
}
複製代碼
在NSOperation中經過addDependency
添加依賴能很好的控制任務執行的前後順序
- (void)operationQueue {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"請求token");
}];
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"拿着token,請求數據1");
}];
NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"拿着數據1,請求數據2");
}];
[bo2 addDependency:bo1];
[bo3 addDependency:bo2];
[self.queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES];
NSLog(@"所有執行完畢");
}
--------------------輸出結果:-------------------
請求token
拿着token,請求數據1
拿着數據1,請求數據2
所有執行完畢
--------------------輸出結果:-------------------
複製代碼
// 掛起
queue.suspended = YES;
// 繼續
queue.suspended = NO;
// 取消
[queue cancelAllOperations];
複製代碼
根據SDWebImage
加載網絡圖片的緩存機制,用NSOperation自定義圖片緩存(本地緩存+內存緩存)
-(void)simulationCacheImage{
UIImage *cacheImage = self.imageCacheDict[model.imageUrl];
if (cacheImage) {
NSLog(@"從內存獲取圖片:%@", model.title);
cell.imageView.image = cacheImage;
return cell;
}
UIImage *diskImage = [UIImage imageWithContentsOfFile:[model.imageUrl getDowloadImagePath]];
if (diskImage) {
NSLog(@"從沙盒獲取image:%@",model.title);
cell.imageView.image = diskImage;
[self.imageCacheDict setValue:diskImage forKey:model.imageUrl];
return cell;
}
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"去下載圖片:%@", model.title);
// 延遲
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
// 存內存
[self.imageCacheDict setValue:image forKey:model.imageUrl];
[data writeToFile:[model.imageUrl getDowloadImagePath] atomically:YES];
// 更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
[self.queue addOperation:bo];
}
複製代碼