天天看點

iOS多線程程式設計——淺談NSOperation

就如同上一篇文章——《iOS多線程程式設計——淺談GCD》中寫到的,iOS的多線程開發中,最常用的就是NSThread,NSOperation,GCD,他們的抽象層次從左往右越來越高,這也就是越來越底層,性能也就越來越好。既然上次談到了GCD,那麼這次就不得不來談談他的兄弟NSOperation。

蘋果官方文檔是這樣描述NSOperation的:

The NSOperation class is an abstract class you use to encapsulate the code and data associated with a single task. Because it is abstract, you do not use this class directly but instead subclass or use one of the system-defined subclasses (NSInvocationOperation or NSBlockOperation) to perform the actual task. Despite being abstract, the base implementation of NSOperation does include significant logic to coordinate the safe execution of your task. The presence of this built-in logic allows you to focus on the actual implementation of your task, rather than on the glue code needed to ensure it works correctly with other system objects.

翻譯:

NSOperation類是一個抽象類,通過這個類,你可以在一個任務将代碼和資料聯系起來。又因為他是抽象的,你不能直接使用它,而是使用它的子類(

NSinvocationOperation

NSBlockOperation

)或者自定義一個子類來實作它實際上的任務。盡管他是抽象的,但是他最基本的方法包含了主要邏輯來保證線程安全。這使得你隻要專心于你想要實作的邏輯處理,而不用關心用來處理連接配接的代碼在這個工程中是否正确。

對于

NSOperation

,實際上他僅僅是一個抽象類。他的兄弟

NSBlockOperation

NSInvocationOperation

才是我們日常編碼過程中用到最多的兩個Operation對象。

也就是說,如果真的要調用NSOperation對象的話,我們有3種方法:

  1. 使用NSInvocationOperation
  2. 使用NSBlockOperation
  3. 自己繼承NSOperation,然後實作對應的内容

既然如此,我們就一個個來講講,讓我們更加簡單的了解NSOperation這個東西吧。

NSInvocationOperation

基本使用:

- (void)operationJustStart{
    NSInvocationOperation * operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run:) object:@"hei"]; //1

    [operation setCompletionBlock:^{    //2
        NSLog(@"helloworld");
    }];

    [operation start];  //3
}
           

解釋:

  1. 用來建立一個

    NSInvocationOperation

    對象,同時将執行時候的代碼設定為run函數,并将參數中的object所指向的對象作為run函數的參數傳入。
  2. 設定當該線程完成後,需要執行的代碼塊(Block)
  3. 線程開始。NSInvocationOperation中,如果直接使用

    start

    函數的話,那麼就會直接在目前線程直接執行對應的代碼(同步執行)

坑:

  1. 隻能同步執行,如果需要使用異步,那麼就需要調用NSOperationQueue。
  2. CompletionBlock的調用是在完成後一段時間後進行調用的,這個可能和runloop有關,這裡先開個坑。一下是坑的代碼:
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    NSLog(@"1");
    [self operationJustStart];

    NSLog(@"2");
}

- (void)operationJustStart{
    NSInvocationOperation * operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run:) object:@"hei"];

    [operation setCompletionBlock:^{
        NSLog(@"helloworld");
    }];

    [operation start];
}

- (void)run:(NSObject *)obj{
    for (int i =  ; i < ; i++) {
        NSLog(@"%d %s",i,__func__);
    }
}
           

調用的結果如下:

2016-07-18 13:09:11.344 NSOperation[59291:6646533] 1
2016-07-18 13:09:11.345 NSOperation[59291:6646533] 0 -[ViewController run:]
2016-07-18 13:09:11.345 NSOperation[59291:6646533] 1 -[ViewController run:]
2016-07-18 13:09:11.345 NSOperation[59291:6646533] 2 -[ViewController run:]
2016-07-18 13:09:11.345 NSOperation[59291:6646533] 3 -[ViewController run:]
2016-07-18 13:09:11.345 NSOperation[59291:6646533] 4 -[ViewController run:]
2016-07-18 13:09:11.345 NSOperation[59291:6646533] 5 -[ViewController run:]
2016-07-18 13:09:11.345 NSOperation[59291:6646533] 6 -[ViewController run:]
2016-07-18 13:09:11.346 NSOperation[59291:6646533] 7 -[ViewController run:]
2016-07-18 13:09:11.346 NSOperation[59291:6646533] 8 -[ViewController run:]
2016-07-18 13:09:11.346 NSOperation[59291:6646533] 9 -[ViewController run:]
2016-07-18 13:09:11.346 NSOperation[59291:6646533] 2
2016-07-18 13:09:11.346 NSOperation[59291:6646590] helloworld
           

注意!!!如果對一個NSOperation對象兩次使用start方法,那麼第二次調用将失敗。因為此時operation已經被銷毀。

使用NSOperationQueue進行異步調用

代碼如下:

- (void)InvocationOperation{
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"hello"];
    [operation setCompletionBlock:^{
        NSLog(@"setCompletionBlock");
    }];


    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"world"];
    [operation2 setCompletionBlock:^{
        NSLog(@"setCompletionBlock");
    }];


    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    [queue addOperation:operation];
    [queue addOperation:operation2];

}

- (void)run:(NSObject *)obj{
    for (int i =  ; i < ; i++) {
        NSLog(@"%d %s",i,__func__);
    }
}
           

調用的結果如下:(很明顯是異步調用,同時,是開了2個線程,畢竟2個operation嘛)

2016-07-18 13:14:56.145 NSOperation[59346:6655570] 1
2016-07-18 13:14:56.145 NSOperation[59346:6655570] 2
2016-07-18 13:14:56.145 NSOperation[59346:6655619] 0 -[ViewController run:]
2016-07-18 13:14:56.145 NSOperation[59346:6655609] 0 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655619] 1 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655609] 1 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655619] 2 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655609] 2 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655619] 3 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655609] 3 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655619] 4 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655609] 4 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655619] 5 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655619] 6 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655609] 5 -[ViewController run:]
2016-07-18 13:14:56.147 NSOperation[59346:6655619] 7 -[ViewController run:]
2016-07-18 13:14:56.161 NSOperation[59346:6655619] 8 -[ViewController run:]
2016-07-18 13:14:56.161 NSOperation[59346:6655609] 6 -[ViewController run:]
2016-07-18 13:14:56.162 NSOperation[59346:6655619] 9 -[ViewController run:]
2016-07-18 13:14:56.162 NSOperation[59346:6655609] 7 -[ViewController run:]
2016-07-18 13:14:56.162 NSOperation[59346:6655609] 8 -[ViewController run:]
2016-07-18 13:14:56.162 NSOperation[59346:6655609] 9 -[ViewController run:]
2016-07-18 13:14:56.162 NSOperation[59346:6655633] setCompletionBlock
2016-07-18 13:14:56.162 NSOperation[59346:6655619] setCompletionBlock
           

NSBlockOperation

這個方法根據名字就很容易得到他的概念,他就是可以通過多個block,來實作串行隊列。

實作方法的話和前面這個類似,代碼如下:

- (void)blockOperation{
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [self run];
    }];

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [self run];
    }];

    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    [queue addOperation:operation1];
    [queue addOperation:operation2];
}

- (void)run:(NSObject *)obj{
    for (int i =  ; i < ; i++) {
        NSLog(@"%d %s",i,__func__);
    }
}
           

執行結果如下,很明顯是異步的:

