魏名华

不要偷懒,做更好的自己

Nothing


No Welcome Message

Concurrent programming

并发编程是核心技术,要复习一下,objc中国有几篇文章:(比较旧,2013-2014年的) 并发编程

官方文档: Concurrency Programming Guide 中文版

iOS多线程——你要知道的NSOperation都在这里 iOS多线程之NSOperationQueue

GCD的基础使用可以看这个系列iOS多线程-GCD之dispatch_barrier_async

需要花时间好好研究下吧,甚至是背出来。

概念

其实同步异步并发并行可能没那么复杂,英文好像是Concurrent vs Non-concurrent vs synchronous vs asynchronous vs serial,其中,Non-concurrent 和 synchronous 好像是等价的??

串行 vs 并行,同步 vs 异步

GCD就有serial和concurrent两种队列,也有dispatch_async和dispatch_sync。

具体来说,对于串行队列,dispatch_async将block添加到队列,都是顺序执行。而并行队列,都是非顺序执行。

串行和并行是队列内的执行顺序问题。而dispatch_async和dispatch_sync,影响的是gcd后面的代码,比如:

dispatch_sync

//获取一个全局队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//要执行的block
void (^blk)() = ^{
    NSLog(@"Execute block");
};

//同步执行操作
dispatch_sync(globalQueue, blk);
NSLog(@"Done!");

运行结果:
2017-02-27 22:36:32.362 GCDLearn[1196:88920] Execute block
2017-02-27 22:36:32.363 GCDLearn[1196:88920] Done!

dispatch_async

//异步执行操作, globalQueue、blk同上
dispatch_async(globalQueue, blk);
NSLog(@"Done!");

运行结果:
2017-02-27 22:38:09.014 GCDLearn[1219:90936] Done!
2017-02-27 22:38:09.014 GCDLearn[1219:91096] Execute block

最终

同步执行: 阻塞当前线程,直到任务执行完成当前线程才可继续执行 异步执行: 不阻塞当前线程,可能使用其他线程来执行任务,不需要等待任务完成当前线程即可立即继续执行

type Serial串行队列 Concurrent并发队列
async异步执行 不阻塞当前线程,使用其他线程串行执行任务,只有一个线程用于执行任务 不阻塞当前线程,并发执行任务,使用多个线程执行任务
sync同步执行 阻塞当前线程,使用同一线程串行执行任务,只有一个线程用于执行任务 阻塞当前线程,可能使用同一线程串行执行任务

所以,针对异步执行/同步执行和串行队列/并发队列,只需要掌握其关键就可以了,同步/异步的区别在于是否阻塞当前线程,串行/并发队列的区别在于有多少个线程参与执行任务。

dispatch_queue_t serialQueue = dispatch_queue_create("com.gcd.syncAndAsyncMix.serialQueue", NULL);
dispatch_async(serialQueue, ^{
    NSLog(@"1");
});
dispatch_async(serialQueue, ^{
    NSLog(@"11");
});
dispatch_async(serialQueue, ^{
    NSLog(@"111");
});
dispatch_async(serialQueue, ^{
    NSLog(@"1111");
});
NSLog(@"2");
dispatch_sync(serialQueue, ^{
    NSLog(@"3");
});
NSLog(@"4");

运行结果:
2017-02-27 22:53:54.204 GCDLearn[1402:101052] 2
2017-02-27 22:53:54.204 GCDLearn[1402:101213] 1
2017-02-27 22:53:54.205 GCDLearn[1402:101213] 11
2017-02-27 22:53:54.206 GCDLearn[1402:101213] 111
2017-02-27 22:53:54.206 GCDLearn[1402:101213] 1111
2017-02-27 22:53:54.206 GCDLearn[1402:101052] 3
2017-02-27 22:53:54.207 GCDLearn[1402:101052] 4

主线程执行[tableView reloadData], 虽然主线程是串行队列,但是reloadData是个异步操作,reloadData后面的代码立刻执行,而不是等待tableView刷新后执行。

