Objective-C-(四)-多線程

介紹多線程前先來理解下進(jìn)程和線程的概念:

進(jìn)程:一個(gè)在前臺(tái)正在運(yùn)行的應(yīng)用程序就是一個(gè)進(jìn)程琼稻。比如打開的微信APP就是一個(gè)進(jìn)程壶唤。

線程:微信APP可以聊天绞吁,發(fā)圖片朦前,而做這些事情都是要通過線程來做的厘唾。線程就是執(zhí)行任務(wù)的基本單元褥符,是CPU調(diào)度和分派的基本單位。一個(gè)進(jìn)程可以有多個(gè)線程抚垃,線程是進(jìn)程的一部分喷楣。

多線程就是多個(gè)線程并發(fā)處理任務(wù)的技術(shù),可以充分利用多核CPU的資源鹤树,提升執(zhí)行性能铣焊。

iOS中處理和操作線程的方案有PthreadsNSThread罕伯、GCD曲伊、NSOperation && NSOperationQueuePthreads 比較底層追他,沒有用過坟募,就不說了,主要來說下NSThread邑狸、GCDNSOperation && NSOperationQueue懈糯。

介紹之前先說下兩個(gè)概念:同步/異步,串行/并發(fā)

  • 同步是指在執(zhí)行任務(wù)的時(shí)候单雾,會(huì)等待當(dāng)前這個(gè)任務(wù)執(zhí)行完畢后再繼續(xù)向下執(zhí)行赚哗。如果當(dāng)前這個(gè)任務(wù)沒有執(zhí)行完畢,那么就會(huì)阻塞當(dāng)前的線程直到這個(gè)任務(wù)執(zhí)行完成铁坎。

  • 異步是指在執(zhí)行任務(wù)的時(shí)候蜂奸,不會(huì)等待當(dāng)前這個(gè)任務(wù)執(zhí)行完畢就會(huì)繼續(xù)向下執(zhí)行犁苏。即使當(dāng)前這個(gè)任務(wù)沒有執(zhí)行完硬萍,也會(huì)立刻執(zhí)行下面的任務(wù)。

同步異步的區(qū)別可以理解為:例如方法A內(nèi)部中有個(gè)方法B围详,當(dāng)前的方法A內(nèi)部執(zhí)行到了方法B朴乖,如果是同步,會(huì)等這個(gè)方法B執(zhí)行完返回后才會(huì)向下執(zhí)行助赞,如果方法B沒有返回买羞,當(dāng)前的線程就會(huì)一直卡住不向下執(zhí)行;如果是異步的話這個(gè)方法B會(huì)立刻返回雹食,然后繼續(xù)向下執(zhí)行畜普。

//方法A
- (void)methodA {
 ....其他任務(wù)
 [self methodB]
 ....其他任務(wù)
}
//方法B
- (void)methodB {
 ...
}
  • 串行:執(zhí)行任務(wù)的時(shí)候按順序執(zhí)行,一次只能執(zhí)行一個(gè)任務(wù)群叶。當(dāng)前的任務(wù)沒有執(zhí)行完吃挑,不會(huì)執(zhí)行下一個(gè)钝荡。

  • 并發(fā):執(zhí)行任務(wù)的時(shí)候可以多個(gè)任務(wù)同時(shí)執(zhí)行。

NSThread

NSThread是一個(gè)直接面向線程的類舶衬,提供了很多可以操作線程的方法埠通。最簡單的創(chuàng)建一個(gè)線程:

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(doThead:) object:@"zzy"];

這樣就創(chuàng)建了一個(gè)線程實(shí)例threadthread通過doThead:這個(gè)方法執(zhí)行任務(wù)逛犹。但是這個(gè)時(shí)候創(chuàng)建的線程并沒有啟動(dòng)執(zhí)行任務(wù)端辱,需要調(diào)用下start方法:[thread start];這樣線程就開始執(zhí)行任務(wù)了。object:@"zzy"參數(shù)會(huì)在線程執(zhí)行doThead:方法的時(shí)候被作為參數(shù)傳入虽画。

我們也可以給一個(gè)線程設(shè)置一個(gè)名字作為標(biāo)記:[thread setName:@"zzy"]; 通過[NSThread currentThread]獲取當(dāng)前線程的信息舞蔽,打印出當(dāng)前這個(gè)線程可以看到:

XXX[67222:788960] thread = <NSThread: 0x600000662600>{number = 3, name = zzy}

通過name我們可以找到我們?cè)O(shè)置的是哪個(gè)thread

