iOS多線程之NSOperation

緊接著上一篇GCD 之后 今天給大家 分享和總結的是NSOperation

廢話不多說:來看看NSOperation 是什么鬼日杈?
官網(wǎng)的解釋是:


image.png

翻譯:一個抽象類蘸泻,表示與單個任務關聯(lián)的代碼和數(shù)據(jù)

如果你有閱讀過 SDWebImage和AFNetworking的源碼的話 那么你就會注意到里面大量的用到了大量的 NSOperation 為什么呢碉纳?
原因:
NSOperation裙戏、NSOperationQueue 是基于 GCD 更高一層的封裝厉萝,完全面向對象屁商。但是比 GCD 更簡單易用、代碼可讀性也更高七问。

  • 可添加完成的代碼塊蜓耻,在操作完成后執(zhí)行。
  • 添加操作之間的依賴關系械巡,方便的控制執(zhí)行順序刹淌。
  • 設定操作執(zhí)行的優(yōu)先級。
  • 可以很方便的取消一個操作的執(zhí)行。
  • 使用 KVO 觀察對操作執(zhí)行狀態(tài)的更改:isExecuteingisFinished浸遗、isCancelled禀崖。

NSOperation的使用

NS_CLASS_AVAILABLE(10_6, 4_0)
@interface NSBlockOperation : NSOperation {
@private
    id _private2;
    void *_reserved2;
}

+ (instancetype)blockOperationWithBlock:(void (^)(void))block;

- (void)addExecutionBlock:(void (^)(void))block;
@property (readonly, copy) NSArray<void (^)(void)> *executionBlocks;

@end
NS_CLASS_AVAILABLE(10_5, 2_0)
NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available")
@interface NSInvocationOperation : NSOperation {
@private
    id _inv;
    id _exception;
    void *_reserved2;
}

- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
- (instancetype)initWithInvocation:(NSInvocation *)inv NS_DESIGNATED_INITIALIZER;

@property (readonly, retain) NSInvocation *invocation;

@property (nullable, readonly, retain) id result;

@end

NSOperation不可以直接創(chuàng)建,但是可以使用它的子類NSBlockOperationNSInvocationOperation,前者是使用Block的方式,使用起來比較方便。

NSBlockOperation

/**
 * 使用子類 NSBlockOperation
 */
- (void)useBlockOperation {
    
    // 1.創(chuàng)建 NSBlockOperation 對象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];
    
    // 2.調用 start 方法開始執(zhí)行操作
    [op start];
}

image.png

可以看出NSBlockOperation 默認在主線程中執(zhí)行

image.png

看下addExecutionBlock

/**
 * 使用子類 NSBlockOperation
 * 調用方法 AddExecutionBlock:
 */
- (void)useBlockOperationAddExecutionBlock {
    
    // 1.創(chuàng)建 NSBlockOperation 對象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];
    
    // 2.添加額外的操作
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"4---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"5---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"6---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"7---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"8---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];
    
    // 3.調用 start 方法開始執(zhí)行操作
    [op start];
}
image.png
 // 1.創(chuàng)建 NSBlockOperation 對象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];

可以看出上面是在主線程中執(zhí)行

使用自定義繼承自 NSOperation 的子類

@implementation JFOperation
/**
 重寫mian方法
 */
- (void)main {
    if (!self.isCancelled) {
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@", [NSThread currentThread]);
        }
    }
}
@end

/**
 * 使用自定義繼承自 NSOperation 的子類
 */
- (void)useCustomOperation {
    // 1.創(chuàng)建 JFOperation 對象
    JFOperation *op = [[JFOperation alloc] init];
    // 2.調用 start 方法開始執(zhí)行操作
    [op start];
}

NSOperationQueue的使用

NS_CLASS_AVAILABLE(10_5, 2_0)
@interface NSOperationQueue : NSObject {
@private
    id _private;
    void *_reserved;
}

- (void)addOperation:(NSOperation *)op;
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));

- (void)addOperationWithBlock:(void (^)(void))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));

@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
@property (readonly) NSUInteger operationCount API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));

@property NSInteger maxConcurrentOperationCount;

@property (getter=isSuspended) BOOL suspended;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));

@property NSQualityOfService qualityOfService API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));

@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));

- (void)cancelAllOperations;

- (void)waitUntilAllOperationsAreFinished;

@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
@property (class, readonly, strong) NSOperationQueue *mainQueue API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));

@end

NS_ASSUME_NONNULL_END

如上系統(tǒng)api所示:NSOperationQueue有一個
@property NSInteger maxConcurrentOperationCount;屬性
顧名思義 最大的并發(fā)操作數(shù)

