iOS 多线程系列 -- NSOperation

iOS 多线程系列 -- 基础概述
iOS 多线程系列 -- pthread
iOS 多线程系列 -- NSThread
iOS 多线程系列 -- GCD全解一(基础)
iOS 多线程系列 -- GCD全解二(常用方法)
iOS 多线程系列 -- GCD全解三(进阶)
iOS 多线程系列 -- NSOperation
测试Demo的GitHub地址

上一篇我们介绍过GCD,这一篇我们学习一下iOS另一种多线程技术--NSOperation技术.

NSOperation也是苹果提供给我们的一套多线程解决方案。实际上它也是基于GCD的一层OC封装,在拥有GCD很多优点的同时,比GCD拥有更强的可控性和代码可读性.和GCD
一样,NSOperation一样不需要我们管理线程的创建销毁等事情,系统会自动根据CPU负载情况,创建合适的线程去执行任务;但同时, NSOperation提供了更多GCD不提供的控制线程属性,例如我们可以控制最大并发数量、取消队列中还没有执行的任务(注意是还没有执行的任务)等

NSOperation

1.基础知识

1.1 认识NSOperation类

  • NSOperation除了代表iOS多线程的一种技术,还是这个技术中的一个重要类。
  • NSOperation本身是抽象基类,不能直接使用,但是他封装了需要执行的操作和执行操作所需的数据方法等。在NSOperation基础上,系统提供了两个子类NSBlockOperation和NSInvocationOperation供我们具体使用,当然,我们也可以自己封装自定义的NSOperation。

1.2 NSOperation的常用方法、属性介绍

  • setCompletionBlock 监听NSOperation的完成,调用NSOperation的setCompletionBlock方法来设置操作完成后想做的事情
    • completionBlock 会在后台线程执行,不会再主线程执行,所以如果需要更新UI,需要注意线程
    • completionBlock 不能接受参数,没有返回值,因此在使用的时候,会有很多限制
  • cancel NSOperation实例调用cancel方法,取消operation执行,但并不是绝对取消任务,如果operation加入到Queue中或者operation已经start了,则无法取消成功。同时还会设置isCancelled属性为YES,这个属性在自定义NSOperation的时候很重要
1)- (id)init;//NSOperation实例初始化方法,用于创建一个NSOperation实例。
2)- (void)start;//该方法是operation的起点,如果需要创建并发operation必须,覆盖start方法。同时调用start方法,会默认做各种验证。
3)- (BOOL)isCancelled;//该property,是用于标示某个operation是否cancel。对于多线程来说需要不断检测这个值。
4)- (void)cancel;//调用cancel方法会取消一个operation,但是如果operation加入到Queue中或者operation已经start了,则无法取消成功,调用cancel也不一定立即执行cancel操作,需要等待时间周期。
5)- (BOOL)isExecuting;//判定operation是否正在执行。
6)- (BOOL)isFinished;//判定operation是否完成,cancel掉某个operation,也会将该operation的该字段设置成为YES。
7)- (BOOL)isConcurrent;//判定该线程是否是并发线程,即调用该operation的start方法的线程是否与operation所在线程相同。
8)- (BOOL)isReady;//在start方法开始之前,需要确定operation是否ready,默认为YES,如果该operation没有ready,则不会start。
9)- (void)addDependency:(NSOperation *)op;//该方法用于配置operation之间的依赖关系,涉及执行顺序稍后会介绍。如果不是手动调用start去执行operation,一定要在将其加入到Queue之前做好依赖,因为一旦加入到Queue中,其也许很快会执行,依赖关系将不会起作用。
10)- (void)removeDependency:(NSOperation *)op;//相对应add,其为移除两个operation之间的依赖关系。
11)- (NSArray *)dependencies;//获取operation的依赖关系的数组。
12)- (NSOperationQueuePriority)queuePriority;//如果将operation加入到Queue中,设定其在Queue中的优先级,优先级高的先执行的概率大,但并不代表一定会先执行,执行顺序稍后介绍。
13)- (void)setQueuePriority:(NSOperationQueuePriority)p;//setter方法。
14)- (void (^)(void))completionBlock NS_AVAILABLE(10_6, 4_0);//在operation完成之后会调用completionBlock,你可以自定义执行行为。
15)- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);//设定是否等待operation执行结束,如果为YES,该线程会一直等待operation执行结束,才会执行接下来的代码。
16)- (double)threadPriority NS_AVAILABLE(10_6, 4_0);//设定operation的线程优先级,涉及执行顺序稍后介绍。线程优先级默认为0.5,最低为0,最大为1.即使设定了线程优先级,也只能保证其在main方法范围内有效,operation的其他代码仍然执行在默认线程。

2.两个系统提供的NSOperation子类