NSThread 也提供了一些關(guān)于線程的狀態(tài)信息码撰,例如:executing喷鸽,finishedcancelled灸拍,分別表示當(dāng)前的線程正在執(zhí)行做祝,完成,取消的狀態(tài)鸡岗。但是cancelled只是一個(gè)標(biāo)記狀態(tài)混槐,并不會(huì)取消線程,如果想強(qiáng)制退出當(dāng)前的線程轩性,可以通過[NSThread exit];声登。

NSThread的使用比較簡單,主要是注意通過NSThread創(chuàng)建的子線程中指定NSTimer計(jì)時(shí)器的情況揣苏。由于子線程中的runloop默認(rèn)不啟動(dòng)悯嗓,所以當(dāng)添加一個(gè)定時(shí)器重復(fù)執(zhí)行任務(wù)的時(shí)候要指定啟動(dòng)runloop。

- (void)doThead:(NSString *)object {    
   NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerRepeatAction:) userInfo:@{@"id":@"zzy"} repeats:YES];
   [timer fire];
   [[NSRunLoop currentRunLoop] run];
}

GCD

GCD是平時(shí)最常用的多線程處理的方案了卸察,我們將任務(wù)放進(jìn)block中追加到隊(duì)列里脯厨,系統(tǒng)就會(huì)自動(dòng)為我們創(chuàng)建相應(yīng)的線程去處理任務(wù),并且任務(wù)完成后在合適的時(shí)機(jī)銷毀線程坑质,整個(gè)過程中不需要我們?nèi)ブ苯硬僮骶€程合武,所以使用起來比較方便。

GCD有三種隊(duì)列:串行隊(duì)列涡扼,并發(fā)隊(duì)列稼跳,主隊(duì)列(特殊的串行隊(duì)列,隊(duì)列中的任務(wù)一定會(huì)在主線程中執(zhí)行)

GCD獲取隊(duì)列的方式有兩種:

  • 第一種通過dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);函數(shù)直接創(chuàng)建隊(duì)列吃沪,第一個(gè)參數(shù)的隊(duì)列的名稱汤善,方便調(diào)試使用,第二個(gè)是隊(duì)列的類型。傳NULLDISPATCH_QUEUE_SERIAL表示串行隊(duì)列红淡,傳DISPATCH_QUEUE_CONCURRENT表示并發(fā)隊(duì)列卸伞。
    dispatch_queue_t queue = dispatch_queue_create("zzy", NULL);
  • 第二種是直接獲取系統(tǒng)提供好的兩個(gè)隊(duì)列:

    • dispatch_get_main_queue:主隊(duì)列,運(yùn)行在主線程中的串行隊(duì)列

    • dispatch_get_global_queue:全局隊(duì)列锉屈,也就是并發(fā)隊(duì)列荤傲,通過這個(gè)函數(shù)的第一個(gè)參數(shù)還可以指定隊(duì)列的優(yōu)先級(jí)。

GCD中平時(shí)常用的有以下幾個(gè)函數(shù):

  • dispathc_once:確保函數(shù)中的block只執(zhí)行一次颈渊,而且是線程安全的遂黍。常用來實(shí)現(xiàn)單利對(duì)象。

  • dispatch_after:延遲指定的時(shí)間之后提交某個(gè)任務(wù)(這里是延遲某個(gè)時(shí)間提交任務(wù)俊嗽,而不是延遲某個(gè)時(shí)間執(zhí)行任務(wù))雾家。常用做一個(gè)定時(shí)操作。

  • dispatch_suspend&&dispatch_resume:暫停和恢復(fù)某個(gè)隊(duì)列

  • dispatch_apply:循環(huán)將某些任務(wù)加入到某個(gè)隊(duì)列當(dāng)中

  • dispatch_set_target_queue:可以設(shè)置目標(biāo)隊(duì)列的優(yōu)先級(jí)绍豁,讓目標(biāo)隊(duì)列的優(yōu)先級(jí)和指定的隊(duì)列優(yōu)先級(jí)一樣芯咧。(由于dispatch_queue_create函數(shù)創(chuàng)建的隊(duì)列沒有提供設(shè)置優(yōu)先級(jí)的參數(shù),默認(rèn)是默認(rèn)的優(yōu)先級(jí)竹揍,所以可以用這個(gè)函數(shù)來設(shè)置其他優(yōu)先級(jí)敬飒,例如dispatch_set_target_queue(targetQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));

  • dispatch_barrier_async:在一個(gè)并發(fā)隊(duì)列中,先并行處理一部分任務(wù)芬位,然后同步執(zhí)行dispatch_barrier_async中的任務(wù)(只執(zhí)行當(dāng)前block中的一個(gè)任務(wù)无拗,其他任務(wù)不執(zhí)行),dispatch_barrier_async任務(wù)執(zhí)行完成后昧碉,然后再恢復(fù)當(dāng)前隊(duì)列并行執(zhí)行的任務(wù)英染。常用來處理在多線程數(shù)據(jù)讀取的時(shí)候插入寫入操作(寫入操作必須是線程安全的)。

