OC底层原理 - 21 NSThread&GCD& NSOperation

本文的主要目的是介绍NSThreadGCDNSOperation常见的使用方式。

NSThread

NSthread是苹果官方提供面向对象的线程操作技术,是对thread的上层封装,比较偏向于底层。简单方便,可以直接操作线程对象,使用频率较少。它需要程序员自己管理线程的生命周期。

NSThread的使用如下

  • 通过init初始化方式创建
//方式1
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument API_AVAILABLE(macos(10.5), iOS(2.0), watchos(2.0), tvos(9.0));
//示例
NSThread* thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:@"方式1"];
[thread1 start];

//方式2
- (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), iOS(10.0), watchos(3.0), tvos(10.0));
//示例
self.thread1 = [[NSThread alloc] initWithBlock:^{
    NSLog(@"%@",[NSThread currentThread]);
}];
[self.thread1 start];
  • 通过detachNewThread(xxx)构造器方式创建
//方式1
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
//示例
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:@"方式1"];

//方式2
+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), iOS(10.0), watchos(3.0), tvos(10.0));
//示例
[NSThread detachNewThreadWithBlock:^{
    NSLog(@"%@", [NSThread currentThread]);
}];
  • 通过performSelector(xxx)方式创建
//在主线程执行
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
//示例
[self performSelectorOnMainThread:@selector(doSomething:) withObject:@"方式3" waitUntilDone:YES modes:@[NSRunLoopCommonModes]];
[self performSelectorOnMainThread:@selector(doSomething:) withObject:@"方式3" waitUntilDone:YES];

//在指定线程执行
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array API_AVAILABLE(macos(10.5), iOS(2.0), watchos(2.0), tvos(9.0));
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), iOS(2.0), watchos(2.0), tvos(9.0));
//示例
[self performSelectorOnMainThread:@selector(doSomething:) withObject:@"方式3" waitUntilDone:YES];
[self performSelector:@selector(doSomething:) onThread:[NSThread currentThread] withObject:@"方式3" waitUntilDone:YES modes:@[NSRunLoopCommonModes]];

//创建一个子线程执行
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg API_AVAILABLE(macos(10.5), iOS(2.0), watchos(2.0), tvos(9.0));
//示例
[self performSelectorInBackground:@selector(doSomething:) withObject:@"方式3"];

NSThread的属性

  • 以下属性用于表示当前线程的状态
//表示线程是否正在执行
@property (readonly, getter=isExecuting) BOOL executing;
//表示线程是否执行完成
@property (readonly, getter=isFinished) BOOL finished;
//线程是否被取消
@property (readonly, getter=isCancelled) BOOL cancelled;
  • 线程优先级
/** NSQualityOfService:
  NSQualityOfServiceUserInteractive:最高优先级,主要用于提供交互UI的操作,比如处理点击事件,绘制图像到屏幕上
  NSQualityOfServiceUserInitiated:次高优先级,主要用于执行需要立即返回的任务
  NSQualityOfServiceDefault:默认优先级,当没有设置优先级的时候,线程默认优先级
  NSQualityOfServiceUtility:普通优先级,主要用于不需要立即返回的任务
  NSQualityOfServiceBackground:后台优先级,用于完全不紧急的任务
*/
@property NSQualityOfService qualityOfService; // read-only after the thread is started

+ (double)threadPriority;
  • 线程名称
@property (nullable, copy) NSString *name;
  • 线程使用栈区大小,默认是512K
@property NSUInteger stackSize;
  • 线程调用栈信息
//该线程中函数调用的虚拟地址的数组
@property (class, readonly, copy) NSArray<NSNumber *> *callStackReturnAddresses;
//线程调用函数的名字数字
@property (class, readonly, copy) NSArray<NSString *> *callStackSymbols;
  • 判断是否主线程或者获取当前
//是否是主线程
@property (readonly) BOOL isMainThread;
@property (class, readonly) BOOL isMainThread; // reports whether current thread is main

//获取主线程
@property (class, readonly, strong) NSThread *mainThread;

//获取当前线程
@property (class, readonly, strong) NSThread *currentThread;

实例方法

  • 线程启动,实例化的线程需要手动启动,此时线程进入就绪状态
- (void)start;

