1. 程式人生 > >基於NSOperation的序列執行緒

基於NSOperation的序列執行緒

背景介紹:在接入七牛SDK的時候,發現SDK沒有批量上傳圖片的介面,業務又涉及到了上傳進度統計,並且要求一次性的圖片完整上傳。

開始的時候打算用GCD,寫著寫著感覺擴充套件性不好,可讀性不高,取消機制也不是很友好,執行緒不能暫停,於是改成了NSBlockOperation來實現

.h檔案

/**
 序列佇列
 
 @param dataList 資料來源
 @param opreationBlock 執行回撥
 @param cancelBlock 中斷回撥
 @param completionBlock 完成回撥
 @return 執行緒佇列
 */
+ (NSOperationQueue *)startOperationOnSerialQueue:(NSArray *)dataList
                     opreationBlock:(void(^)(id data, NSOperationQueue *queue, NSInteger index, double totalProgress))opreationBlock
                        cancelBlock:(BOOL(^)(id data, NSInteger index))cancelBlock
                    completionBlock:(void(^)(BOOL success))completionBlock;

介紹下h檔案,這個類方法可以建立一個執行緒池,序列佇列的順序由dataList陣列源中的元素順序決定,提供三種不同時機的回撥,根據實際情況使用,opreationBlock是主要回調,相當於thread的main。

.m檔案

+ (NSOperationQueue *)startOperationOnSerialQueue:(NSArray *)dataList
                     opreationBlock:(void(^)(id data, NSOperationQueue *queue, NSInteger index, double totalProgress))opreationBlock
                        cancelBlock:(BOOL(^)(id data, NSInteger index))cancelBlock
                    completionBlock:(void(^)(BOOL success))completionBlock
{
    // 建立一個執行緒佇列,最大併發數為1
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    
    // 根據資料來源建立Operation
    __block NSBlockOperation *lastBlockOperation;
    [dataList enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        __block NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
            // 如果設定了中斷回撥,優先檢測中斷回撥是否觸發
            if (cancelBlock) {
                BOOL cancelFlag = cancelBlock(obj,idx);
                //如果中斷了
                if (cancelFlag) {
                    //取消所有執行緒
                    [queue cancelAllOperations];
                    // 觸發一次完成回撥
                    [queue addOperation:[NSBlockOperation blockOperationWithBlock:^{
                        if (completionBlock) {
                            completionBlock(NO);
                        }
                    }]];
                } else {
                    if (opreationBlock) {
                        opreationBlock(obj, queue, idx, idx*1.0f/dataList.count);
                    }
                }
            } else {
                if (opreationBlock) {
                    opreationBlock(obj, queue, idx, idx*1.0f/dataList.count);
                }
            }
        }];
        
        // 將當前執行緒設定為上一次執行緒的依賴,保證執行順序
        if (lastBlockOperation) {
            [blockOperation addDependency:lastBlockOperation];
        }
        lastBlockOperation = blockOperation;
        [queue addOperation:blockOperation];
        if (idx == dataList.count-1) {
            //當最後一個執行緒執行完畢後,觸發完成回撥
            NSBlockOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
                if (completionBlock) {
                    completionBlock(YES);
                }
            }];
            [completionOperation addDependency:blockOperation];
            [queue addOperation:completionOperation];
        }
    }];
    
    return queue;
}

流程都有註釋,主要邏輯就是將執行緒設定成單項依賴,然後在適當的時機執行中斷判斷,觸發完成回撥。到現在為止完成了同步操作的序列佇列執行,但大家都知道,同步操作即使不用佇列,也是序列執行的,所以這裡要解決非同步操作的序列佇列執行,這就用到了NSOperationQueue的suspended功能,在執行非同步操作前先暫停佇列,等非同步操作完成後,再繼續佇列。

具體實現參考

NSMutableArray *imageKeys = [NSMutableArray array];
    __block NSString *lastResultKey = nil;
    [GinOperationQueueManager startOperationOnSerialQueue:imageList opreationBlock:^(id data, NSOperationQueue *queue, NSInteger index, double totalProgress) {
        // 這裡掛起佇列
        queue.suspended = YES;
        [self uploadSingleImage:data filename:[CEQiNiuManager createJPGImageKey] progress:nil completion:^(QNResponseInfo *info, NSString *key, NSDictionary *resp, UIImage *image) {
            lastResultKey = key;
            if (!key) {
                if (completion) {
                    completion(NO, nil);
                }
            } else {
                if (progress) {
                    progress(key, totalProgress ,index);
                }
                [imageKeys addObject:key];
            }
            //執行完成後繼續佇列
            queue.suspended = NO;
        }];
    } cancelBlock:^BOOL(id data, NSInteger index) {
        return ![lastResultKey isNotBlank] && index != 0;
    } completionBlock:^(BOOL success) {
        if (completion) {
            completion(success, imageKeys.count!=0?imageKeys:nil);
        }
    }];

這裡是原始檔,供大家參考
DEMO地址