這里主要介紹下平時(shí)處理異步任務(wù)的時(shí)候比較有用的任務(wù)組dispatch_group_t和信號(hào)量dispatch_semaphore_t被饿。

dispatch_group_t

dispatch_group_t是一個(gè)任務(wù)組四康,可以將幾個(gè)并發(fā)任務(wù)一起放到任務(wù)組里面,當(dāng)這幾個(gè)并發(fā)任務(wù)都執(zhí)行完成后狭握,同步得到通知回調(diào)闪金。涉及到兩個(gè)常用的函數(shù):dispatch_waitdispatch_notify

dispatch_wait是一個(gè)同步的函數(shù)哥牍,一旦被調(diào)用該函數(shù)就會(huì)一直處于調(diào)用的狀態(tài)而不返回毕泌,直到dispatch group內(nèi)的任務(wù)都執(zhí)行完成或者經(jīng)過dispatch_wait中第二參數(shù)指定的時(shí)間后它才會(huì)返回喝检,否則它會(huì)一直阻塞當(dāng)前的線程嗅辣,無法繼續(xù)執(zhí)行。如果dispatch_wait函數(shù)返回值為0挠说,說明group內(nèi)的任務(wù)已經(jīng)執(zhí)行完畢澡谭;如果返回值不為0,說明經(jīng)過了指定的時(shí)間group內(nèi)的任務(wù)依然沒有執(zhí)行完。

- (void)dispathGroup {
/**
 使用dispatch_group_t也可以向group中指定不同優(yōu)先級(jí)的隊(duì)列蛙奖,不一定非要是同一個(gè)隊(duì)列潘酗,他們都?xì)w屬同一個(gè)group
*/
 dispatch_group_t group = dispatch_group_create();
 dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 dispatch_queue_t highQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
 dispatch_group_async(group, defaultQueue, ^{
 sleep(2);
 NSLog(@"任務(wù)一");
 });
 dispatch_group_async(group, defaultQueue, ^{
 sleep(2);
 NSLog(@"任務(wù)二");
 });
 dispatch_group_async(group, highQueue, ^{
 NSLog(@"高優(yōu)先級(jí)任務(wù)");
 });
 dispatch_wait(group, DISPATCH_TIME_FOREVER);
 NSLog(@"任務(wù)執(zhí)行完成");
}

執(zhí)行后:

2019-01-01 10:09:53.092345+0800 XXX[12671:1433314] 高優(yōu)先級(jí)任務(wù)
2019-01-01 10:09:55.094704+0800 XXX[12671:1433327] 任務(wù)二
2019-01-01 10:09:55.094704+0800 XXX[12671:1433313] 任務(wù)一
2019-01-01 10:09:55.095105+0800 XXX[12671:1433235] 任務(wù)執(zhí)行完成

可以看到dispatch_wait 會(huì)一直阻塞當(dāng)前的線程,直到任務(wù)執(zhí)行完成(這里指定的時(shí)間是永久等待雁仲,也可以自己定義臨界時(shí)間)仔夺。

dispatch_notify提供了一個(gè)異步執(zhí)行的函數(shù),它不會(huì)阻塞當(dāng)前的線程攒砖,但是會(huì)監(jiān)聽dispatch group的任務(wù)執(zhí)行情況缸兔,一旦group內(nèi)的都執(zhí)行完成后就會(huì)調(diào)用dispatch_notify函數(shù)。dispatch_notify(object, queue, notification_block)函數(shù)提供了三個(gè)參數(shù)吹艇,第一個(gè)參數(shù)就是監(jiān)聽的dispatch group惰蜜,另外兩個(gè)參數(shù)提供了可以讓某個(gè)任務(wù)在指定隊(duì)列中執(zhí)行的功能。也就說當(dāng)dispatch group內(nèi)的任務(wù)都完成后會(huì)通知dispatch_notify然后可以進(jìn)行一些完成后的處理操作受神。