NSThread* thread = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:@"方式1"];
[thread start];
  • 线程取消。调用-cancel方法并不会立刻取消线程,它仅仅是将cancelled属性设置为YES。cancelled也仅仅是一个用于记录状态的属性。线程取消的功能需要我们在main函数中自己实现
- (void)cancel;

[thread cancel];
  • 如果创建NSThread时不指定target或者selector,那么main函数就会直接推出,如果都指定了,main函数会调用[target selector:argument],执行完成后退出。当调用start时,start方法内部调用的就是main方法
- (void)main;   // thread body method

[thread main];

类方法

  • 线程阻塞(暂停)
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
  • 强制停止线程,线程进入死亡状态。该方法属于核弹级别终极API,调用之后会立即终止线程,即使任务还没有执行完成也会中断。这就非常有可能导致内存泄露等严重问题,所以一般不推荐使用
+ (void)exit;
  • 设置线程调度优先级 priority(优先级)。调度优先级的取值范围是0.0 - 1.0默认0.5,值越大,优先级越高。
+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;

GCD

dispatch_after函数

dispatch_after能让我们添加进队列的任务延时执行,该函数并不是在指定时间后执行处理,而只是在指定时间后追加处理到dispatch_queue

/*
参数1 when:任务在何时开始执行
参数2 queue:执行任务的队列
参数3 block:需要执行的任务
*/
void
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
        
//示例
dispatch_queue_t queue = dispatch_queue_create("hqThread", DISPATCH_QUEUE_SERIAL);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC), queue, ^{
    NSLog(@"test dispatch_after");
});

dispatch_once函数

dispatch_once保证在App运行期间,block中的代码只执行一次。多用于创建单例的情况。

重点:dispatch_once是线程安全的。

/*
参数1 predicate:用于检查该代码块是否已经被调度的谓词,虽然它是一个长整形,但是它是作为BOOL使用,且当值超过
参数2 block:需要执行的任务
*/
void
dispatch_once(dispatch_once_t *predicate, DISPATCH_NOESCAPE dispatch_block_t block);

//示例
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    NSLog(@"创建单例");
});

dispatch_apply函数

该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。

注意:该函数是需要等待全部任务执行完成之后再继续函数后面的操作,效果与dispatch_sync类似,因此,在指定执行任务的线程时,需要防止死锁。

/*
参数1 iterations:指定任务执行的次数
参数2 queue:执行任务的线程
参数3 block:需要执行的任务,该block有个入参,表示当前是第几次执行
*/
void
dispatch_apply(size_t iterations,
        dispatch_queue_t DISPATCH_APPLY_QUEUE_ARG_NULLABILITY queue,
        DISPATCH_NOESCAPE void (^block)(size_t));

//示例
dispatch_apply(3, dispatch_queue_create("hqThread", DISPATCH_QUEUE_CONCURRENT), ^(size_t a) {
    NSLog(@"[%zu] current thread:%@", a, [NSThread currentThread]);
});

dispatch_group_t 任务组

它的主要用于监听管理任务组中任务完成情况。与dispatch_group_notify函数配合,可以在任务完成后做一些操作处理。

常用方法如下:

// 创建一个任务组
dispatch_group_t dispatch_group_create(void);

// 将任务添加到任务组里,并且异步执行
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);

// 表示接下来手动向任务组添加任务,即任务组中的任务数+1
void dispatch_group_enter(dispatch_group_t group);

// 任务组中任务数-1与dispatch_group_enter必须成对出现
void dispatch_group_leave(dispatch_group_t group);

// 等待之前任务执行完成后才继续执行
intptr_t dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);

// 当任务组中任务完成,会出发出发此方法的block
void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);

接下来分别通过示例来说任务组的使用进行说明。

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("hqThread", DISPATCH_QUEUE_SERIAL);
    
dispatch_group_async(group, queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"[任务1] %@", [NSThread currentThread]);
});
    
dispatch_group_enter(group);
dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"[任务2] %@", [NSThread currentThread]);
});
    
dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"[任务3] %@", [NSThread currentThread]);
    dispatch_group_leave(group);
});

dispatch_async(queue, ^{
    NSLog(@"wait start");
    dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC*6));
    NSLog(@"wait end");
});
    
dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:4];
    NSLog(@"[任务4] %@", [NSThread currentThread]);
});
    