线程

线程(thread)是组成进程的子单元,操作系统的调度器可以对线程进行单独的调度。实际上,所有的并发编程 API 都是构建于线程之上的 —— 包括 GCD 和操作队列(operation queues)。

线程池的问题

这在大型工程中是一个常见问题。例如,在 8 核 CPU 中,你创建了 8 个线程来完全发挥 CPU 性能。然而在这些线程中你的代码所调用的框架代码也做了同样事情(因为它并不知道你已经创建的这些线程),这样会很快产生成成百上千的线程。代码的每个部分自身都没有问题,然而最后却还是导致了问题。使用线程并不是没有代价的,每个线程都会消耗一些内存和内核资源。

GCD

通过 GCD,开发者不用再直接跟线程打交道了,只需要向队列中添加代码块即可,GCD 在后端管理着一个线程池。

GCD 带来的另一个重要改变是,作为开发者可以将工作考虑为一个队列,而不是一堆线程,这种并行的抽象模型更容易掌握和使用。

GCD 公开有 5 个不同的队列:运行在主线程中的 main queue,3 个不同优先级的后台队列,以及一个优先级更低的后台队列(用于 I/O)。 另外,开发者可以创建自定义队列:串行或者并行队列。自定义队列非常强大,在自定义队列中被调度的所有 block 最终都将被放入到系统的全局队列中和线程池中。

GCD的队列总是先进先出,但是Operation Queues可以更自由的配置执行的顺序和依赖

创建队列

//定义
dispatch_queue_create(const char *label, dispatch_queue_attr_t attire);

//示例
dispatch_queue_t mySerialQueue = dispatch_queue_create("com.gcd.queueCreate.mySerialQueue", NULL);

第一个参数是队列名称,采用域名反转的命名规则,便于调试。

第二个参数用于区分创建串行队列还是并行队列。

串行队列: 传入 NULL 或 DISPATCH_QUEUE_SERIAL 并行队列: 传入 DISPATCH_QUEUE_CONCURRENT

dispatch_apply

for (i = 0; i < count; i++) {
   printf("%u\n",i);
}
NSLog(@"done!");


dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
dispatch_apply(count, queue, ^(size_t i) {
   printf("%u\n",i);
});
NSLog(@"done!");

跟for循环比没有性能优势,当传入的queue是并行队列时,重复执行的操作输出是无序的,而done输出始终在最后。把队列改为串行队列,输入出有序的。https://www.jianshu.com/p/4c376c0f1296

dispatch_async和dispatch_async_f

f是function的意思

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
    //Background Thread
    dispatch_async(dispatch_get_main_queue(), ^(void){
        //Run UI Updates can be done only on main thread
    });
});
void mainFunc(void) {} // your function
void callingFuncForAsyncTask(void*) { mainFunc(); } // new function which takes arguments for calling inside async_f

dispatch_async_f(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 0, &callingFuncForAsyncTask);

dispatch_barrier_async

dispatch_barrier_async一般叫做“栅栏函数”,它就好像栅栏一样可以将多个操作分隔开,在它前面追加的操作先执行,在它后面追加的操作后执行。 栅栏函数也可以执行队列上的操作(参数列表中有queue和block),也有对应的 dispatch_barrier_sync 函数。

作者:Autolying 链接:https://www.jianshu.com/p/d63c3100dd63 來源:简书 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

Suspending and Resuming Queues

您可以通过挂起来阻止队列临时执行 block objects。您使用该dispatch_suspend函数挂起调度队列并使用该函数恢复它dispatch_resume。

Dispatch Semaphores

// Create the semaphore, specifying the initial pool size
dispatch_semaphore_t fd_sema = dispatch_semaphore_create(getdtablesize() / 2);
 
// Wait for a free file descriptor
dispatch_semaphore_wait(fd_sema, DISPATCH_TIME_FOREVER);
fd = open("/etc/services", O_RDONLY);
 