- (void)dispathGroup {
 dispatch_group_t group = dispatch_group_create();
 dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 dispatch_queue_t highQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
 dispatch_group_async(group, defaultQueue, ^{
 sleep(2);
 NSLog(@"任務(wù)一");
 });
 dispatch_group_async(group, defaultQueue, ^{
 sleep(2);
 NSLog(@"任務(wù)二");
 });
 dispatch_group_async(group, highQueue, ^{
 NSLog(@"高優(yōu)先級(jí)任務(wù)");
 });

 dispatch_notify(group, dispatch_get_main_queue(), ^{
 NSLog(@"dispatch group執(zhí)行完成");
 });
 NSLog(@"任務(wù)執(zhí)行完成");
}

執(zhí)行后:

2019-01-01 10:28:40.864817+0800 XXX[12801:1454449] 任務(wù)執(zhí)行完成
2019-01-01 10:28:40.864821+0800 XXX[12801:1454557] 高優(yōu)先級(jí)任務(wù)
2019-01-01 10:28:42.865759+0800 XXX[12801:1454555] 任務(wù)二
2019-01-01 10:28:42.865760+0800 XXX[12801:1454558] 任務(wù)一
2019-01-01 10:28:42.866070+0800 XXX[12801:1454449] dispatch group執(zhí)行完成

dispatch_notify函數(shù)提供的這種獲取dispatch group內(nèi)任務(wù)完成后的通知抛猖,然后再異步執(zhí)行處理很常用。

如果對(duì)同步鼻听、異步理解的不夠深刻的話财著,使用dispatch_group_async函數(shù)的時(shí)候可能會(huì)導(dǎo)致一個(gè)錯(cuò)誤的認(rèn)識(shí)。比如在dispatch_group_async中添加幾個(gè)網(wǎng)絡(luò)請(qǐng)求的任務(wù)撑碴,會(huì)發(fā)現(xiàn)請(qǐng)求還沒完成就執(zhí)行了dispatch_notify回調(diào)瓢宦。這是因?yàn)榘l(fā)起網(wǎng)絡(luò)請(qǐng)求本身就是一個(gè)任務(wù),在把這個(gè)任務(wù)通過block追加到dispatch_group_async中的隊(duì)列后就算完成了灰羽,并不會(huì)等待網(wǎng)絡(luò)請(qǐng)求的完成驮履,因?yàn)榫W(wǎng)絡(luò)請(qǐng)求本身也是異步的。所以在遇到這種異步操作情況的時(shí)候廉嚼,可以用另外一對(duì)函數(shù)將來判定任務(wù)完成情況:dispatch_group_enter(添加到group中)玫镐、dispatch_group_leave(執(zhí)行完成退出group)。

這兩個(gè)函數(shù)必須同時(shí)存在怠噪,類似于引用計(jì)數(shù)恐似,這是對(duì)group內(nèi)的任務(wù)的遞增和遞減,如果只有遞增沒有遞減傍念,那么group的任務(wù)就會(huì)永遠(yuǎn)執(zhí)行不完矫夷,也就一直不會(huì)回調(diào)dispatch_notify函數(shù),或者dispatch_wait函數(shù)一直不會(huì)返回憋槐。

- (void)dispathGroup {
 dispatch_group_t group = dispatch_group_create();
 dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 dispatch_queue_t highQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

 dispatch_group_enter(group);
 dispatch_async(defaultQueue, ^{
 sleep(2);
 NSLog(@"任務(wù)一");
 dispatch_group_leave(group);
 });

 dispatch_group_enter(group);
 dispatch_async(defaultQueue, ^{
 sleep(2);
 NSLog(@"任務(wù)二");
 dispatch_group_leave(group);
 });

 dispatch_group_enter(group);
 dispatch_async(highQueue, ^{
 NSLog(@"高優(yōu)先級(jí)任務(wù)");
 dispatch_group_leave(group);
 });

 dispatch_notify(group, dispatch_get_main_queue(), ^{
 NSLog(@"dispatch group執(zhí)行完成");
 });
 NSLog(@"任務(wù)執(zhí)行完成");
}

dispatch_semaphore_t

