iOS 多線程之 NSOperation

前序:按順序閱讀更好編程

概念篇-進程與線程,任務和隊列緩存

iOS 多線程之GCDmarkdown

iOS 多線程之 NSOperation網絡

一 簡介

NSOperation是對GCD的包裝,GCD只支持FIFO的隊列,而NSOpration能夠設置最大併發數、設置優先級、添加依賴關係等調整執行順序,NSOpration甚至能夠跨隊列設置依賴關係多線程

NSOperatio有2個核心概念:NSOperation(操做)和NSOperationQueue(隊列). NSOperation是個抽象類,依賴於子類NSInvocationOperation、NSBlockOperation去實現,另外還能夠自定義NSOperation.併發

二 基本使用

1. NSInvocationOperation

① 基本使用異步

- (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]將已經運行的線程再次運行會形成線程混亂

  1. NSBlockOperation

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執行,任務完成
--------------------輸出結果:-------------------
複製代碼
3 設置優先級
- (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有更高的概率調用,不是說設置高就必定所有先完成

  • 不使用sleep——高優先級的任務一先於低優先級的任務二
  • 使用sleep進行延時——高優先級的任務一慢於低優先級的任務二
4 線程間通信

在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]);
        }];
    }];
}
複製代碼
5 設置併發數

在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]);
        }];
    }
}
複製代碼
6 添加依賴

在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
所有執行完畢
--------------------輸出結果:-------------------
複製代碼
7. 任務的掛起、繼續、取消
// 掛起
queue.suspended = YES;
// 繼續
queue.suspended = NO;
// 取消
[queue cancelAllOperations];
複製代碼
  • 暫停操做不能使當前正在處於執行狀態的任務暫停,而是該任務執行結束,後面的任務不會執行,處於排隊等待狀態。而是該任務執行結束,後面的任務不會執行,處於排隊等待狀態。
  • 取消操做跟暫停類似,當前正在執行的任務不會當即取消,而是後面的全部任務永遠再也不執行,且該操做是不能夠恢復的

三 自定義NSOperation緩存機制

根據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];
}
複製代碼
相關文章
相關標籤/搜索