// Release the file descriptor when done
close(fd);
dispatch_semaphore_signal(fd_sema);

创建信号量时,指定可用资源的数量。该值成为信号量的初始计数变量。每次等待信号量时,dispatch_semaphore_wait函数会将count变量递减1.如果结果值为负,则函数告诉内核阻塞线程。另一方面,该dispatch_semaphore_signal函数将count变量递增1以指示资源已被释放。如果有任务被阻止并等待资源,则其中一个随后被解除阻塞并被允许执行其工作。

dispatch_group

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
 
// Add a task to the group
dispatch_group_async(group, queue, ^{
   // Some asynchronous work
});
 
// Do some other work while the tasks execute.
 
// When you cannot make any more forward progress,
// wait on the group to block the current thread.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
 
// Release the group when it is no longer needed.
dispatch_release(group);

Dispatch Sources

计时器,文件操作,系统信号,进程退出,mach端口调度等等,另外,也可以自定义

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
                                 myDescriptor, 0, myQueue);
dispatch_source_set_event_handler(source, ^{
   // Get some data from the source variable, which is captured
   // from the parent context.
   size_t estimated = dispatch_source_get_data(source);
 
   // Continue reading the descriptor...
});
dispatch_resume(source);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0); //每秒执行
dispatch_source_set_event_handler(_timer, ^{
    
    if (self.secondsCountDown <= 0) { //倒计时结束,关闭
        dispatch_source_cancel(_timer);
        dispatch_async(dispatch_get_main_queue(), ^{
            //设置界面的按钮显示 根据自己需求设置
            [self.securityCodeButton setTitle:@"获取验证码" forState:UIControlStateself.securityCodeButton.layer.borderColor = HEXCOLOR(0xff6430).CGColor;
            self.securityCodeButton.userInteractionEnabled = YES;
            self.securityCodeButton.enabled = YES;
        });
    } else {
        int seconds = self.secondsCountDown % 240;
        NSString *strTime = [NSString stringWithFormat:@"%.2d", seconds];
        dispatch_async(dispatch_get_main_queue(), ^{
            //设置界面的按钮显示 根据自己需求设置
            [self.securityCodeButton setTitle:[NSString stringWithFormat:@"%@秒", strTime] forState:UIControlStateNormal];
            self.securityCodeButton.titleLabel.text = [NSString stringWithFormat:@"%@秒", strTime];
            self.securityCodeButton.layer.borderColor = HEXCOLOR(0xffac99).CGColor;
            
            self.securityCodeButton.userInteractionEnabled = NO;
            
        });
        self.secondsCountDown--;
    }
});
dispatch_resume(_timer);
dispatch_source_t CreateDispatchTimer(uint64_t interval,
              uint64_t leeway,
              dispatch_queue_t queue,
              dispatch_block_t block)
{
   dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
                                                     0, 0, queue);
   if (timer)
   {
      dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
      dispatch_source_set_event_handler(timer, block);
      dispatch_resume(timer);
   }
   return timer;
}
 