dispatch_semaphore_t 信號(hào)量一共就三個(gè)函數(shù)双藕,用起來比較簡單,只是需要結(jié)合不同的場(chǎng)景去理解才能發(fā)揮很大的用處:

  • dispatch_semaphore_create : 創(chuàng)建一個(gè)信號(hào)量阳仔,并指定信號(hào)的初始化個(gè)數(shù)

  • dispatch_semaphore_wait:鎖住當(dāng)前的線程忧陪,等待信號(hào)的計(jì)數(shù)大于等于1,然后將計(jì)數(shù)減去1,并且該函數(shù)返回嘶摊。如果信號(hào)是0延蟹,則該函數(shù)一直不返回麻捻,阻塞當(dāng)前的線程岩四。

  • dispatch_semaphore_signal:釋放信號(hào),讓信號(hào)計(jì)數(shù)加1

在很多開源庫中都使用dispatch_semaphore_t作為鎖來處理多線程時(shí)對(duì)數(shù)據(jù)寫入的保護(hù)速妖。例如下面示例代碼

- (void)dispatchSemaphore {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    NSMutableArray *array = [NSMutableArray array];
    for (int i = 0; i < 1000; i++) {
        dispatch_async(queue, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            [array addObject:[NSNumber numberWithInt:i]];
            dispatch_semaphore_signal(semaphore);
        });
    }
}

在一個(gè)異步并發(fā)線程中虱颗,對(duì)數(shù)組進(jìn)行添加元素的操作俯萌。由于會(huì)有多個(gè)線程同時(shí)對(duì)array進(jìn)行寫入操作,如果不加dispatch_semaphore_t則很可能會(huì)導(dǎo)致內(nèi)存訪問錯(cuò)誤導(dǎo)致程序終止上枕。

加了dispatch_semaphore_t后咐熙,將信號(hào)的計(jì)數(shù)指定為1,每當(dāng)執(zhí)行一次dispatch_semaphore_wait函數(shù)后辨萍,信號(hào)就會(huì)減1棋恼,此時(shí)信號(hào)為0,則阻塞線程無法執(zhí)行下面的[array addObject:[NSNumber numberWithInt:i]];方法锈玉,直到前面的線程執(zhí)行完對(duì)數(shù)組的操作然后執(zhí)行dispatch_semaphore_signal函數(shù)使信號(hào)加1爪飘,當(dāng)前的線程才能訪問數(shù)組,這樣一來拉背,每次對(duì)數(shù)組的訪問操作都只能有一個(gè)線程师崎,就保護(hù)了數(shù)據(jù)訪問的安全。當(dāng)然對(duì)本例也可以用其他方法來處理椅棺,例如指定一個(gè)串行隊(duì)列犁罩。

通過dispatch_semaphore_t信號(hào)量和dispatch_group_t的組合,也能實(shí)現(xiàn)代替上面例子中使用dispatch_group_enter两疚、dispatch_group_leave的效果床估。

- (void)dispatchSemaphoreGroup {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_async(group, defaultQueue, ^{
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        dispatch_async(defaultQueue, ^{
            sleep(2);
            NSLog(@"任務(wù)一");
            dispatch_semaphore_signal(semaphore);
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });
    
    dispatch_group_async(group, defaultQueue, ^{
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        dispatch_async(defaultQueue, ^{
            sleep(2);
            NSLog(@"任務(wù)二");
            dispatch_semaphore_signal(semaphore);
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });
    
    dispatch_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"dispatch group執(zhí)行完成");
    });
    NSLog(@"任務(wù)執(zhí)行完成");
}

在上面的代碼中,每次追加block中的任務(wù)到group的時(shí)候诱渤,都先創(chuàng)建一個(gè)信號(hào)計(jì)數(shù)為0的信號(hào)量丐巫,然后開啟一個(gè)block內(nèi)部的異步任務(wù)的執(zhí)行,在block的最后使用dispatch_semaphore_wait鎖住當(dāng)前的線程勺美,讓當(dāng)前的block無法返回递胧,則group的任務(wù)就一直無法完成。直到block內(nèi)部的這個(gè)異步任務(wù)執(zhí)行完成后釋放信號(hào)赡茸,通過dispatch_semaphore_signal讓信號(hào)加1缎脾,則block返回,group內(nèi)的任務(wù)完成坛掠,再回調(diào)dispatch_notify函數(shù)赊锚。

信號(hào)量在網(wǎng)絡(luò)請(qǐng)求的同步處理治筒,資源競(jìng)爭等情況下可以發(fā)揮很大的用處屉栓,可以使用信號(hào)量來解決這些問題舷蒲。

NSOperation && NSOperationQueue

相對(duì)于GCD都是純C的函數(shù),NSOperation提供了一個(gè)更高層面面向?qū)ο蟮亩嗑€程處理方案友多。NSOperation是用來封裝任務(wù)的一個(gè)類牲平。我們把需要執(zhí)行的任務(wù)封裝進(jìn)NSOperation實(shí)例當(dāng)中,通過調(diào)用start方法它會(huì)自動(dòng)去執(zhí)行這些任務(wù)域滥。它也提供了很多種的狀態(tài)便于我們觀測(cè)了解當(dāng)前任務(wù)的執(zhí)行狀態(tài)纵柿,例如:isCancelledisReady启绰,isExecuting昂儒,isFinished等。NSOperation是一個(gè)抽象類委可,我們不能直接使用NSOperation類的去處理任務(wù)渊跋,系統(tǒng)提供了兩個(gè)子類NSInvocationOperationNSBlockOperation方便我們使用,也可以自己創(chuàng)建一個(gè)NSOperation的子類去實(shí)現(xiàn)着倾。