dispatch_group_notify(group, queue, ^{
    NSLog(@"所有任务执行完成");
});
    
dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:4];
    NSLog(@"[任务5] %@", [NSThread currentThread]);
});

//当任务的队列为DISPATCH_QUEUE_SERIAL时,执行结果:
2021-04-16 16:27:21.101260+0800 Demo[87930:20756012] [任务1] <NSThread: 0x600003759880>{number = 7, name = (null)}
2021-04-16 16:27:23.106189+0800 Demo[87930:20756012] [任务2] <NSThread: 0x600003759880>{number = 7, name = (null)}
2021-04-16 16:27:25.111495+0800 Demo[87930:20756012] [任务3] <NSThread: 0x600003759880>{number = 7, name = (null)}
2021-04-16 16:27:25.111771+0800 Demo[87930:20756012] wait start
2021-04-16 16:27:31.112103+0800 Demo[87930:20756012] wait end
2021-04-16 16:27:35.116521+0800 Demo[87930:20756012] [任务4] <NSThread: 0x600003759880>{number = 7, name = (null)}
2021-04-16 16:27:39.119116+0800 Demo[87930:20756012] [任务5] <NSThread: 0x600003759880>{number = 7, name = (null)}
2021-04-16 16:27:39.119395+0800 Demo[87930:20756012] 所有任务执行完成

//当任务的队列为DISPATCH_QUEUE_CONCURRENT时,执行结果:
2021-04-16 16:48:48.220717+0800 Demo[88208:20774077] wait start
2021-04-16 16:48:50.223659+0800 Demo[88208:20774082] [任务1] <NSThread: 0x60000139d900>{number = 3, name = (null)}
2021-04-16 16:48:50.223659+0800 Demo[88208:20774080] [任务2] <NSThread: 0x6000013e10c0>{number = 6, name = (null)}
2021-04-16 16:48:50.223659+0800 Demo[88208:20774083] [任务3] <NSThread: 0x6000013e5f00>{number = 7, name = (null)}
2021-04-16 16:48:50.223970+0800 Demo[88208:20774077] wait end
2021-04-16 16:48:50.223979+0800 Demo[88208:20774082] 所有任务执行完成
2021-04-16 16:48:52.222427+0800 Demo[88208:20774078] [任务5] <NSThread: 0x6000013e06c0>{number = 9, name = (null)}
2021-04-16 16:48:52.222427+0800 Demo[88208:20774079] [任务4] <NSThread: 0x6000013e0a80>{number = 4, name = (null)}

总结:

  1. dispatch_group_async函数是自动将任务添加到任务组中。
  2. dispatch_group_enter函数是手动将任务添加到任务组中。
  3. 当通过dispatch_group_enter函数被调用后,dispatch_group_leave函数调用前,其间的所有任务都被添加到任务组中。
  4. 当任务所在的队列为串行队列时,会等待group关联队列中所有任务执行完成之后才会发出notify信号,调用dispatch_group_notify函数,但是,当group组中的任务执行完成之后,会执行dispatch_group_wait函数。

dispatch_barrier_sync & dispatch_barrier_async

栅栏函数,主要有两种使用场景:串行队列、并发队列

  • 栅栏函数 + 串行队列
dispatch_queue_t queue = dispatch_queue_create("hqThread", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"[任务1] %@", [NSThread currentThread]);
});
    
dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"[任务2] %@", [NSThread currentThread]);
});
    
dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"[任务3] %@", [NSThread currentThread]);
});
    
dispatch_barrier_async(queue, ^{
    NSLog(@"------------栅栏任务------------%@", [NSThread currentThread]);
});
    
dispatch_async(queue, ^{
    NSLog(@"[任务4] %@", [NSThread currentThread]);
});
    
dispatch_async(queue, ^{
    NSLog(@"[任务5] %@", [NSThread currentThread]);
});

