iOS多線程(二):NSOperation 自定義子類實現(xiàn)非并發(fā)和并發(fā)操作

1 自定義非并行的 NSOperation

前文介紹過 NSInvocationOperation 和 NSBlockOperation 都繼承自NSOperation類。

我們亦可以通過繼承 NSOperation 類倔监,來自定義非并行的 Operation。

@interface VinnyOperation : NSOperation
@end

頭文件很簡單,只需要繼承 NSOperation ,可根據(jù)實際需要決定是否需要自定義init方法够话。而且僅僅需要自定義main方法禁熏,將需要執(zhí)行的操作寫在main方法中。

#import "VinnyOperation.h"

@implementation VinnyOperation
- (void)main
{
    NSLog(@"main begin");
    @try {
        // 提供一個變量標識明也,來表示需要執(zhí)行的操作是否完成了,當然惯裕,沒開始執(zhí)行之前温数,為NO
        BOOL taskIsFinished = NO;
        // while 保證:只有當沒有執(zhí)行完成和沒有被取消,才執(zhí)行自定義的相應(yīng)操作
        while (taskIsFinished == NO && [self isCancelled] == NO){
            // 自定義的操作
            sleep(10);  // 睡眠模擬耗時操作
            NSLog(@"currentThread = %@", [NSThread currentThread]);
            NSLog(@"mainThread    = %@", [NSThread mainThread]);
            // 這里相應(yīng)的操作都已經(jīng)完成蜻势,后面就是要通知KVO我們的操作完成了撑刺。
            taskIsFinished = YES;
        }
    }
    @catch (NSException * e) {
        NSLog(@"Exception %@", e);
    }
    NSLog(@"main end");
}
@end

使用的時候也非常簡單

    VinnyOperation *op = [[VinnyOperation alloc] init];
    NSLog(@"start before");
    [op start];
    NSLog(@"start after");

看一下控制臺打印的結(jié)果

2015-12-29 19:21:56.895 test[67010:50712745] start before
2015-12-29 19:21:56.896 test[67010:50712745] main begin
2015-12-29 19:22:06.900 test[67010:50712745] currentThread = <NSThread: 0x7fc560d044d0>{number = 1, name = main}
2015-12-29 19:22:06.900 test[67010:50712745] mainThread    = <NSThread: 0x7fc560d044d0>{number = 1, name = main}
2015-12-29 19:22:06.900 test[67010:50712745] main end
2015-12-29 19:22:06.900 test[67010:50712745] start after

可以看出是main方法是非并行的,而且執(zhí)行的操作與調(diào)用start是在同一個線程中握玛。

2 自定義并行的 NSOperation

自定義并行的 NSOperation 則要復(fù)雜一點够傍,首先必須重寫以下幾個方法:

  • start: 所有并行的 Operations 都必須重寫這個方法,然后在你想要執(zhí)行的線程中手動調(diào)用這個方法挠铲。注意:任何時候都不能調(diào)用父類的start方法王带。
  • main: 在start方法中調(diào)用,但是注意要定義獨立的自動釋放池與別的線程區(qū)分開市殷。
  • isExecuting: 是否執(zhí)行中愕撰,需要實現(xiàn)KVO通知機制。
  • isFinished: 是否已完成醋寝,需要實現(xiàn)KVO通知機制搞挣。
  • isConcurrent: 該方法現(xiàn)在已經(jīng)由isAsynchronous方法代替,并且 NSOperationQueue 也已經(jīng)忽略這個方法的值音羞。
  • isAsynchronous: 該方法默認返回 NO 囱桨,表示非并發(fā)執(zhí)行。并發(fā)執(zhí)行需要自定義并且返回 YES嗅绰。后面會根據(jù)這個返回值來決定是否并發(fā)舍肠。

與非并發(fā)操作不同的是搀继,需要另外自定義一個方法來執(zhí)行操作而不是直接調(diào)用start方法

@interface MyOperation : NSOperation
- (BOOL)performOperation:(NSOperation*)anOp;    // 執(zhí)行操作調(diào)用這個方法
@end

實現(xiàn)其中的必要方法:

#import "MyOperation.h"

@interface MyOperation () {
    BOOL        executing;  // 執(zhí)行中
    BOOL        finished;   // 已完成
}
@end

@implementation MyOperation
- (id)init {
    self = [super init];
    if (self) {
        executing = NO;
        finished = NO;
    }
    return self;
}

- (void)start {
    // Always check for cancellation before launching the task.
    if ([self isCancelled])
    {
        // Must move the operation to the finished state if it is canceled.
        [self willChangeValueForKey:@"isFinished"];
        finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }
    
    // If the operation is not canceled, begin executing the task.
    [self willChangeValueForKey:@"isExecuting"];
    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    executing = YES;
    [self didChangeValueForKey:@"isExecuting"];
}

- (void)main {
    NSLog(@"main begin");
    @try {
        // 必須為自定義的 operation 提供 autorelease pool,因為 operation 完成后需要銷毀翠语。
        @autoreleasepool {
            // 提供一個變量標識叽躯,來表示需要執(zhí)行的操作是否完成了,當然肌括,沒開始執(zhí)行之前点骑,為NO
            BOOL taskIsFinished = NO;
            // while 保證:只有當沒有執(zhí)行完成和沒有被取消,才執(zhí)行自定義的相應(yīng)操作
            while (taskIsFinished == NO && [self isCancelled] == NO){
                // 自定義的操作
                //sleep(10);  // 睡眠模擬耗時操作
                NSLog(@"currentThread = %@", [NSThread currentThread]);
                NSLog(@"mainThread    = %@", [NSThread mainThread]);
                
                // 這里相應(yīng)的操作都已經(jīng)完成谍夭,后面就是要通知KVO我們的操作完成了黑滴。
                taskIsFinished = YES;
            }
            [self completeOperation];
            
        }
    }
    @catch (NSException * e) {
        NSLog(@"Exception %@", e);
    }
    NSLog(@"main end");
}

- (void)completeOperation {
    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];
    
    executing = NO;
    finished = YES;
    
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}

// 已經(jīng)棄用,使用 isAsynchronous 代替
//- (BOOL)isConcurrent {
//    return NO;
//}

- (BOOL)isAsynchronous {
    return YES;
}

- (BOOL)isExecuting {
    return executing;
}

- (BOOL)isFinished {
    return finished;
}

// 執(zhí)行操作
- (BOOL)performOperation:(NSOperation*)anOp
{
    BOOL        ranIt = NO;
    
    if ([anOp isReady] && ![anOp isCancelled])
    {
        if (![anOp isAsynchronous]) {
            [anOp start];
        }
        else {
            [NSThread detachNewThreadSelector:@selector(start)
                                     toTarget:anOp withObject:nil];
        }
        ranIt = YES;
    }
    else if ([anOp isCancelled])
    {
        // If it was canceled before it was started,
        //  move the operation to the finished state.
        [self willChangeValueForKey:@"isFinished"];
        [self willChangeValueForKey:@"isExecuting"];
        executing = NO;
        finished = YES;
        [self didChangeValueForKey:@"isExecuting"];
        [self didChangeValueForKey:@"isFinished"];
        
        // Set ranIt to YES to prevent the operation from
        // being passed to this method again in the future.
        ranIt = YES;
    }
    return ranIt;
}

使用這個 Operation 如下:

    MyOperation *op = [[MyOperation alloc] init];
    NSLog(@"start before");
    [op performOperation:op];
    NSLog(@"start after");

看一下控制臺打印的結(jié)果

2015-12-29 20:01:53.130 test[27083:51105353] start before
2015-12-29 20:01:53.131 test[27083:51105353] start after
2015-12-29 20:01:53.131 test[27083:51105608] main begin
2015-12-29 20:01:53.131 test[27083:51105608] currentThread = <NSThread: 0x7ff148d976d0>{number = 3, name = (null)}
2015-12-29 20:01:53.131 test[27083:51105608] mainThread    = <NSThread: 0x7ff148e01250>{number = 1, name = (null)}
2015-12-29 20:01:53.131 test[27083:51105608] main end