NSInvocationOperation提供了一種target - action模式響應(yīng)的處理任務(wù)類拾酝,通過指定target,去對(duì)應(yīng)執(zhí)行action操作卡者。例如:

- (void)invocationOperationAction {
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationAction:) object:@"zzy"];
    [operation start];
}

- (void)invocationOperationAction:(id)userInfo {
    NSLog(@"userInfo = %@", userInfo);
}

NSBlockOperation則提供了block的方式去處理任務(wù)蒿囤,通過把任務(wù)封裝進(jìn)block塊中對(duì)應(yīng)去執(zhí)行。

NSBlockOperation *operaion = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"operation1");
}];
[operation start];

還可以給operation中添加多個(gè)任務(wù):

[operaion addExecutionBlock:^{
      NSLog(@"operation2");
}];
[operaion addExecutionBlock:^{
      NSLog(@"operation3");
}];
[operation start];

只有當(dāng)這些添加的Block中的任務(wù)都執(zhí)行完崇决,這個(gè)operation才算執(zhí)行完成材诽。

需要注意的是,如果通過手動(dòng)執(zhí)行NSOperation的start方法去啟動(dòng)任務(wù)的話恒傻,start方法是同步的岳守。NSOperation提供了一個(gè)只讀屬性isAsynchronous(也可以通過concurrent)來標(biāo)識(shí)當(dāng)前NSOperation是否是異步執(zhí)行,默認(rèn)這個(gè)屬性是NO碌冶。如同上面關(guān)于同步的解釋湿痢,start方法會(huì)阻塞當(dāng)前調(diào)用它的線程,直到operation的操作都完成扑庞∑┲兀看下例子:

- (void)blockOperationThread {
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(synchronousOperation) object:nil];
    [thread start];
}

- (void)synchronousOperation {
    NSBlockOperation *operaion = [NSBlockOperation blockOperationWithBlock:^{
        sleep(2);
        NSLog(@"operationBlock1");
        NSLog(@"operation1 Thread = %@", [NSThread currentThread]);
    }];
    [operaion addExecutionBlock:^{
        NSLog(@"operationBlock2");
        NSLog(@"operation2 Thread = %@", [NSThread currentThread]);
    }];
    [operaion start];
    NSLog(@"synchronousOperation....");
}

打印如下:

2019-01-01 11:17:30.504408+0800 XXX[11341:1269966] operationBlock2
2019-01-01 11:17:30.504966+0800 XXX[11341:1269966] operation2 Thread = <NSThread: 0x60400046be80>{number = 4, name = (null)}
2019-01-01 11:17:32.506224+0800 XXX[11341:1269987] operationBlock1
2019-01-01 11:17:32.506513+0800 XXX[11341:1269987] operation1 Thread = <NSThread: 0x600000474dc0>{number = 3, name = (null)}
2019-01-01 11:17:32.506980+0800 XXX[11341:1269987] synchronousOperation....

可以看到直到兩秒之后才執(zhí)行NSLog(@"synchronousOperation....");也就是說NSOperation的執(zhí)行是同步的。同時(shí)我們通過打印執(zhí)行NSOperation任務(wù)的線程發(fā)現(xiàn)罐氨,同一個(gè)operaion內(nèi)添加的多個(gè)任務(wù)臀规,執(zhí)行這些任務(wù)的線程并不是同一個(gè)。也就是說雖然NSOperation是同步的栅隐,但是operaion內(nèi)部添加的任務(wù)可能是在不同線程中并發(fā)執(zhí)行的塔嬉,這些任務(wù)執(zhí)行完成后才會(huì)向下執(zhí)行玩徊,有點(diǎn)類似GCD的dispatch_group