void MyCreateTimer()
{
   dispatch_source_t aTimer = CreateDispatchTimer(30ull * NSEC_PER_SEC,
                               1ull * NSEC_PER_SEC,
                               dispatch_get_main_queue(),
                               ^{ MyPeriodicTask(); });
 
   // Store it somewhere for later use.
    if (aTimer)
    {
        MyStoreTimer(aTimer);
    }
}
dispatch_source_t ProcessContentsOfFile(const char* filename)
{
   // Prepare the file for reading.
   int fd = open(filename, O_RDONLY);
   if (fd == -1)
      return NULL;
   fcntl(fd, F_SETFL, O_NONBLOCK);  // Avoid blocking the read operation
 
   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   dispatch_source_t readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
                                   fd, 0, queue);
   if (!readSource)
   {
      close(fd);
      return NULL;
   }
 
   // Install the event handler
   dispatch_source_set_event_handler(readSource, ^{
      size_t estimated = dispatch_source_get_data(readSource) + 1;
      // Read the data into a text buffer.
      char* buffer = (char*)malloc(estimated);
      if (buffer)
      {
         ssize_t actual = read(fd, buffer, (estimated));
         Boolean done = MyProcessFileData(buffer, actual);  // Process the data.
 
         // Release the buffer when done.
         free(buffer);
 
         // If there is no more data, cancel the source.
         if (done)
            dispatch_source_cancel(readSource);
      }
    });
 
   // Install the cancellation handler
   dispatch_source_set_cancel_handler(readSource, ^{close(fd);});
 
   // Start reading the file.
   dispatch_resume(readSource);
   return readSource;
}
dispatch_source_t WriteDataToFile(const char* filename)
{
    int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC,
                      (S_IRUSR | S_IWUSR | S_ISUID | S_ISGID));
    if (fd == -1)
        return NULL;
    fcntl(fd, F_SETFL); // Block during the write.
 
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_source_t writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE,
                            fd, 0, queue);
    if (!writeSource)
    {
        close(fd);
        return NULL;
    }
 
    dispatch_source_set_event_handler(writeSource, ^{
        size_t bufferSize = MyGetDataSize();
        void* buffer = malloc(bufferSize);
 
        size_t actual = MyGetData(buffer, bufferSize);
        write(fd, buffer, actual);
 
        free(buffer);
 
        // Cancel and release the dispatch source when done.
        dispatch_source_cancel(writeSource);
    });
 
    dispatch_source_set_cancel_handler(writeSource, ^{close(fd);});
    dispatch_resume(writeSource);
    return (writeSource);
}
dispatch_source_t MonitorNameChangesToFile(const char* filename)
{
   int fd = open(filename, O_EVTONLY);
   if (fd == -1)
      return NULL;
 
   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE,
                fd, DISPATCH_VNODE_RENAME, queue);
   if (source)
   {
      // Copy the filename for later use.
      int length = strlen(filename);
      char* newString = (char*)malloc(length + 1);
      newString = strcpy(newString, filename);
      dispatch_set_context(source, newString);
 
      // Install the event handler to process the name change
      dispatch_source_set_event_handler(source, ^{
            const char*  oldFilename = (char*)dispatch_get_context(source);
            MyUpdateFileName(oldFilename, fd);
      });
 
      // Install a cancellation handler to free the descriptor
      // and the stored string.
      dispatch_source_set_cancel_handler(source, ^{
          char* fileStr = (char*)dispatch_get_context(source);
          free(fileStr);
          close(fd);
      });
 
      // Start processing events.
      dispatch_resume(source);
   }
   else
      close(fd);
 
   return source;
}
void InstallSignalHandler()
{
   // Make sure the signal does not terminate the application.
   signal(SIGHUP, SIG_IGN);
 
   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGHUP, 0, queue);
 
   if (source)
   {
      dispatch_source_set_event_handler(source, ^{
         MyProcessSIGHUP();
      });
 
      // Start processing signals
      dispatch_resume(source);
   }
}
void MonitorParentProcess()
{
   pid_t parentPID = getppid();
 
   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC,
                                                      parentPID, DISPATCH_PROC_EXIT, queue);
   if (source)
   {
      dispatch_source_set_event_handler(source, ^{
         MySetAppExitFlag();
         dispatch_source_cancel(source);
         dispatch_release(source);
      });
      dispatch_resume(source);
   }
}

Suspending and Resuming Dispatch Sources

您可以使用dispatch_suspend和dispatch_resume方法临时暂停和恢复调度源事件的传递

###防止GCD产生死锁

接下来将讲解一下GCD使用时可能会产生死锁的情况,首先举一个比较简单的栗子:

- (void)viewWillAppear:(BOOL)animated
{
    NSLog(@"Before");
    
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"In");
    });
    
    NSLog(@"After");
}