最大并發(fā)操作數(shù):maxConcurrentOperationCount

maxConcurrentOperationCount默認情況下為-1,表示不進行限制雇逞,可進行并發(fā)執(zhí)行。
maxConcurrentOperationCount為1時茁裙,隊列為串行隊列塘砸。只能串行執(zhí)行。
maxConcurrentOperationCount大于1時晤锥,隊列為并發(fā)隊列谣蠢。操作并發(fā)執(zhí)行,當然這個值不應超過系統(tǒng)限制,即使自己設置一個很大的值眉踱,系統(tǒng)也會自動調整為 min{自己設定的值挤忙,系統(tǒng)設定的默認最大值}。

maxConcurrentOperationCount = 1

image.png

如上圖maxConcurrentOperationCount= 1 是串行

maxConcurrentOperationCount = 2

image.png

如上圖maxConcurrentOperationCount= 2 是并發(fā)

maxConcurrentOperationCount = 9

image.png

maxConcurrentOperationCount 也是并發(fā)
開啟線程數(shù)量是由系統(tǒng)決定的谈喳,不需要我們來管理

NSOperation 操作依賴

- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;
@property (readonly, copy) NSArray<NSOperation *> *dependencies; 
/**
 * 操作依賴
 * 使用方法:addDependency:
 */
- (void)addDependency {

    // 1.創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.創(chuàng)建操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];

    // 3.添加依賴
    [op2 addDependency:op1]; // 讓op2 依賴于 op1册烈,則先執(zhí)行op1,在執(zhí)行op2

    // 4.添加操作到隊列中
    [queue addOperation:op1];
    [queue addOperation:op2];
}
image.png

如打有銮荨:op2在op1后執(zhí)行

NSOperation赏僧、NSOperationQueue 線程間的通信

/**
 * 線程間通信
 */
- (void)communication {

    // 1.創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    // 2.添加操作
    [queue addOperationWithBlock:^{
        // 異步進行耗時操作
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印當前線程
        }

        // 回到主線程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            // 進行一些 UI 刷新等操作
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
                NSLog(@"2---%@", [NSThread currentThread]); // 打印當前線程
            }
        }];
    }];
}
image.png

打印如上這個和NSThred和GCD一樣。也是先在其他線程中執(zhí)行操作扭倾,等操作執(zhí)行完了之后再回到主線程執(zhí)行主線程的相應操作

image.png
image.png

打印如上票數(shù)是錯亂的

/**
 * 線程安全:使用 NSLock
 * 初始化彩票數(shù)量淀零、賣票窗口(線程安全)、并開始賣票
 */
- (void)initTicketStatusSave {
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
    
    self.ticketSurplusCount = 50;
    
    self.lock = [[NSLock alloc] init];  // 初始化 NSLock 對象
    
    
    // 1.創(chuàng)建 queue1,queue1 代表足球彩票售賣窗口
    NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
    queue1.maxConcurrentOperationCount = 1;
    
    // 2.創(chuàng)建 queue2,queue2 代表籃球票售賣窗口
    NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
    queue2.maxConcurrentOperationCount = 1;
    
    // 3.創(chuàng)建賣票操作 op1
    __weak typeof(self) weakSelf = self;
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        [weakSelf saleTicketSafe];
    }];
    
    // 4.創(chuàng)建賣票操作 op2
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [weakSelf saleTicketSafe];
    }];
    
    // 5.添加操作膛壹,開始賣票
    [queue1 addOperation:op1];
    [queue2 addOperation:op2];
}


/**
 * 售賣彩票票(線程安全)
 */
- (void)saleTicketSafe {
    while (1) {
        
        // 加鎖
        [self.lock lock];
        
        if (self.ticketSurplusCount > 0) {
            //如果還有票驾中,繼續(xù)售賣
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票數(shù):%ld 窗口:%@", (long)self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        }
        
        // 解鎖
        [self.lock unlock];
        
        if (self.ticketSurplusCount <= 0) {
            NSLog(@"所有彩票均已售完");
            break;
        }
}
}
image.png
image.png

打印如上 正是我們想要的結果

NSOperation,NSOperationQueue 的優(yōu)先級

NSOperation對象使用setQueuePriority:設置自身在NSOperationQueue對象中執(zhí)行的優(yōu)先級模聋。參數(shù)有:

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};
-(void)queuePriority{
    
    NSBlockOperation *blkop1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"執(zhí)行blkop1");
    }];
    
    NSBlockOperation *blkop2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"執(zhí)行blkop2");
    }];
    
    // 設置操作優(yōu)先級
    blkop1.queuePriority = NSOperationQueuePriorityLow;
    blkop2.queuePriority = NSOperationQueuePriorityVeryHigh;
    
    NSLog(@"blkop1 == %@",blkop1);
    NSLog(@"blkop2 == %@",blkop2);
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 操作添加到隊列
    [queue addOperation:blkop1];
    [queue addOperation:blkop2];
    
    NSLog(@"%@",[queue operations]);
    for (NSOperation *op in [queue operations]) {
        NSLog(@"op == %@",op);
    }
    
}