那么如何實(shí)現(xiàn)異步執(zhí)行的NSOperation呢谨究?可以通過兩種方式:一種是通過創(chuàng)建NSOperation子類自己去實(shí)現(xiàn)異步操作恩袱,另外一種是通過NSOperationQueue。我們平時(shí)用的比較多的是通過使用NSOperationQueue來實(shí)現(xiàn)異步處理NSOperation胶哲。

NSOperationQueue

通過NSOperationQueue來實(shí)現(xiàn)NSOperation的執(zhí)行比較簡單畔塔,直接將NSOperation放進(jìn)NSOperationQueue的隊(duì)列中就可以了。NSOperationQueue會(huì)自動(dòng)為NSOperation創(chuàng)建線程并且調(diào)用它的start方法啟動(dòng)任務(wù)鸯屿。當(dāng)然NSOperation添加到NSOperationQueue中后也不一定會(huì)立刻被執(zhí)行澈吨,如果NSOperationQueue中有很多個(gè)operation,當(dāng)前的operation會(huì)在隊(duì)列排到它以后自動(dòng)執(zhí)行寄摆。

通過NSOperationQueue來執(zhí)行operation:

//將上面的代碼改為NSOperationQueue來執(zhí)行
- (void)synchronousOperation {
    NSBlockOperation *operaion = [NSBlockOperation blockOperationWithBlock:^{
        sleep(2);
        NSLog(@"operationBlock1");
        NSLog(@"operation1 Thread = %@", [NSThread currentThread]);
    }];
    [operaion addExecutionBlock:^{
        NSLog(@"operationBlock2");
        NSLog(@"operation2 Thread = %@", [NSThread currentThread]);
    }];

//    [operaion start];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operaion];
    NSLog(@"synchronousOperation");
}

打印如下:

2019-01-01 11:42:41.789280+0800 XXX[11463:1298262] synchronousOperation
2019-01-01 11:42:41.789611+0800 XXX[11463:1298233] operationBlock2
2019-01-01 11:42:41.791127+0800 XXX[11463:1298233] operation2 Thread = <NSThread: 0x600000465680>{number = 4, name = (null)}
2019-01-01 11:42:43.790340+0800 XXX[11463:1298241] operationBlock1
2019-01-01 11:42:43.790851+0800 XXX[11463:1298241] operation1 Thread = <NSThread: 0x60400066a440>{number = 5, name = (null)}

可以看到operation不會(huì)阻塞當(dāng)前的線程谅辣,會(huì)自動(dòng)的異步去執(zhí)行任務(wù)。

自定義NSOperation

通過自定義NSOperation可以實(shí)現(xiàn)更多的定制化任務(wù)處理婶恼,例如異步執(zhí)行NSOperation桑阶。自定義NSOperation要實(shí)現(xiàn)以下幾個(gè)方法:

  • start :在start方法中實(shí)現(xiàn)異步處理的操作,不論是調(diào)用異步處理函數(shù)還是說開啟新的線程熙尉。而且要及時(shí)通過KVO更新operation當(dāng)前的狀態(tài):isExecuting联逻,isFinished,以便于通知它的監(jiān)測(cè)者检痰。

  • main 官方文檔中建議在這個(gè)方法中實(shí)現(xiàn)執(zhí)行任務(wù)的操作包归。當(dāng)然也可以不實(shí)現(xiàn)這個(gè)方法,直接在start中實(shí)現(xiàn)處理任務(wù)也可以铅歼。SDWebImage中的自定義operation就只實(shí)現(xiàn)了start方法公壤。

  • isConcurrent是否是并發(fā),返回YES即可椎椰。

  • isExecuting:是否正在執(zhí)行

  • isFinished 是否完成厦幅,包括任務(wù)完成和取消,取消也是Finished慨飘。

我自己沒有實(shí)現(xiàn)過自定義NSOperation确憨,之前看SDWebImage的時(shí)候看過里面自定義SDWebImageDownloaderOperation類實(shí)現(xiàn)了異步處理圖片下載請(qǐng)求,寫了很多東西瓤的,要控制各種狀態(tài)休弃,并自行手動(dòng)實(shí)現(xiàn)KVO(因?yàn)橐陨蠋追N狀態(tài)都是readonly),感興趣的可以參考SDWebImage圈膏。

添加任務(wù)依賴