可以看到這個操作是并發(fā)執(zhí)行紧索,并且是一個獨立的線程袁辈。

3 總結(jié)

  1. 如果需要自定義并發(fā)執(zhí)行的 Operation,必須重寫 start珠漂、main晚缩、isExecutingisFinished甘磨、isAsynchronous 方法。
  2. 在 operation 的 main 方法里面眯停,必須提供 autorelease pool,因為你的 operation 完成后需要銷毀济舆。
  3. 一旦你的 operation 開始了,必須通過 KVO莺债,告訴所有的監(jiān)聽者滋觉,現(xiàn)在該operation的執(zhí)行狀態(tài)。
  4. 調(diào)用時齐邦,如果需要并發(fā)執(zhí)行 Operation椎侠,必須調(diào)用performOperation:方法,當然措拇,也可以改為自定義其他方法或者直接在start方法添加多線程調(diào)用我纪。
  5. 對于自定義的 Operation 類,如果不需要并發(fā)執(zhí)行丐吓,可以直接調(diào)用start方法浅悉。

4 尾巴

剛開始看 NSOperation 的文檔沒搞明白怎么自定義多線程的操作,文檔里只是說需要自定義 isExecuting券犁、isFinished术健、isConcurrentisAsynchronous 這四個方法粘衬,然后說根據(jù) isAsynchronous 的返回值來判斷是否多線程荞估,我以為只要重寫這個方法的時候返回 YES 就行了咳促,NSOperation 就會自動多線程執(zhí)行了,但是測試發(fā)現(xiàn)卻不是這樣的勘伺,多線程還得自己去創(chuàng)建再使用跪腹。

再有就是自定義多線程的 NSOperation 時,還必須自己管理其中表示狀態(tài)的成員娇昙,而且需要實現(xiàn) KVO 機制尺迂,使得這個過程復(fù)雜化了。

其實在大多數(shù)時候我們并不會直接去使用自定義的 NSOperation 冒掌,如果操作不復(fù)雜噪裕,可以直接使用 NSInvocationOperation 和 NSBlockOperation 這兩個子類,那就直接用了股毫,如果復(fù)雜一些膳音,必須自定義又需要多線程,通常都會用 NSOperationQueue 來包裝铃诬,使用起來更加簡潔祭陷,下一篇會詳細介紹 NSOperationQueue 的使用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末趣席,一起剝皮案震驚了整個濱河市兵志,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宣肚,老刑警劉巖想罕,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異霉涨,居然都是意外死亡按价,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門笙瑟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來楼镐,“玉大人,你說我怎么就攤上這事往枷】虿” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵错洁,是天一觀的道長茅信。 經(jīng)常有香客問我,道長墓臭,這世上最難降的妖魔是什么蘸鲸? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮窿锉,結(jié)果婚禮上酌摇,老公的妹妹穿的比我還像新娘膝舅。我一直安慰自己,他們只是感情好窑多,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布仍稀。 她就那樣靜靜地躺著,像睡著了一般埂息。 火紅的嫁衣襯著肌膚如雪技潘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天千康,我揣著相機與錄音享幽,去河邊找鬼。 笑死拾弃,一個胖子當著我的面吹牛值桩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播豪椿,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼奔坟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了搭盾?” 一聲冷哼從身側(cè)響起咳秉,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鸯隅,沒想到半個月后澜建,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡滋迈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年霎奢,在試婚紗的時候發(fā)現(xiàn)自己被綠了户誓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饼灿。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖帝美,靈堂內(nèi)的尸體忽然破棺而出碍彭,到底是詐尸還是另有隱情,我是刑警寧澤悼潭,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布庇忌,位于F島的核電站,受9級特大地震影響舰褪,放射性物質(zhì)發(fā)生泄漏皆疹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一占拍、第九天 我趴在偏房一處隱蔽的房頂上張望略就。 院中可真熱鬧捎迫,春花似錦、人聲如沸表牢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽崔兴。三九已至彰导,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間敲茄,已是汗流浹背位谋。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留折汞,地道東北人倔幼。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像爽待,于是被迫代替她去往敵國和親损同。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

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