//执行结果
2021-04-19 09:46:18.453361+0800 Demo[13733:22451112] [任务1] <NSThread: 0x6000014b6c00>{number = 6, name = (null)}
2021-04-19 09:46:20.458536+0800 Demo[13733:22451112] [任务2] <NSThread: 0x6000014b6c00>{number = 6, name = (null)}
2021-04-19 09:46:22.463882+0800 Demo[13733:22451112] [任务3] <NSThread: 0x6000014b6c00>{number = 6, name = (null)}
2021-04-19 09:46:22.464409+0800 Demo[13733:22451112] ------------栅栏任务------------<NSThread: 0x6000014b6c00>{number = 6, name = (null)}
2021-04-19 09:46:22.464688+0800 Demo[13733:22451112] [任务4] <NSThread: 0x6000014b6c00>{number = 6, name = (null)}
2021-04-19 09:46:22.464940+0800 Demo[13733:22451112] [任务5] <NSThread: 0x6000014b6c00>{number = 6, name = (null)}
  • 栅栏函数 + 并发队列
dispatch_queue_t queue = dispatch_queue_create("hqThread", DISPATCH_QUEUE_CONCURRENT);//DISPATCH_QUEUE_CONCURRENT
dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"[任务1] %@", [NSThread currentThread]);
});
    
dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"[任务2] %@", [NSThread currentThread]);
});
    
dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"[任务3] %@", [NSThread currentThread]);
});
    
dispatch_barrier_async(queue, ^{
    NSLog(@"------------栅栏任务------------%@", [NSThread currentThread]);
});
    
dispatch_async(queue, ^{
    NSLog(@"[任务4] %@", [NSThread currentThread]);
});
    
dispatch_async(queue, ^{
    NSLog(@"[任务5] %@", [NSThread currentThread]);
});

//执行结果
2021-04-19 09:47:09.266069+0800 Demo[13753:22452474] [任务2] <NSThread: 0x60000192c940>{number = 5, name = (null)}
2021-04-19 09:47:09.266121+0800 Demo[13753:22452477] [任务1] <NSThread: 0x60000192c240>{number = 8, name = (null)}
2021-04-19 09:47:09.266191+0800 Demo[13753:22452478] [任务3] <NSThread: 0x6000019212c0>{number = 3, name = (null)}
2021-04-19 09:47:09.266493+0800 Demo[13753:22452478] ------------栅栏任务------------<NSThread: 0x6000019212c0>{number = 3, name = (null)}
2021-04-19 09:47:09.266699+0800 Demo[13753:22452478] [任务4] <NSThread: 0x6000019212c0>{number = 3, name = (null)}
2021-04-19 09:47:09.266762+0800 Demo[13753:22452474] [任务5] <NSThread: 0x60000192c940>{number = 5, name = (null)}

从上面代码和结果分析可知,dispatch_barrier_async函数的主要作用是等待队列中,位于dispatch_barrier_async函数前的任务完成之后,再执行dispatch_barrier_async函数后的任务。

在串行队列中,任务本身就是按顺序执行,因此,栅栏函数的作用不明显。而在并发队列中,使用栅栏函数可以很好的控制队列内任务执行的顺序。

重点:dispatch_barrier_asyncdispatch_barrier_sync
相同点:都是起到栅栏的作用,必须等待栅栏前面的任务执行完成之后再执行栅栏之后的任务。
不同点:dispatch_barrier_sync函数不仅会阻塞队列任务的执行,也会阻塞线程的执行,阻塞线程执行同dispatch_sync函数类似,也可能会引起死锁。所以dispatch_barrier_sync函数需要慎用。

栅栏函数中尽量使用自定义队列,因为使用全局队列是无效的,因为使用全局队列时由于对全局队列造成堵塞,可能致使系统其他调用全局队列的地方也堵塞从而导致崩溃。

dispatch_semaphore_t 信号量

信号量主要用作同步锁,用于控制GCD最大并发数。常见的函数有:

dispatch_semaphore_create():创建信号量dispatch_semaphore_t

dispatch_semaphore_wait():信号量减1.当信号量< 0时会阻塞当前线程,根据传入的等待时间决定接下来的操作——如果永久等待将等到信号(signal)才执行下去

dispatch_semaphore_signal():信号量加1.当信号量>= 0 会执行wait之后的代码.

示例代码如下:

dispatch_queue_t queue = dispatch_queue_create("hqThread", DISPATCH_QUEUE_CONCURRENT);
        
for (int i = 0; i < 10; i++) {
    dispatch_async(queue, ^{
        NSLog(@"当前 - %d, 线程 - %@", i, [NSThread currentThread]);
    });
}