NSOperation可以指定任務(wù)間的依賴塔猾,一個(gè)任務(wù)A可以依賴于另外一個(gè)任務(wù)B,任務(wù)B沒有執(zhí)行完成稽坤,任務(wù)A不會(huì)開始執(zhí)行丈甸。這里要注意的是糯俗,任務(wù)B不一定非要是執(zhí)行成功,因?yàn)槿∠菜闶峭瓿?code>isFinished睦擂。

[operation2 addDependency:operaion1];

添加依賴一定要在operation 執(zhí)行start方法或者添加到NSOperationQueue前得湘,否則無效。

而且這兩個(gè)operation不一定非要是在同一個(gè)NSOperationQueue中祈匙,不同的NSOperationQueue中的operation也可以指定依賴忽刽。

- (void)blockOperationAction {
    NSBlockOperation *operaion1 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(2);
        NSLog(@"operation1");
        NSLog(@"operation1 Thread = %@", [NSThread currentThread]);
    }];
    
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation2");
        NSLog(@"operation2 Thread = %@", [NSThread currentThread]);
    }];
    
    [operation2 addDependency:operaion1];

    NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
    [queue1 addOperation:operaion1];
    
    NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
    [queue2 addOperation:operation2];
}

打印如下:

2019-01-01 12:29:44.008728+0800 XXX[11812:1350215] operation1
2019-01-01 12:29:44.009073+0800 XXX[11812:1350215] operation1 Thread = <NSThread: 0x60400047ca00>{number = 3, name = (null)}
2019-01-01 12:29:44.009623+0800 XXX[11812:1350214] operation2
2019-01-01 12:29:44.009877+0800 XXX[11812:1350214] operation2 Thread = <NSThread: 0x60400047cac0>{number = 4, name = (null)}

NSOperation還有很多功能:

  • 可以通過queuePriority屬性指定在隊(duì)列中的優(yōu)先級(jí)天揖;

  • 可以通過setCompletionBlock:方法設(shè)置任務(wù)完成的回調(diào)夺欲;

  • 可以手動(dòng)cancel取消掉一個(gè)任務(wù);也可以通過調(diào)用NSOperationQueuecancelAllOperations方法取消隊(duì)列中所有的任務(wù)今膊。

  • 可以通過NSOperationQueuesuspended來暫停隊(duì)列中的任務(wù)些阅。不過它只是暫停operation queue調(diào)度新的任務(wù),并不會(huì)暫停正在執(zhí)行的任務(wù)斑唬。

  • 還可以通過NSOperationQueuemaxConcurrentOperationCount屬性來設(shè)置operation queue中任務(wù)的最大并發(fā)數(shù)市埋。如果設(shè)置為1那就是串行執(zhí)行。但是這里的串行執(zhí)行順序并不一定恕刘,要看當(dāng)然operation的優(yōu)先級(jí)和isReady的狀態(tài)缤谎。

NSOperation提供的對(duì)于任務(wù)的控制很豐富很靈活,可以做很多事情褐着。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坷澡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子含蓉,更是在濱河造成了極大的恐慌频敛,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件馅扣,死亡現(xiàn)場(chǎng)離奇詭異斟赚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)差油,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門拗军,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蓄喇,你說我怎么就攤上這事发侵。” “怎么了公罕?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵器紧,是天一觀的道長。 經(jīng)常有香客問我楼眷,道長铲汪,這世上最難降的妖魔是什么熊尉? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮掌腰,結(jié)果婚禮上狰住,老公的妹妹穿的比我還像新娘。我一直安慰自己齿梁,他們只是感情好催植,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著勺择,像睡著了一般创南。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上省核,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天稿辙,我揣著相機(jī)與錄音,去河邊找鬼气忠。 笑死邻储,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的旧噪。 我是一名探鬼主播吨娜,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼淘钟!你這毒婦竟也來了宦赠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤日月,失蹤者是張志新(化名)和其女友劉穎袱瓮,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體爱咬,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡尺借,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了精拟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片燎斩。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蜂绎,靈堂內(nèi)的尸體忽然破棺而出栅表,到底是詐尸還是另有隱情,我是刑警寧澤师枣,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布怪瓶,位于F島的核電站,受9級(jí)特大地震影響践美,放射性物質(zhì)發(fā)生泄漏洗贰。R本人自食惡果不足惜找岖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望敛滋。 院中可真熱鬧许布,春花似錦、人聲如沸绎晃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽庶艾。三九已至袁余,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間落竹,已是汗流浹背泌霍。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國打工货抄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留述召,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓蟹地,卻偏偏與公主長得像积暖,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子怪与,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容