image.png

注意:

  • 優(yōu)先級只能應用于相同queue中的operations肩民。
  • 操作的優(yōu)先級高低不等于操作在隊列中排列的順序。換句話說链方,優(yōu)先級高的操作不代表一定排在隊列的前面持痰。后入隊的操作有可能因為優(yōu)先級高而先被執(zhí)行。PS:操作在隊列中的順序取決于隊列的addOperation:方法祟蚀。
  • 優(yōu)先級高只代表先被執(zhí)行工窍。不代表操作先被執(zhí)行完成。執(zhí)行完成的早晚還取決于操作耗時長短前酿。
  • 優(yōu)先級不能替代依賴移剪,優(yōu)先級也絕不等于依賴。優(yōu)先級只是對已經準備好的操作確定其執(zhí)行順序薪者。
  • 操作的執(zhí)行優(yōu)先滿足依賴關系,然后再滿足優(yōu)先級剿涮。即先根據(jù)依賴執(zhí)行操作言津,然后再從所有準備好的操作中取出優(yōu)先級最高的那一個執(zhí)行。

好了 NSOperation 的分享就到這里 下篇將會給大家?guī)?code>NSRunloop大家哪里不清楚的 取试,有啥想了解的 在評論區(qū)評論悬槽。

demo地址:https://github.com/tubie/JFMultiThreading

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瞬浓,隨后出現(xiàn)的幾起案子初婆,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件磅叛,死亡現(xiàn)場離奇詭異屑咳,居然都是意外死亡,警方通過查閱死者的電腦和手機弊琴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門兆龙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人敲董,你說我怎么就攤上這事紫皇。” “怎么了腋寨?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵聪铺,是天一觀的道長。 經常有香客問我萄窜,道長铃剔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任脂倦,我火速辦了婚禮番宁,結果婚禮上,老公的妹妹穿的比我還像新娘赖阻。我一直安慰自己蝶押,他們只是感情好,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布火欧。 她就那樣靜靜地躺著棋电,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苇侵。 梳的紋絲不亂的頭發(fā)上赶盔,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音榆浓,去河邊找鬼于未。 笑死,一個胖子當著我的面吹牛陡鹃,可吹牛的內容都是我干的烘浦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼萍鲸,長吁一口氣:“原來是場噩夢啊……” “哼闷叉!你這毒婦竟也來了?” 一聲冷哼從身側響起脊阴,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤握侧,失蹤者是張志新(化名)和其女友劉穎蚯瞧,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體品擎,經...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡埋合,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了孽查。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饥悴。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖盲再,靈堂內的尸體忽然破棺而出西设,到底是詐尸還是另有隱情,我是刑警寧澤答朋,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布贷揽,位于F島的核電站,受9級特大地震影響梦碗,放射性物質發(fā)生泄漏禽绪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一洪规、第九天 我趴在偏房一處隱蔽的房頂上張望印屁。 院中可真熱鬧,春花似錦斩例、人聲如沸雄人。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽础钠。三九已至,卻和暖如春叉谜,著一層夾襖步出監(jiān)牢的瞬間旗吁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工停局, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留很钓,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓董栽,卻偏偏與公主長得像码倦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子裆泳,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容

  • 垂直導航條 1.效果 2.html代碼:利用無序列表創(chuàng)建導航 3.css 先清除距離,去除列表樣式柠硕,設置顏色邊框樣...
    SpareNoEfforts閱讀 538評論 0 0
  • 最近忙來忙去,回到宿舍里的時間甚少闻葵,閉上眼睛往床上一倒民泵,也顧不上什么地上灰塵頭發(fā),桌上廢紙雜物了槽畔,果不其然栈妆,沒挨過...
    林青青l(xiāng)in閱讀 268評論 0 0
  • 1. 自從被別人以忙碌為借口打發(fā)掉之后鳞尔,凡事,我便不愿意再找忙碌的借口早直。 寫了這么久寥假,千字只需要20分鐘的我,再加...
    安之騰閱讀 754評論 50 29