//利用信号量来改写
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for (int i = 0; i < 10; i++) {
    dispatch_async(queue, ^{
        NSLog(@"当前 - %d, 线程 - %@", i, [NSThread currentThread]);

        dispatch_semaphore_signal(sem);
    });
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}

如果当创建信号量时传入值为1又会怎么样呢?
答:当i=0时,开启线程去执行任务后,wait信号量-1,但是此时信号量为0不会阻塞线程,所以进入i=1;
当i=1时,开启线程去执行任务后,wait信号量-1,此时信号量为-1阻塞线程,它将等待signal(即信号量+1)后再执行下去;
因此,若是并发队列时,当创建信号量为1时,任务1和任务2执行顺序不固定。

dispatch_source_t

dispatch_source_t主要用于计时操作,其原因是因为它创建的timer不依赖于RunLoop,且计时精准度比NSTimer高。

在iOS开发中一般使用NSTimer来处理定时逻辑,但NSTimer是依赖Runloop的,而Runloop可以运行在不同的模式下。如果NSTimer添加在一种模式下,当Runloop运行在其他模式下的时候,定时器就挂机了;假如当前Runloop处于阻塞状态,NSTimer触发时间就会推迟到下一个Runloop周期。因此NSTimer在计时上会有误差,并不是特别精确,而GCD定时器不依赖Runloop,计时精度要高很多。

dispatch_source是一种基本的数据类型,可以用来监听一些底层的系统事件,如:

  • Timer Dispatch Source:定时器事件源,用来生成周期性的通知或回调

  • Signal Dispatch Source:监听信号事件源,当有UNIX信号发生时会通知

  • Descriptor Dispatch Source:监听文件或socket事件源,当文件或socket数据发生变化时会通知

  • Process Dispatch Source:监听进程事件源,与进程相关的事件通知

  • Mach port Dispatch Source:监听Mach端口事件源

  • Custom Dispatch Source:监听自定义事件源

Dispatch Source一共可以监听六类事件,分为11个类型:

  • DISPATCH_SOURCE_TYPE_DATA_ADD:属于自定义事件,可以通过dispatch_source_get_data函数获取事件变量数据,在我们自定义的方法中可以调用dispatch_source_merge_data函数向Dispatch Source设置数据,下文中会有详细的演示。

  • DISPATCH_SOURCE_TYPE_DATA_OR:属于自定义事件,用法同上面的类型一样。

  • DISPATCH_SOURCE_TYPE_MACH_SEND:Mach端口发送事件。

  • DISPATCH_SOURCE_TYPE_MACH_RECV:Mach端口接收事件。

  • DISPATCH_SOURCE_TYPE_PROC:与进程相关的事件。

  • DISPATCH_SOURCE_TYPE_READ:读文件事件。

  • DISPATCH_SOURCE_TYPE_WRITE:写文件事件。

  • DISPATCH_SOURCE_TYPE_VNODE:文件属性更改事件。

  • DISPATCH_SOURCE_TYPE_SIGNAL:接收信号事件。

  • DISPATCH_SOURCE_TYPE_TIMER:定时器事件。

  • DISPATCH_SOURCE_TYPE_MEMORYPRESSURE:内存压力事件

主要使用的API:

  • dispatch_source_create: 创建事件源
/*
* 参数1(type):第一个参数用于标识Dispatch Source要监听的事件类型,共有11个类型
* 参数2(handle):取决于要监听事件的类型,比如监听的是Mach端口相关的事件,那该参数就是mach_port_t类型的Mach端口号;
* 参数3(mask):取决于要监听事件的类型,比如监听文件属性更改的事件,那么该参数就是标识文件的哪个属性,如:DISPATCH_VNODE_RENAME
* 参数4(queue):回调函数所在的队列
*/
dispatch_source_t dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, uintptr_t mask, dispatch_queue_t _Nullable queue);
    
//示例
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
  • dispatch_source_set_timer:给监听事件类型为DISPATCH_SOURCE_TYPE_TIMER的Dispatch Source设置相关属性
/*
* 参数1(source):该参数为目标Dispatch Source
* 参数2(start):定时器的起始时间
* 参数3(interval):定时器的间隔时间,单位为纳秒
* 参数4(leeway):间隔时间的精度,单位为纳秒
*/
void dispatch_source_set_timer(dispatch_source_t source, dispatch_time_t start, uint64_t interval, uint64_t leeway);