上述代码就会产生死锁,分析下原因,首先,viewWillAppear:方法是在主线程中执行的,接着调用dispatch_sync方法,该方法会阻塞当前线程,也就是会阻塞主线程,主线程被阻塞是为了等待任务的完成,然后该代码将任务添加到了主队列,主队列会将任务交给主线程执行,但此时主线程阻塞了,任务添加进了主线程得不到运行,而主线程在等待任务的执行,因此就造成了死锁。

可以实现计时器https://www.jianshu.com/p/e9d8a087f6c0

Operation Queues

操作队列(operation queue)是由 GCD 提供的一个队列模型的 Cocoa 抽象。GCD 提供了更加底层的控制,而操作队列则在 GCD 之上实现了一些方便的功能,这些功能对于 app 的开发者来说通常是最好最安全的选择。

基础用法在官方文档,另外WWDC-2015有一个视频Advanced NSOperations,关于NSOperation的一个另类的用法

Operation

NSOperation是抽象类,使用时要继承它,或使用系统提供有2个子类:NSInvocationOperation、NSBlockOperation

/**
 * 使用子类 NSInvocationOperation
 */
- (void)useInvocationOperation {
    
    // 1.创建 NSInvocationOperation 对象
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
    
    // 2.调用 start 方法开始执行操作
    [op start];
}

- (void)task1 {
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@", [NSThread currentThread]);     // 打印当前线程
    }
}

/**
 * 使用子类 NSBlockOperation
 */
- (void)useBlockOperation {
    
    // 1.创建 NSBlockOperation 对象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];          // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    
    // 2.调用 start 方法开始执行操作
    [op start];
}

所有NSOperation对象都支持以下主要功能:

  • 支持在操作对象之间建立基于图形的依赖关系。

  • Support for an optional completion block, which is executed after the operation’s main task finishes. 支持可选的完成块,该操作在主要任务完成后执行。(仅限OS X v10.6及更高版本。)

  • 支持使用KVO通知监控操作执行状态的更改。

  • 支持确定操作的优先级,从而影响其相对执行顺序。有关更多信息,请参阅更改操作的执行优先级。

  • 支持取消允许您在执行操作时暂停操作的语义

不应该在主线程执行operation

Concurrent Versus Non-concurrent Operations

直接调用NSOperation的start方法,不能保证代码会并发执行,可以调用NSOperation的isConcurrent获取

大多数情况下不需要为了并发去设计一个 Concurrent Operations 类,使用NSOperationQueue会自动实现并发,除非不打算将operation加入queue,才需要去设计一个 Concurrent Operations 类

NSInvocationOperation 和 NSBlockOperation 的 isConcurrent isAsynchronous 为 NO

自定义NSOperation

每个NSOperation至少应至少实现以下方法:

  1. 自定义初始化方法
  2. main
@interface MyNonConcurrentOperation : NSOperation
@property id (strong) myData;
-(id)initWithData:(id)data;
@end
 
@implementation MyNonConcurrentOperation
- (id)initWithData:(id)data {
   if (self = [super init])
      myData = data;
   return self;
}
 
-(void)main {
   @try {
      // Do some work on myData and report the results.
   }
   @catch(...) {
      // Do not rethrow exceptions.
   }
}
@end

响应取消事件

自定义的NSOperation要能支持取消,就像网络请求能取消一样,这样使用operation时,就能在不需要的时候取消它

isCancelled方法本身非常轻量级,可以经常调用,而不会造成任何明显的性能损失。在设计操作对象时,应考虑isCancelled在代码中的以下位置调用该方法:

  • 在您执行任何实际工作之前
  • 在循环的每次迭代期间至少一次,或者如果每次迭代相对较长则更频繁
  • 在代码中的任何位置,相对容易中止操作