2.1 NSBlockOperation

  • 能够并发地执行一个或多个block对象,所有相关的block都执行完之后,操作才算完成

  • 创建NSBlockOperation对象

    + (id)blockOperationWithBlock:(void (^)(void))block;
    
  • 通过addExecutionBlock:方法添加更多的操作

    - (void)addExecutionBlock:(void (^)(void))block;
    
  • 注意:

    • 调用start方法,默认会在调用start的线程中执行任务,但是只要NSBlockOperation封装的操作数大于1,调用start方法执行任务,就会异步执行操作;可以在下面示例代码中验证这一点
  • 示例代码:

NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ // 在主线程
        NSLog(@"下载1------%@", [NSThread currentThread]);
    }];
    [op addExecutionBlock:^{ // 添加额外的任务(在子线程执行)
        NSLog(@"下载2------%@", [NSThread currentThread]);
    }];
    [op start];//开始执行任务
  • 示例代码打印结果:

2017-07-04 20:01:09.586 Test - 多线程[20794:4540810] 下载1------<NSThread: 0x60000007fb80>{number = 1, name = main}
2017-07-04 20:01:09.586 Test - 多线程[20794:4540931] 下载2------<NSThread: 0x608000269e00>{number = 3, name = (null)}

2.2 NSInvocationOperation

  • 基于一个对象和selector来创建操作。如果你已经有现有的方法来执行需要的任务,就可以使用这个类
  • 创建NSInvocationOperation对象

    NSInvocationOperation *invo=[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

  • 线程启动的两种方法(start和NSOperationQueue):
    • 调用start方法开始执行操作

      - (void)start;
      一旦执行操作,就会调用target的sel方法
      
      //可以调用这个方法启动执行任务,但是这样的方法不会启动新的线程,只会在当前线程执行
      [invo start];
      
  • 注意
    • 默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作

3.进阶 -- 自定义operation

如果NSInvocationOperation和NSBlockOperation对象不能满足需求, 你可以直接继承NSOperation, 并添加任何你想要的行为。

NOTE 需要自己创建自动释放池(因为如果是异步操作,子线程运行循环默认不工作,不会主动创建自动释放池),但是在查看最新的苹果官方文档时发现,里面指出 “This method will automatically execute within an autorelease pool provided by NSOperation, so you do not need to create your own autorelease pool block in your implementation” 也就是说不需要我们创建autorelease pool。

  • 优点:提供了更加灵活可控的处理方式,如:
    • 我们在自定义任务的时候,可以非常方便的在需要的地方监听NSOperation的isCanceled属性并作出我们需要的处理。

3.1 创建非并发的NSOperation

  • 只需要重载-(void)main这个方法,在这个方法里面执行主任务
  • 在重载main方法里面,添加自动释放池,因为子线程运行循环默认不工作,不会主动创建自动释放池
  • 定期监听operation的isCanceled属性并正确的响应cancel操作即可.下面列举一般在什么地方添加监听:
    • 在开始执行前判断isCancelled,如果为YES直接return中断任务
    • 执行任何实际的工作之前
    • 在循环的每次迭代过程中,如果每个迭代相对较长可能需要调用多次
    • 代码中相对比较容易中止操作的任何地方
  • 示例代码如下:
//捕获异常
@try {
    //在这里我们要创建自己的释放池,因为子线程运行循环默认不工作,不会主动创建自动释放池
    @autoreleasepool {
        if (self.isCancelled)
        {
            NSLog(@"0000self.isCancelled = yes , return");
            return;
        }
        for (NSInteger i = 0; i<100; i++) {
            [NSThread sleepForTimeInterval:0.1];
            NSString *string = [NSString stringWithFormat:@"XLOperation1 %zd",i];
            NSLog(@"XLOperation1 -%zd -- isCanceled = %zd-- executing=%zd -- isFinished = %zd -- %@", i, self.isCancelled,self.executing,self.isFinished,[NSThread currentThread]);
        }
    }
}
@catch (NSException *exception) {
    
}

3.2 并发的NSOperation

  • 如果需要创建一个并发的Operation,并手动执行,则需要重写main,start,isCancelled,isReady,isConcurrent,isFinished,isExcluting等方法,并维护每个property的KVO,同时确保每个property的原子性,详细使用示例可以去看SDWebImage中的SDWebImageDownloaderOperation使用
  • 使用步骤:
    • start: 所有并行的 Operations 都必须重写这个方法,然后在你想要执行的线程中手动调用这个方法。注意:任何时候都不能调用父类的start方法。
    • main: 在start方法中调用,但是注意要定义独立的自动释放池与别的线程区分开。
    • isExecuting: 是否执行中,需要实现KVO通知机制。
    • isFinished: 是否已完成,需要实现KVO通知机制。
    • isConcurrent: 该方法现在已经由isAsynchronous方法代替,并且 NSOperationQueue 也已经忽略这个方法的值。
    • isAsynchronous: 该方法默认返回 NO ,表示非并发执行。并发执行需要自定义并且返回 YES。后面会根据这个返回值来决定是否并发。

参考资料:

NSOperation那些事

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,565评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,021评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,003评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,015评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,020评论 5 370
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,856评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,178评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,824评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,264评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,788评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,913评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,535评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,130评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,102评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,334评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,298评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,622评论 2 343

推荐阅读更多精彩内容