//示例
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC, 0.1*NSEC_PER_SEC);
  • dispatch_source_set_event_handler: 设置数据源回调。
    当Dispatch Source监听到事件时,会调用指定的回调函数或者闭包,该回调函数和闭包就是Dispatch Source的事件处理器,我们可以通过dispatch_source_set_event_handler或者dispatch_source_set_event_handler_f给创建好的Dispatch Source设置处理器,其中前者是设置闭包形式的处理器,后者是设置函数形式的处理器。
/*
* 参数1(source):目标Dispatch Source
* 参数2(handler):事件处理器
*/
void dispatch_source_set_event_handler(dispatch_source_t source, dispatch_block_t _Nullable handler);

//示例
dispatch_source_set_event_handler(self.timer, ^{
    NSLog(@"GCDTimer");
});
  • dispatch_source_merge_data: 设置事件源数据。
    将数据合并到Dispatch Source中,并将handler提交到目标队列queue再执行。
/*
* 参数1(source):目标Dispatch Source
* 参数2(value):需要合并的数据
*/
void
dispatch_source_merge_data(dispatch_source_t source, uintptr_t value);

//示例
dispatch_source_merge_data(source, 100)
  • dispatch_source_get_data: 获取事件源数据
/*
* 参数1(source):目标Dispatch Source
* 返回值:Dispatch Source待处理的数据
*/
uintptr_t dispatch_source_get_data(dispatch_source_t source);

//示例
dispatch_source_get_data(source)
  • dispatch_resume: 继续
    刚创建好的Dispatch Source是处于暂停状态的,需要手动调用dispatch_resume函数将其启动。
/*
* 参数1(object):非活动的、未挂起的调度源对象
*/
void dispatch_resume(dispatch_object_t object);

//示例
dispatch_resume(timer)
  • dispatch_suspend: 挂起,将Dispatch Source暂时挂起,挂起后可通过dispatch_resume重新激活。
/*
* 参数1(object):调度源对象
*/
void dispatch_suspend(dispatch_object_t object);

//示例
dispatch_suspend(timer)

NSOperation

NSOperation基于GCD的更高一层的封装,NSOperation需要配合NSOperationQueue来实现多线程。
NSOperation实现多线程步骤如下:

  1. 创建任务,将需要执行的任务封装到NSOperation对象中
  2. 创建队列,创建NSOperationQueue队列
  3. 将任务添加到队列中。将NSOperation对象添加到NSOperationQueue中。

基本使用:

NSBlockOperation* operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"NSBlockOperation");
}];
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];

NSOperation类是一个抽象类,实际使用中使用的是NSOperation的子类。

  • NSInvocationOperation类
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomething:) object:@"NSInvocationOperation"];
[queue addOperation:invocationOperation];
  • NSBlockOperation类
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
NSBlockOperation* operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"NSBlockOperation1 - %@", [NSThread currentThread]);
}];
    
[operation addExecutionBlock:^{
    NSLog(@"NSBlockOperation2 - %@", [NSThread currentThread]);
}];
    
[operation addExecutionBlock:^{
    NSLog(@"NSBlockOperation3 - %@", [NSThread currentThread]);
}];
    
[queue addOperation:operation];
  • 自定义NSOperation类
    自定义NSOperation类可以通过实现内部相应的方法来封装任务。
@interface HQOperation : NSOperation
@end

@implementation HQOperation
- (void)main{
    for(int i = 0; i < 3; i++){
        NSLog(@"[%d] %@", i, [NSThread currentThread]);
    }
}
@end

HQOperation* operation = [[HQOperation alloc] init];
[operation start];

NSOperation设置优先级

NSOperationQueue* queue = [[NSOperationQueue alloc] init];
NSBlockOperation* operation1 = [NSBlockOperation blockOperationWithBlock:^{
    [NSThread sleepForTimeInterval:3];
    NSLog(@"NSBlockOperation1 - %@", [NSThread currentThread]);
}];
    
NSBlockOperation* operation2 = [NSBlockOperation blockOperationWithBlock:^{
    [NSThread sleepForTimeInterval:3];
    NSLog(@"NSBlockOperation2 - %@", [NSThread currentThread]);
}];