- (void)main {
   @try {
      BOOL isDone = NO;
 
      while (![self isCancelled] && !isDone) {
          // Do some work and set isDone to YES when finished
      }
   }
   @catch(...) {
      // Do not rethrow exceptions.
   }
}

Configuring Operations for Concurrent Execution

非并发的自定义operation很简单,并发的比较复杂,但可以提供更高的可定制性,这也是为什么SDWebImage使用自定义子类来实现下载任务SDWebImageDownloaderOperation。(为什么SDWebImage需要自定义异步的NSOperation?因为SDWebImageDownloaderOperation这个操作内,是一个异步的下载图片的过程,不自定义,无法实现)

默认是同步执行的,如果要设置为并发,需要覆盖这些方法:

方法 描述
start (必需)所有并发操作必须覆盖此方法,并将默认行为替换为自己的自定义实现。要手动执行操作,请调用其start方法。因此,此方法的实现是操作的起点,也是设置执行任务的线程或其他执行环境的位置。您的实施不得super随时致电。
main (可选)此方法通常用于实现与操作对象关联的任务。虽然您可以在start方法中执行任务,但使用此方法实现任务可以使您的设置和任务代码更清晰地分离。
isExecuting、isFinished (必需)并发操作负责设置其执行环境并将该环境的状态报告给外部客户端。因此,并发操作必须维护一些状态信息,以便知道它何时执行其任务以及何时完成该任务。然后必须使用这些方法报告该状态。您同时从其他线程调用这些方法的实现必须是安全的。更改这些方法报告的值时,还必须为预期的键路径生成相应的KVO通知。
isConcurrent (必需)要将操作标识为并发操作,请覆盖此方法并返回YES。
@interface MyOperation : NSOperation {
    BOOL        executing;
    BOOL        finished;
}
- (void)completeOperation;
@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 {
   @try {
 
       // Do the main work of the operation here.
 
       [self completeOperation];
   }
   @catch(...) {
      // Do not rethrow exceptions.
   }
}
 
- (void)completeOperation {
    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];
 
    executing = NO;
    finished = YES;
 
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}
 
- (BOOL)isExecuting {
    return executing;
}
 
- (BOOL)isFinished {
    return finished;
}

// 这个已过期
// - (BOOL)isConcurrent {
//    return YES;
//}

// 有一些说:注:当操作任务加入到操作队列后,会忽略该属性(https://www.jianshu.com/p/52fe1b85c404)
- (BOOL)isAsynchronous
{
    return YES;
}

@end

Maintaining KVO Compliance

自定义异步operation时复杂的,需要做很多事情,比如kvo

自定义操作对象的执行行为

可以设置operation的优先级,依赖,OS X 还能改Underlying Thread Priority

completionBlock 文档说只有 OS X 支持,但是好像iOS也能用

tips

Avoid Per-Thread Storage: 大意是避免多线程访问的问题,比如2个售票线程买1份票。具体不懂

POSIX线程-Per-Thread Storage

Keep References to Your Operation Object As Needed

执行operation

NSOperationQueue

NSOperationQueue * aQueue = [[NSOperationQueue alloc] init];
[aQueue addOperation:anOp]; // Add a single operation
[aQueue addOperations:anArrayOfOps waitUntilFinished:NO]; // Add multiple operations
[aQueue addOperationWithBlock:^{
   /* Do something. */
}];

在将操作对象添加到队列之前,您应该对操作对象进行所有必要的配置和修改,因为一旦添加,操作可以在任何时间运行,这对于更改具有预期效果可能为时已晚。

手动执行