2016-07-18 19:35:05.637 NSOperation[64600:7323370] 1
2016-07-18 19:35:05.638 NSOperation[64600:7323370] 2
2016-07-18 19:35:05.638 NSOperation[64600:7323407] 0 -[ViewController run:]
2016-07-18 19:35:05.638 NSOperation[64600:7323405] 0 -[ViewController run:]
2016-07-18 19:35:05.638 NSOperation[64600:7323407] 1 -[ViewController run:]
2016-07-18 19:35:05.638 NSOperation[64600:7323405] 1 -[ViewController run:]
2016-07-18 19:35:05.638 NSOperation[64600:7323407] 2 -[ViewController run:]
2016-07-18 19:35:05.638 NSOperation[64600:7323405] 2 -[ViewController run:]
2016-07-18 19:35:05.638 NSOperation[64600:7323407] 3 -[ViewController run:]
2016-07-18 19:35:05.638 NSOperation[64600:7323405] 3 -[ViewController run:]
2016-07-18 19:35:05.638 NSOperation[64600:7323407] 4 -[ViewController run:]
2016-07-18 19:35:05.638 NSOperation[64600:7323405] 4 -[ViewController run:]
2016-07-18 19:35:05.639 NSOperation[64600:7323407] 5 -[ViewController run:]
2016-07-18 19:35:05.639 NSOperation[64600:7323405] 5 -[ViewController run:]
2016-07-18 19:35:05.639 NSOperation[64600:7323407] 6 -[ViewController run:]
2016-07-18 19:35:05.639 NSOperation[64600:7323405] 6 -[ViewController run:]
2016-07-18 19:35:05.639 NSOperation[64600:7323407] 7 -[ViewController run:]
2016-07-18 19:35:05.640 NSOperation[64600:7323407] 8 -[ViewController run:]
2016-07-18 19:35:05.640 NSOperation[64600:7323405] 7 -[ViewController run:]
2016-07-18 19:35:05.640 NSOperation[64600:7323407] 9 -[ViewController run:]
2016-07-18 19:35:05.640 NSOperation[64600:7323405] 8 -[ViewController run:]
2016-07-18 19:35:05.641 NSOperation[64600:7323405] 9 -[ViewController run:]
           

而在這裡面有一個坑:

也就是如果你在建立block的時候調用了他的operation.name的話,就算你後面再進行設定,他的内容也不會發生改變。

代碼如下:

- (void)blockOperation{
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@ is complete 1",operation1.name);
    }];
    operation1.name = @"o1";

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@ is complete 2",operation1.name);
    }];
    operation2.name = @"o2";


    NSOperationQueue *queue = [[NSOperationQueue alloc]init];



    [queue addOperation:operation1];
    [queue addOperation:operation2];

}
           

結果如下:

2016-07-18 19:40:20.363 NSOperation[64660:7332789] 1
2016-07-18 19:40:20.364 NSOperation[64660:7332789] 2
2016-07-18 19:40:20.364 NSOperation[64660:7333007] (null) is complete 1
2016-07-18 19:40:20.364 NSOperation[64660:7332999] o1 is complete 2
           

Custom Operation

最後再來講講自定義的Operation

由于要自定義,而且operation預設情況下是抽象類,是以我們對裡面的部分内容必須要得到實作。

  1. 重寫main方法
  2. 為了能夠順利的使用NSOperation中的cancel方法,我們需要在main方法中添加isCancel來判斷是否被終止,進而确定能夠在調用這個線程的時候,順利終止這個線程。

代碼如下:

- (void)main {
    if (self.isCancelled) return;

    // 擷取圖檔資料
    NSURL *url = [NSURL URLWithString:self.imageUrl];
    NSData *imageData = [NSData dataWithContentsOfURL:url];

    if (self.isCancelled) {
        url = nil;
        imageData = nil;
        return;
    }

    // 初始化圖檔
    UIImage *image = [UIImage imageWithData:imageData];

    if (self.isCancelled) {
        image = nil;
        return;
    }

    if ([self.delegate respondsToSelector:@selector(downloadFinishWithImage:)]) {
        // 把圖檔資料傳回到主線程
        [(NSObject *)self.delegate performSelectorOnMainThread:@selector(downloadFinishWithImage:) withObject:image waitUntilDone:NO];
    }
}