NSOperation, NSOperationQueue 原理探析

通过GNUstep的Foundation来尝试探索下NSOperation,NSOperationQueue

 

示例程序

 

写一个简单的程序

 

- (void)viewDidLoad {

    [super viewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.

    

    [self configurationQueue];

    LDNSOperation *operation = [[LDNSOperation alloc] init];

    [self.operationQueue addOperation:operation];

    [NSThread sleepForTimeInterval:3];

    [operation cancel];

    

}

 

-(void)configurationQueue{

    self.operationQueue = [[NSOperationQueue alloc] init];

    self.operationQueue.maxConcurrentOperationCount = 4;

}

 

LDNSOperation为NSOperation的子类,重写strat方法

 

-(void)start{

    while (true) {

        if(self.cancelled){

            NSLog(@"已经取消");

            return;

        }

        NSLog(@"start");

        [NSThread sleepForTimeInterval:1];

    }

}

 

实现的效果很简单,打印三个strat,然后结束operation。

 

初探

 

根据阅读GNU的源码,也只能是猜想,但是尝试了很多方法,没有找到可以验证的方案,但是实现原理上还是有很多相似处的。

 

NSOperation有三种状态,isReady, isExecuting, isFinished.

 

很多其他的参数也会随着NSOperationQueue的addOperation操作而变化着。

 

例如:

 

[self.operationQueue addOperation:operation];

 

添加一个未完成的NSOperation,其实就是将NSOperation添加到一个动态数组当中

 

- (void) addOperation: (NSOperation *)op

{

  if (op == nil || NO == [op isKindOfClass: [NSOperation class]])

    {

      [NSException raise: NSInvalidArgumentException

  format: @"[%@-%@] object is not an NSOperation",

NSStringFromClass([self class]), NSStringFromSelector(_cmd)];

    }

  [internal->lock lock];

  if (NSNotFound == [internal->operations indexOfObjectIdenticalTo: op]

    && NO == [op isFinished])

    {

      [op addObserver: self

   forKeyPath: @"isReady"

      options: NSKeyValueObservingOptionNew

      context: NULL];

      [self willChangeValueForKey: @"operations"];

      [self willChangeValueForKey: @"operationCount"];

      [internal->operations addObject: op];

      [self didChangeValueForKey: @"operationCount"];

      [self didChangeValueForKey: @"operations"];

      if (YES == [op isReady])

      {

          [self observeValueForKeyPath: @"isReady"

      ofObject: op

change: nil

       context: nil];

      }

    }

  [internal->lock unlock];

}

 

internal就是一个内部类,指代的就是NSOperationQueue,这里也是一个KVO的手动通知,进行operations,与operationCount的改变通知。

 

这里lock是NSRecursiveLock(递归锁),原因我猜测是因为递归锁的特性是可以被同一线程多次请求,而不会引起死锁。同一线程的多次addOperation操做情况还是很多的。

 

每一次属性的变化,都伴随着其他属性的改变

 

- (void) observeValueForKeyPath: (NSString *)keyPath

       ofObject: (id)object

                         change: (NSDictionary *)change

                        context: (void *)context

{

  [internal->lock lock];

  if (YES == [object isFinished])

    {

      internal->executing--;

      [object removeObserver: self

  forKeyPath: @"isFinished"];

      [internal->lock unlock];

      [self willChangeValueForKey: @"operations"];

      [self willChangeValueForKey: @"operationCount"];

      [internal->lock lock];

      [internal->operations removeObjectIdenticalTo: object];

      [internal->lock unlock];

      [self didChangeValueForKey: @"operationCount"];

      [self didChangeValueForKey: @"operations"];

    }

  else if (YES == [object isReady])

    {

      [object removeObserver: self

  forKeyPath: @"isReady"];

      [internal->waiting addObject: object];

      [internal->lock unlock];

    }

  [self _execute];

}

 

其实在maxConcurrentOperationCount和suspended的setter方法里面都会调用_execute方法,以及在其他属性如operationCount、operations、值发生变化的时候都会调用它。

 

那么_execute究竟是什么?

 

整个源码都拿上来

 

- (void) _execute

{

  NSInteger max;

 

  [internal->lock lock];

 

  max = [self maxConcurrentOperationCount];

  if (NSOperationQueueDefaultMaxConcurrentOperationCount == max)

    {

      max = maxConcurrent;

    }

 

  while (NO == [self isSuspended]

    && max > internal->executing

    && [internal->waiting count] > 0)

    {

      NSOperation *op;

      op = [internal->waiting objectAtIndex: 0];

      [internal->waiting removeObjectAtIndex: 0];

      [op addObserver: self

   forKeyPath: @"isFinished"

      options: NSKeyValueObservingOptionNew

      context: NULL];

      internal->executing++;

      if (YES == [op isConcurrent])

{

          [op start];

}

      else

{

  NSUInteger pending;

 

  [internal->cond lock];

  pending = [internal->starting count];

  [internal->starting addObject: op];

  if (0 == internal->threadCount

    || (pending > 0 && internal->threadCount

    {

      internal->threadCount++;

      [NSThread detachNewThreadSelector: @selector(_thread)

       toTarget: self

     withObject: nil];

    }

  /* Tell the thread pool that there is an operation to start.

   */

  [internal->cond unlockWithCondition: 1];

}

    }

  [internal->lock unlock];

}

 

从源码中可以看到,根据isConcurrent分为直接执行,和非直接执行,isConcurrent为YES的话可以直接执行start操作,但是如果isConcurrent为NO,那么这里使用detachNewThreadSelector来创建新的线程去执行start。

 

总结下来:

 

  • 所有的线程都很忙,并且没有达到threadCount的最大值的时候会创建新的线程,这代表queue并不是一个线程,也有可能有几个

  • _execute就是一个执行队列,依次将等待队列里面的所有operation进行start。

 

其实对于start函数来说的话,一个NSOperation并没有新创建一条线程,依然操作在[NSThread currentThread]中,感兴趣可以去做一下测试。从源码中也是可以看出来的,

 

- (void) start

{

  NSAutoreleasePool *pool = [NSAutoreleasePool new];

  double prio = [NSThread  threadPriority];

 

  [internal->lock lock];

  NS_DURING

    {

      if (YES == [self isConcurrent])

{

  [NSException raise: NSInvalidArgumentException

      format: @"[%@-%@] called on concurrent operation",

    NSStringFromClass([self class]), NSStringFromSelector(_cmd)];

}

      if (YES == [self isExecuting])

{

  [NSException raise: NSInvalidArgumentException

      format: @"[%@-%@] called on executing operation",

    NSStringFromClass([self class]), NSStringFromSelector(_cmd)];

}

      if (YES == [self isFinished])

{

  [NSException raise: NSInvalidArgumentException

      format: @"[%@-%@] called on finished operation",

    NSStringFromClass([self class]), NSStringFromSelector(_cmd)];

}

      if (NO == [self isReady])

{

  [NSException raise: NSInvalidArgumentException

      format: @"[%@-%@] called on operation which is not ready",

    NSStringFromClass([self class]), NSStringFromSelector(_cmd)];

}

      if (NO == internal->executing)

{

  [self willChangeValueForKey: @"isExecuting"];

  internal->executing = YES;

  [self didChangeValueForKey: @"isExecuting"];

}

    }

  NS_HANDLER

    {

      [internal->lock unlock];

      [localException raise];

    }

  NS_ENDHANDLER

  [internal->lock unlock];

 

  NS_DURING

    {

      if (NO == [self isCancelled])

{

  [NSThread setThreadPriority: internal->threadPriority];

  [self main];

}

    }

  NS_HANDLER

    {

      [NSThread setThreadPriority:  prio];

      [localException raise];

    }

  NS_ENDHANDLER;

 

  [self _finish];

  [pool release];

}

 

总结

 

整个过程伴随着很多属性的变化,同步这些属性,KVO在其中起着举足轻重的作用,通过源码也可以发现,NSOperationQueue对NSOperation的处理分为并发和非并发的情况。如果不想采用非并发的形式,我们可以直接自定义子类化,在NSOperationQueue中添加,并且管理就可以了,功能类似线程池的用法。

 

但是如果想要自定义的NSOperation是并发的仅仅是重写isExecuting、isFinished、isConcurrent、isAsynchronous 这四个方法,isAsynchronous反回YES就可以了吗?

 

从源码中我们可以看到,NSOperation的start依然使用的[NSThread currentThread]。所以依然需要自己创建,例如:

 

[NSThread detachNewThreadSelector:@selector(start) toTarget:self withObject:nil];

 

现在来思考下,也就明白了为什么NSOperationQueue要有两种处理方式了,如果NSOperation支持并发,然后NSOperationQueue在为其分配线程,那就是线程里面又跑了一条线程,这样就很尴尬了,通过isConcurrent可以避免这种现象。

 

通常在大多数时候我们并不会直接去使用自定义的 NSOperation ,如果操作不复杂,可以直接使用 NSInvocationOperation 和 NSBlockOperation 这两个子类。

 

如果真的需要使用多线程,通常都会用 NSOperationQueue来处理就可以了。

 

这里也是仅仅简单的探索了一下,上面的源码是封装的NSThread,但是Apple的实现可能封装的不是NSThread,因为断点后并没有看到跟NSThread相关的东西,还是有很多细节需要去推敲。

 

 

转载于:https://www.cnblogs.com/fengmin/p/6108165.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/402598.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MyBatis总结六:resultMap详解(包含多表查询)

From: https://www.cnblogs.com/Alex-zqzy/p/9296039.html 简介:   MyBatis的每一个查询映射的返回类型都是ResultMap,只是当我们提供的返回类型属性是resultType的时候,MyBatis对自动的给我们把对应的值赋给resultType所指定对象的属性&a…

object c 快速构建对象

__block NSNumber *capacity (0);

mysql语句添加索引

1.PRIMARY KEY(主键索引) mysql>ALTER TABLE table_name ADD PRIMARY KEY ( column ) 2.UNIQUE(唯一索引) mysql>ALTER TABLE table_name ADD UNIQUE (column ) 3.INDEX(普通索引) mysql>ALTER TABLE table…

基于beego一键创建RESTFul应用

2019独角兽企业重金招聘Python工程师标准>>> API应用开发入门 Go是非常适合用来开发API应用的,而且我认为也是Go相对于其他动态语言的最大优势应用。beego在开发API应用方面提供了非常强大和快速的工具,方便用户快速的建立API应用原型&#…

MyBatis中in的使用

From: https://www.cnblogs.com/w-bb/articles/6378031.html foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。 foreach元素的属性主要有 item,index,collection,open,separator,close…

JS 限制input框的输入字数,并提示可输入字数

<!DOCTYPE html> <html> <head lang"en"><meta charset"UTF-8"><title>限制文件字数字</title> </head> <body> <span class"span">备注信息</br><span id"stay" sty…

采购订单单价金额屏蔽

应用 Oracle Purchasing 层 Level Function 函数名 Funcgtion Name CUXPOXPOVPO 表单名 Form Name POXPOVPO 说明 Description 采购订单单价金额屏蔽 条件 Condition 触发器事件 Tirgger Event WHEN-NEW-FORM-INSTANCE 触发器对象 Tirgger Object / 条件 Cond…

MyBatis总结五:#{}和${}的用法和区别

From: https://www.cnblogs.com/blazeZzz/p/9295634.html #{}的用法&#xff1a; 我们发现&#xff0c;在Mapper.xml映射文件中&#xff0c;经常使用#{属性名} 来作为SQL语句的占位符&#xff0c;来映射Sql需要的实际参数 如果只有一个参数 <select id"getUserById&…

面试题二

1.数据库连接查询。 连接查询用于多表查询 内连接&#xff1a;用inner join 关键字&#xff08;以学生表为例&#xff1a;select stu.stu_name&#xff0c;sc.stu_score from student stu inner join score sc on stu.stu_idsc.stu_id&#xff09; 左外连接:left join on 特征&…

iOS多视图代码操作

2019独角兽企业重金招聘Python工程师标准>>> 摘抄 http://supershll.blog.163.com/blog/static/370704362012112021115/ 2.1.1 层次结构 基于树的层次结构对iPhone屏幕上的可见内容进行排序。从主窗口开始&#xff0c;视图以特殊的分层方式布置。所有视图都可以有…

MongoDB服务重启及后台运行解决方法

1 在MongoDB 安装目录下 新建一个test文件夹 mkdir /test 2 切换到MongoDB的安装目录(可通过 find -name mongod命令查找安装目录)下 执行&#xff1a; bin/mongod --dbpath ./test/ 重启MongoDB服务 3 在MongoDB安装目录下继续执行&#xff1a; bin/mongod -dbpath ./test/ -…

clover 隐藏没用的启动项

From: https://blog.csdn.net/A807296772/article/details/88636458 参考链接: http://bbs.pcbeta.com/viewthread-1610560-1-3.html 总体思路: 引导项全部禁掉,然后添加自定义项. 具体步骤如下: 开机,出现Clover引导选项、选好mac的启动分区后,先按一下F2,再回车,直到进入…

(转)Tiny210v2( S5PV210 ) 平台下 FIMD 对应 的 framebuffer 驱动中,关于 video buffer 的理解...

原文:http://www.arm9home.net/read.php?tid-25938.html 管理提醒&#xff1a; 本帖被 xoom 执行加亮操作(2012-12-13)如之前所说&#xff0c;一直想知道显示数据都在哪个地方&#xff0c;通常的数据&#xff0c;比如 framebuffer 中的显示数据&#xff0c;和OpenGL 处理的数据…

Swift--控制流与oc不同的地方

1.For-in循环中... for index in 1...5 { print("\(index) times 5 is \(index * 5)") } for _ in 1...5 { 可以用下划线忽略当前值 } 2.字典通过元祖返回 3.do while循环变成repeat repeat { statements } while condition 4.switch不需要break let someCharacter: …

MySQL——字符串拆分(含分隔符的字符串截取)

From: https://blog.csdn.net/pjymyself/article/details/81668157 有分隔符的字符串拆分 题目要求 数据库中 num字段值为&#xff1a; 数据库中 num字段值 实现的效果&#xff1a;需要将一行数据变成多行 实现效果 实现的SQL SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(7…

python3 TypeError: 'str' does not support the buffer interface in python

http://stackoverflow.com/questions/38714936/typeerror-str-does-not-support-the-buffer-interface-in-python 下面这样会报错&#xff1a; bb{"m":1} import urllib.parse urllib.parse.unquote(b) 修正方案&#xff1a; bb.decode() urllib.parse.unquote(b) 即…

C链表反转(时间复杂度O(n))

面试的时候经常会出现的问题,现在都做一遍,回忆一下,练练手. 这个题目需要注意两点: 1.head->next 要先设置为NULL ,否则反转后,它还是指向之前的next节点 2.需要有一个tmp指针,临时保存p->next的地址,这个在改变一个节点的next地址时,经常会用到 示意图 代码实现 #inclu…

mysql中find_in_set()函数的使用及in()用法详解

From: http://www.manongjc.com/article/2710.html MySQL手册中find_in_set函数的语法解释&#xff1a; FIND_IN_SET(str,strlist) str 要查询的字符串 strlist 字段名 参数以”,”分隔 如 (1,2,6,8,10,22) 查询字段(strlist)中包含(str)的结果&#xff0c;返回结果为null…

浅谈SQLiteOpenHelper之onUpgrade例子

当你看到这个博文&#xff0c;首先你要了解onCreate这个创建方法&#xff0c;再来继续下文!&#xff08;可以参考我的上一个博文http://www.cnblogs.com/896240130Master/p/6119616.html&#xff09; 这个onUpgrade类要在onCreate类的基础上建立&#xff01;我们知道onUpgrade是…

jboss-AS目录结构了解(资料摘取)

Directory Description bin Contains startup, shutdown and other system-specific scripts. Basically all the entry point JARs and start scripts included with the JBoss distribution are located in the bin directory. 包含了服务器启动&#xff0c;关闭和系统相关…