- (BOOL)performOperation:(NSOperation*)anOp
{
   BOOL        ranIt = NO;
 
   if ([anOp isReady] && ![anOp isCancelled])
   {
      if (![anOp isConcurrent])
         [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;
}

等待操作完成

为了获得最佳性能,您应该将操作设计为尽可能异步,让应用程序在执行操作时可以自由地执行其他工作。如果创建操作的代码也处理该操作的结果,则可以使用该waitUntilFinished方法NSOperation来阻止该代码,直到操作完成。

除了等待单个操作完成之外,您还可以通过调用waitUntilAllOperationsAreFinished方法等待队列中的所有操作NSOperationQueue。等待整个队列完成时,请注意应用程序的其他线程仍然可以向队列添加操作,从而延长等待时间。

暂停和恢复队列

如果要暂停执行操作,可以使用该setSuspended:方法挂起相应的操作队列。挂起队列不会导致已执行的操作在其任务中间暂停。它只是阻止计划执行新操作。您可以暂停队列以响应用户请求暂停任何正在进行的工作,因为期望用户最终可能希望恢复该工作。

SDWebImage中的operation

NSOperation的妙用

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock {
    /// ...
    
    NSOperation *operation = [NSOperation new];
    void(^queryDiskBlock)(void) =  ^{
        if (operation.isCancelled) {
            // do not call the completion if cancelled
            return;
        }
        
        @autoreleasepool {
            /// query
        }
    };
    
    if (options & SDImageCacheQueryDiskSync) {
        queryDiskBlock();
    } else {
        // ioQueue: dispatch_queue_t
        // ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
        dispatch_async(self.ioQueue, queryDiskBlock); 
    }
    
    return operation;
}

operation的作用

整个函数将operation返回,queryDiskBlock加入ioQueue之后,不是立刻执行的,所以在执行queryDiskBlock的时候,block里面对operation进行判断,如果外部将operation已经取消了,就不执行查询了。

这里为什么不用NSBlockOperation?

难道NSBlockOperation不能取消,并不是,而是NSBlockOperation不能加入GCD的dispatch_queue_t

SDWebImageDownloaderOperation

为什么SDWebImage需要自定义异步的NSOperation?

因为SDWebImageDownloaderOperation这个操作内,是一个异步的下载图片的过程,不自定义,无法实现

重点是start等方法的实现,阐释了如何自定义异步的NSOperation。以及外部的queue对SDWebImageDownloaderOperation的调用和管理

Migrating Away from Threads

Replacing Threads with Dispatch Queues

Eliminating Lock-Based Code 消除基于锁的代码

对于线程代码,锁是同步对线程之间共享的资源的访问的传统方法之一。但是,使用锁是有代价的。即使在无争议的情况下,总是会出现与锁定相关的性能损失。在有争议的情况下,一个或多个线程有可能在等待锁定释放时阻塞不确定的时间。

用队列替换基于锁的代码消除了与锁相关的许多惩罚,并简化了剩余的代码。您可以改为创建一个队列来序列化访问该资源的任务,而不是使用锁来保护共享资源。队列不会像锁一样处罚。例如,排队任务不需要陷入内核以获取互斥锁。

排队任务时,您必须做出的主要决定是同步还是异步。异步提交任务可让当前线程在执行任务时继续运行。同步提交任务会阻止当前线程,直到任务完成。这两个选项都有适当的用途,尽管只要有可能,异步提交任务肯定是有利的。

Implementing an Asynchronous Lock 实现异步锁

大概是,资源的修改必须交由一个同步队列执行

dispatch_async(obj->serial_queue, ^{
   // Critical section
});

Executing Critical Sections Synchronously 同步执行关键部分

dispatch_sync(my_queue,^ {
   // Critical section
});

Improving on Loop Code

/// 每1 apply
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(count, queue, ^(size_t i) {
   printf("%u\n", i);
});

/// 每137 apply
int stride = 137;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
dispatch_apply(count / stride, queue, ^(size_t idx){
    size_t j = idx * stride;
    size_t j_stop = j + stride;
    do {
       printf("%u\n", (unsigned int)j++);
    }while (j < j_stop);
});
 
/// for
size_t i;
for (i = count - (count % stride); i < count; i++)
   printf("%u\n", (unsigned int)i);

不知道说的是什么,也许循环体内任务重时,这个好用