//设置了最高优先级
operation2.qualityOfService = NSQualityOfServiceUserInteractive;
    
[queue addOperation:operation1];
[queue addOperation:operation2];

//执行结果
2021-04-20 15:21:42.698211+0800 Demo[24004:23167490] NSBlockOperation2 - <NSThread: 0x6000010df780>{number = 7, name = (null)}
2021-04-20 15:21:42.701567+0800 Demo[24004:23167487] NSBlockOperation1 - <NSThread: 0x600001088200>{number = 8, name = (null)}

设置并发数,在GCD的使用中,可以通过信号量的方式来设置并发数,同样,在NSOperation中,也可以轻松设置并发数。

NSOperationQueue* queue = [[NSOperationQueue alloc] init];
//设置最大并发数为3,即同时有3条线程执行任务
queue.maxConcurrentOperationCount = 3;
for(int i = 0; i<10; i++){
    [queue addOperationWithBlock:^{
        NSLog(@"[%d] -- %@", i, [NSThread currentThread]);
    }];
}

//执行结果
2021-04-20 15:29:07.062726+0800 Demo[24082:23173786] [0] -- <NSThread: 0x600002dc1900>{number = 3, name = (null)}
2021-04-20 15:29:07.062730+0800 Demo[24082:23173778] [1] -- <NSThread: 0x600002dcd280>{number = 7, name = (null)}
2021-04-20 15:29:07.062780+0800 Demo[24082:23173787] [2] -- <NSThread: 0x600002dcc1c0>{number = 6, name = (null)}
2021-04-20 15:29:07.062934+0800 Demo[24082:23173786] [3] -- <NSThread: 0x600002dc1900>{number = 3, name = (null)}
2021-04-20 15:29:07.062988+0800 Demo[24082:23173787] [5] -- <NSThread: 0x600002dcc1c0>{number = 6, name = (null)}
2021-04-20 15:29:07.062994+0800 Demo[24082:23173778] [4] -- <NSThread: 0x600002dcd280>{number = 7, name = (null)}
2021-04-20 15:29:07.063117+0800 Demo[24082:23173786] [6] -- <NSThread: 0x600002dc1900>{number = 3, name = (null)}
2021-04-20 15:29:07.063154+0800 Demo[24082:23173787] [7] -- <NSThread: 0x600002dcc1c0>{number = 6, name = (null)}
2021-04-20 15:29:07.063269+0800 Demo[24082:23173778] [8] -- <NSThread: 0x600002dcd280>{number = 7, name = (null)}
2021-04-20 15:29:07.064370+0800 Demo[24082:23173786] [9] -- <NSThread: 0x600002dc1900>{number = 3, name = (null)}

在NSOperation中,还可以添加依赖关系

NSOperationQueue* queue = [[NSOperationQueue alloc] init];
NSBlockOperation* operation1 = [NSBlockOperation blockOperationWithBlock:^{
    [NSThread sleepForTimeInterval:3];
    NSLog(@"请求数据1 - %@", [NSThread currentThread]);
}];
    
NSBlockOperation* operation2 = [NSBlockOperation blockOperationWithBlock:^{
    [NSThread sleepForTimeInterval:3];
    NSLog(@"根据数据1,请求数据2 - %@", [NSThread currentThread]);
}];
    
NSBlockOperation* operation3 = [NSBlockOperation blockOperationWithBlock:^{
    [NSThread sleepForTimeInterval:3];
    NSLog(@"根据数据2,请求数据3 - %@", [NSThread currentThread]);
}];
    
[operation2 addDependency:operation1];
[operation3 addDependency:operation2];
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];

//执行结果
2021-04-20 15:33:51.102025+0800 Demo[24165:23179016] 请求数据1 - <NSThread: 0x600002502380>{number = 7, name = (null)}
2021-04-20 15:33:54.104030+0800 Demo[24165:23179022] 根据数据1,请求数据2 - <NSThread: 0x600002572740>{number = 6, name = (null)}
2021-04-20 15:33:57.108527+0800 Demo[24165:23179016] 根据数据2,请求数据3 - <NSThread: 0x600002502380>{number = 7, name = (null)}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容