Objective-C多线程编程指南
在iOS开发中,多线程编程实践有多种途径,它们各有侧重。
- NSThread
- NSOperation
- GCD
- pthread
NSThread
NSThread是Apple官方推荐的多线程操作途径,它的抽象程度最高,确定是需要自己管理线程的生命周期,线程周期等核心问题。
核心属性有:
-
executing
:是否正在执行,调用start
方法之后为TRUE
。 -
finished
:是否执行完成。 -
cancelled
:是否已取消,调用cancel
方法之后为TRUE
。
核心方法:
-
start
:开始执行任务,实际调用下面的main方法; -
main
:任务最终在此执行; -
cancel
:取消任务。
初始化
直接创建
通过直接使用Apple封装好的接口,就可以多线程执行,简单高效。常用的接口有:
detachNewThreadWithBlock:
detachNewThreadSelector:toTarget:withObject:
initWithBlock:
initWithTarget:selector:object:
// 1. 直接开启一个新线程执行任务
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];
// 2. 先创建线程对象,再运行线程操作,运行前可以设置线程优先级等线程信息
NSThread* myThread = [[NSThread alloc] initWithTarget:self selector:@selecto (doSomething: object:nil];
[myThread start];
//3. 不显式创建线程的方法,使用NSObject的类方法创建一个线程
[self performSelectorInBackground:@selector(doSomething) withObject:nil];
完整示例见: 直接通过创建NSThread加载用户头像列表
继承
通过继承NSThread,将耗时任务封装在类内,可以起到“高内聚,低耦合”的作用。
具体而言,重写NSThread的main
方法执行相关逻辑,然后调用start
方法即可开始执行。
+ (instancetype)threadWithUser:(WRGithubUser *)user{
return [[self alloc] initWithUser:user];
}
- (instancetype)initWithUser:(WRGithubUser*)user{
if ((self = [super init])) {
self.user = user;
}
return self;
}
- (void)setHandler:(WRGithubUserAvatarHandler)handler{
_handler = handler;
// 开始调用下面的main方法
[self start];
}
- (void)main{
if (!_user || !_user.avatarUrlString) {
return;
}
NSURL *url = [NSURL URLWithString:_user.avatarUrlString];
NSData *imageData = [NSData dataWithContentsOfURL:url];
[self.user setAvatar:[UIImage imageWithData:imageData]];
if (_handler) {
_handler();
}
}
完整示例见: 通过继承NSThread加载用户头像列表
NSOperation
NSOperation是一个抽象类,不可直接调用,要么使用系统定义好的两个子类NSInvocationOperation和NSBlockOperation,要么继承自定义实现。
实现逻辑和NSThread大体相同,main
函数是最终执行单任务逻辑的地方,start
用来控制何时以及在哪里开始执行任务,cancel
用来取消任务。不同点在于NSOperation可以:
- 设置任务之间的依赖关系,
addDependency:
removeDependency:
; - 不用管任务执行状态,当一个任务执行完成或被取消,则直接
return
; - 配合NSOperationQueue,加入队列之后自动执行,使用起来会更方便。
NSOperationQueue
NSOperationQueue用来维护一组NSOperation对象的执行顺序和流程。执行次序不但和加入的顺序相关,而且还和任务的优先级Priority有关,很明显高优先级的任务要先执行,低优先级的任务后执行。
一旦加入进去,就不可移除,直到执行完成为止。执行完成之后,自动释放任务对象。
重要的属性:
-
maxConcurrentOperationCount
:设置最大并发数量; -
suspended
:控制该队列是否要挂起; -
currentQueue
:当前队列,属于类对象的静态属性; -
mainQueue
:和主线程相关的任务队列,处理事件循环相关任务。
重要方法:
-
addOperation:
:添加任务; -
addBarrierBlock:
:添加barrier任务,也就是说已入队的所有任务完成之后,才能执行新入队的任务; -
cancelAllOperations
:取消所有任务,但并没有移除,包括正在执行的任务; -
waitUntilAllOperationsAreFinished
:阻塞当前线程,等待所有任务执行完成,此时不可再添加任务。
简单示例:
- (void)start{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (NSString *str in [self urlStrs]) {
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImageWithUrlString:) object:str];
[queue addOperation:operation];
}
[queue addBarrierBlock:^{
NSLog(@"all operations finished");
}];
}
- (void)downloadImageWithUrlString:(NSString*)urlStr{
NSURL *url = [NSURL URLWithString:urlStr];
NSData *data = [NSData dataWithContentsOfURL:url];
NSLog(@"response data length:%zd from \nurl:%@", data.length, urlStr);
// ...
}
- (NSArray<NSString*>*)urlStrs{
return @[
@"https://avatar.csdnimg.cn/B/A/A/3_qq_25537177.jpg",
@"https://profile.csdnimg.cn/B/4/2/3_qq_41185868",
@"https://profile.csdnimg.cn/8/0/6/3_qq_35190492",
@"https://profile.csdnimg.cn/5/2/2/3_dataiyangu",
];
}
完整代码见:NSOperation实践
GCD
Grand Central Dispatch简称GCD,是Apple为多核设备并发编程提供的一套综合性的解决方案,因为是在系统级别上实现的,所以更高效。
概况
队列Queue
在GCD中,一共有三种队列,分别是:
- Serial:对应
DISPATCH_QUEUE_SERIAL
,同一时间只能执行一个任务。常用于访问一些特殊资源,尤其临界资源。 - Concurrent:对应
DISPATCH_QUEUE_CONCURRENT
,实际上是global queue,具有真正的并发能力,任务执行的次序是随机的。 - Main:是一个Serial队列,用来维护在主线程上执行的所有任务。
优先级Priority
GCD中,所有任务都可以指定优先级,共分为四种:
-
DISPATCH_QUEUE_PRIORITY_HIGH
:最高优先级; -
DISPATCH_QUEUE_PRIORITY_DEFAULT
:默认优先级; -
DISPATCH_QUEUE_PRIORITY_LOW
:较低优先级; -
DISPATCH_QUEUE_PRIORITY_BACKGROUND
:最低,常用于处理IO任务。
不过,任务优先级现在被另一个特性服务质量QOS所取代,QOS即Quality of Service。它有五个值,和优先级有一定的对应关系。
-
QOS_CLASS_USER_INTERACTIVE
:表示主线程事件循环相关事件,往往需要更新UI,比如绘制、动画事件。这个级别的任务要保持小规模。 -
QOS_CLASS_USER_INITIATED
:表示由用户发起的,需要等待结果的异步任务,比如创建一个任务,并用进度条显示进度。 -
QOS_CLASS_DEFAULT
:表示来自系统的任务,在这种场景中,任务没有额外说明信息。 -
QOS_CLASS_UTILITY
:表示不需要立即等待执行结果的任务,这类任务往往更加注重性能考量,显示进度与否并不重要。经常用来进行计算、I/O、网络请求等任务。 -
QOS_CLASS_BACKGROUND
:表示不由用户主动发起的任务,用户也不需要知道它的存在,唯一要考量的就是性能。比如预加载任务。
对于global queue,也就是系统级的并发队列,任务优先级和QOS之间的对应关系如下:
Priority | Quality of Service |
---|---|
DISPATCH_QUEUE_PRIORITY_HIGH | QOS_CLASS_USER_INITIATED |
DISPATCH_QUEUE_PRIORITY_DEFAULT | QOS_CLASS_DEFAULT |
DISPATCH_QUEUE_PRIORITY_LOW | QOS_CLASS_UTILITY |
DISPATCH_QUEUE_PRIORITY_BACKGROUND | QOS_CLASS_BACKGROUND |
dispatch_queue_t main, serial, concur1, concur2, concur3;
// 主线程队列,用来维护在主线程执行的任务执行次序
main = dispatch_get_main_queue();
// 串行队列
serial = dispatch_queue_create("COM.WALKER.S", DISPATCH_QUEUE_SERIAL);
// 并发队列
concur1 = dispatch_queue_create("COM.WALKER.C", DISPATCH_QUEUE_CONCURRENT);
// 下面两种是同一回事,但是推荐后面的写法
concur2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
concur3 = dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0);
实践
创建任务
任务表示一段逻辑上完整且具有意义的代码块,可分为同步任务和异步任务。
- 同步任务。使用
dispatch_sync
创建,只有当提交的任务完成时,才会返回。使用的不多,因为容易造成死锁。 - 异步任务。使用
dispatch_async
创建,提交任务之后立即返回,队列属性决定是串行还是并发执行。相互独立的串行队列可并行处理。推荐使用,在需要大量时间才能完成的任务,尤其与UI无关的任务。比如,网络请求,IO,数据库读写时,必须使用它来创建。
// 在串行队列serial中执行同步任务
dispatch_sync(serial, ^{
[self do...]
});
// 在并行队列concur1中执行异步任务
dispatch_async(concur1, ^{
[self do...]
});
// 下载图片
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
NSURL * url = [NSURL URLWithString:@"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"];
NSData * data = [[NSData alloc]initWithContentsOfURL:url];
UIImage *image = [[UIImage alloc]initWithData:data];
if (data != nil) {
// 在主线程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
}
});
单次任务dispatch_once
在一些场景中,某个任务只被允许执行一次,比如创建单例。
// 单次任务
- (void)doOnceTask{
static NSData *data;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"some-url"]];
});
}
// 单例
+ (instancetype)sharedManager{
static WRSnippetManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [WRSnippetManager new];
});
return manager;
}
延迟执行dispatch_after
延迟执行的使用场景很多,比如显示一些反馈信息给用户,但需要过一小段时间之后隐藏,如登录成功、失败,上传任务完成等。
/**
时间单位:
秒:NSEC_PER_SEC
毫秒:NSEC_PER_MSEC
纳秒:NSEC_PER_USEC
*/
// 延迟2秒后执行
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
dispatch_after(delayTime, dispatch_get_main_queue(), ^{
[self do...]
});
dispatch_barrier
GCD的dispatch_barrier
相关API和NSOperationQueue的addBarrierBlock:
类似,都可以保证在当前加入队列的任务执行时,前面已经加入的所有任务都执行完成,但dispatch_barrier更加强大灵活。用它可以高效地实现读写问题,即单一资源的线程安全问题。
注意:使用Dispatch Barrier API时,Dispatch Queue必须是DISPATCH_QUEUE_CONCURRENT
类型的。
下面是一个多读单写实现。
@implementation GCDQueueExample{
dispatch_queue_t wrQueue;
NSMutableDictionary *userInfo;
}
- (instancetype)init{
if ((self = [super init])) {
wrQueue = dispatch_queue_create("COM.WALKER.WRQ", DISPATCH_QUEUE_CONCURRENT);
userInfo = [NSMutableDictionary dictionary];
}
return self;
}
- (void)setValue:(id)value forKey:(NSString *)key{
// 如果调用者传入的是一个NSMutableString,在返回之后如果修改key值,则可能出错
// 所以,为了避免这些问题,对key进行copy
key = [key copy];
dispatch_barrier_async(wrQueue, ^{
if (key && value) {
[self->userInfo setValue:value forKey:key];
}
});
}
- (id)valueForKey:(NSString *)key{
__block id value = nil;
dispatch_barrier_sync(wrQueue, ^{
value = [userInfo objectForKey:key];
});
return value;
}
在这个例子中,写操作是异步执行,读操作是同步执行。因为对于很多场景,只要能够按照调用者的意图写入数据就可以了,至于要不要等待并不重要;而对于读,能够立即获得数据是值得的。
dispatch_apply
利用dispatch_apply可以快速迭代,因为可以并行执行任务。
for (int i=0; i<1e6; i++) {
// ...
}
dispatch_apply(1e6, DISPATCH_APPLY_AUTO, ^(size_t x) {
// ...
});
但是呢🤔,经过测试发现:在一般任务上dispatch_apply比for循环要慢。
任务组dispatch_group
任务组dispatch_group
和任务队列dispatch_queue
的道理一样,都用来对任务进行约束,但任务组除过约束单个任务之后,还可以约束队列。也就是说,任务组dispatch_group的约束维度更高。
在复杂问题中,任务组dispatch_group是非常必要的,比如监视一组由不同队列组成的任务,在适当时机进行适当处理。
常用的方法有:
-
dispatch_group_create
,创建任务组; -
dispatch_group_async
,提交任务到特定队列和组; -
dispatch_group_notify
,在组内任务执行完成之后,通知调用者,以便执行特定任务; -
dispatch_group_wait
,同步等待已加入组内的所有任务直到完成或者超时,会阻塞当前线程; -
dispatch_group_enter
,进入任务组,相当于添加任务到特定任务组,一直到dispatch_group_leave
为止; -
dispatch_group_leave
,离开任务组,和dispatch_group_enter
配合使用,表示任务结束。
下面这个例子,通过两个类GCDTaskItem、GCDTaskScheduler来模拟任务组的使用方法。
@implementation GCDTaskItem
- (instancetype)initWithSleepSeconds:(NSInteger)seconds name:(nonnull NSString *)name queue:(nonnull dispatch_queue_t)queue{
if (self = [super init]) {
self.sleepSeconds = seconds;
self.name = name;
self.queue = queue;
}
return self;
}
- (void)start{
NSDate *start = [NSDate date];
NSLog(@"task-%@ start do task.", _name);
[NSThread sleepForTimeInterval:_sleepSeconds];
NSLog(@"---task-%@ using %.3f seconds finishing task ---", _name, [[NSDate date] timeIntervalSinceDate:start]);
}
- (void)asyncStart{
NSDate *start = [NSDate date];
NSLog(@"task-%@ start do task.", _name);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_sleepSeconds * NSEC_PER_SEC)), _queue, ^{
NSLog(@"---task-%@ using %.3f seconds finishing task ---", self.name, [[NSDate date] timeIntervalSinceDate:start]);
});
}
@end
@implementation GDCGroupTaskScheduler
- (instancetype)initWithTasks:(NSArray<GCDTaskItem *> *)tasks name:(nonnull NSString *)name{
if (self = [super init]) {
self.tasks = tasks;
self.name = name;
self.group = dispatch_group_create();
}
return self;
}
- (void)dispatchTasksWaitUntilDone{
NSDate *start = [NSDate date];
NSLog(@"group-%@ start dispatch tasks",_name);
for (GCDTaskItem *task in _tasks) {
dispatch_group_async(_group, task.queue, ^{
[task start];
});
}
// 同步【synchronously】等待当前组中的所有队列中的任务完成,会阻塞当前线程
dispatch_group_wait(_group, DISPATCH_TIME_FOREVER);
NSLog(@"group-task-%@ using %.3f seconds finishing task", _name, [[NSDate date] timeIntervalSinceDate:start]);
NSLog(@"=========================");
}
- (void)dispatchTasksUntilDoneNofityQueue:(dispatch_queue_t)queue nextTask:(GDCGroupTasksCompletionHandler)next{
NSDate *start = [NSDate date];
NSLog(@"group-%@ start dispatch tasks",_name);
for (GCDTaskItem *task in _tasks) {
dispatch_group_async(_group, task.queue, ^{
[task start];
});
}
dispatch_group_notify(_group, queue, ^{
NSLog(@"group-task-%@ using %.3f seconds finishing task", self.name, [[NSDate date] timeIntervalSinceDate:start]);
NSLog(@"=========================");
if (next) {
next();
}
});
}
@end
初始化任务:
- (void)initGroupTasks{
queue1 = dispatch_get_global_queue(0, 0);
queue2 = dispatch_get_global_queue(0, 0);
tasks1 = @[
[[GCDTaskItem alloc] initWithSleepSeconds:2 name:@"T11" queue:queue1],
[[GCDTaskItem alloc] initWithSleepSeconds:5 name:@"T12" queue:queue2]
];
tasks2 = @[
[[GCDTaskItem alloc] initWithSleepSeconds:1 name:@"T21" queue:queue1],
[[GCDTaskItem alloc] initWithSleepSeconds:3 name:@"T22" queue:queue2]
];
scheduler1 = [[GDCGroupTaskScheduler alloc] initWithTasks:tasks1 name:@"S1"];
scheduler2 = [[GDCGroupTaskScheduler alloc] initWithTasks:tasks2 name:@"S2"];
}
使用dispatch_group_wait同步等待任务完成:
- (void)performTasksWithWait{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
[self->scheduler1 dispatchTasksWaitUntilDone];
[self->scheduler2 dispatchTasksWaitUntilDone];
});
}
// 结果:
/**
2020-11-13 09:27:18.443424+0800 Snippets[16360:1149514] group-S1 start dispatch tasks
2020-11-13 09:27:18.445682+0800 Snippets[16360:1149517] task-T11 start do task.
2020-11-13 09:27:18.447914+0800 Snippets[16360:1149516] task-T12 start do task.
2020-11-13 09:27:20.452293+0800 Snippets[16360:1149517] ---task-T11 using 2.007 seconds finishing task ---
2020-11-13 09:27:23.452638+0800 Snippets[16360:1149516] ---task-T12 using 5.005 seconds finishing task ---
2020-11-13 09:27:23.453117+0800 Snippets[16360:1149514] group-task-S1 using 5.010 seconds finishing task
2020-11-13 09:27:23.453377+0800 Snippets[16360:1149514] =========================
2020-11-13 09:27:23.454756+0800 Snippets[16360:1149514] group-S2 start dispatch tasks
2020-11-13 09:27:23.454999+0800 Snippets[16360:1149516] task-T21 start do task.
2020-11-13 09:27:23.455093+0800 Snippets[16360:1149517] task-T22 start do task.
2020-11-13 09:27:24.457332+0800 Snippets[16360:1149516] ---task-T21 using 1.002 seconds finishing task ---
2020-11-13 09:27:26.457219+0800 Snippets[16360:1149517] ---task-T22 using 3.002 seconds finishing task ---
2020-11-13 09:27:26.457524+0800 Snippets[16360:1149514] group-task-S2 using 3.003 seconds finishing task
2020-11-13 09:27:26.457746+0800 Snippets[16360:1149514] =========================
*/
使用dispatch_group_notify异步等待完成通知:
- (void)performTasksWithNofity{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
[self->scheduler1 dispatchTasksUntilDoneAndNofity];
[self->scheduler2 dispatchTasksUntilDoneAndNofity];
});
}
// 结果:
/**
2020-11-13 09:41:58.804265+0800 Snippets[16462:1157254] group-S1 start dispatch tasks
2020-11-13 09:41:58.805894+0800 Snippets[16462:1157254] group-S2 start dispatch tasks
2020-11-13 09:41:58.806184+0800 Snippets[16462:1157255] task-T11 start do task.
2020-11-13 09:41:58.806658+0800 Snippets[16462:1157532] task-T12 start do task.
2020-11-13 09:41:58.807275+0800 Snippets[16462:1157254] task-T21 start do task.
2020-11-13 09:41:58.808840+0800 Snippets[16462:1157533] task-T22 start do task.
2020-11-13 09:41:59.815052+0800 Snippets[16462:1157254] ---task-T21 using 1.008 seconds finishing task ---
2020-11-13 09:42:00.812159+0800 Snippets[16462:1157255] ---task-T11 using 2.006 seconds finishing task ---
2020-11-13 09:42:01.816091+0800 Snippets[16462:1157533] ---task-T22 using 3.007 seconds finishing task ---
2020-11-13 09:42:01.816527+0800 Snippets[16462:1157182] group-task-S2 using 3.011 seconds finishing task
2020-11-13 09:42:01.816773+0800 Snippets[16462:1157182] =========================
2020-11-13 09:42:03.813934+0800 Snippets[16462:1157532] ---task-T12 using 5.007 seconds finishing task ---
2020-11-13 09:42:03.814274+0800 Snippets[16462:1157182] group-task-S1 using 5.010 seconds finishing task
2020-11-13 09:42:03.814508+0800 Snippets[16462:1157182] =========================
*/
可以看见,用任务组dispatch_group
约束来自不同队列的任务之后,程序依然可按照预期的流程执行。
详细示例见:使用dispatch_group约束任务的执行流程
信号量dispatch_semaphore
信号量适合控制一个(组)仅限于有限个用户访问的共享资源,信号量的初始值表示可同时访问的数量,或者共享资源的数量。
信号量只有两种操作方式,wait
和signal
,前者表示信号量减一,后者表示信号量加一。如果信号量为0,则需要等待,直至信号量为正方可进行后续操作。
在GCD中,信号量dispatch_semaphore
的使用方法包括:
-
dispatch_semaphore_create
,创建信号量,需要一个>=0的数初始化; -
dispatch_semaphore_wait
,信号量减一; -
dispatch_semaphore_signal
,信号量加一。
下面这个例子演示了海底捞火锅店的营业活动。
@implementation GCDSemaphoreExample
{
dispatch_semaphore_t chairs; // 表示海底捞的椅子数量
}
- (instancetype)init{
if ((self = [super init])) {
chairs = dispatch_semaphore_create(10);
}
return self;
}
- (void)startOperation{
NSLog(@"HiHotPot start operation");
__block NSTimer *timer = [NSTimer timerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
[self consumeHiHotPot];
}];
[NSRunLoop.mainRunLoop addTimer:timer forMode:NSDefaultRunLoopMode];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(20 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[timer invalidate];
NSLog(@"HiHotPot end operation");
});
}
- (void)consumeHiHotPot{
NSLog(@"start waiting for chair...");
dispatch_semaphore_wait(chairs, DISPATCH_TIME_FOREVER);
NSLog(@"starting eating... ");
NSUInteger duration = arc4random()%5;
// 一定时间之后吃完,时间随机
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"finish eating...");
dispatch_semaphore_signal(self->chairs);
});
}
@end
完整示例见:用信号量模拟海底捞的营业活动
调度块dispatch_block
调度块是根据现有的block对象,根据特定信息在堆上创建一个新的调度块对象。
我们都知道,直接用GCD创建的任务一旦完成创建,就不能取消,只能等待执行。这在有些场景中就会出现问题,而调度块就能实现取消,但也有个条件:此任务还没有被执行。
而且,它也可以结合任务组一起使用。
常用方法有:
-
dispatch_block_create
,创建,需要制定flag; -
dispatch_block_perform
,同步执行,完成之后释放资源; -
dispatch_block_wait
,等待直到block完成,或者超时;如果已完成则直接返回; -
dispatch_block_notify
,提交一个完成通知; -
dispatch_block_cancel
,取消一个调度块对象,只能在未执行之前被调用。
dispatch_source
Dispatch Source API是一组对低层次系统对象进行监控的接口,比如监视其他进程变化、内存压力、文件修改等。
需要注意的是,创建好特定类型的Dispatch Source之后,要通过dispatch_resume
或者dispatch_activate
(更推荐)进行激活,因为它们是以非活动状态创建的。
进程PROC
用Dispatch Source监控进程状态的变化,比如退出、创建子进程等。
文件系统
int const fd = open([[dirUrl path] fileSystemRepresentation], O_EVTONLY);
if (fd < 0) {
char buffer[80];
strerror_r(errno, buffer, sizeof(buffer));
NSLog(@"Unable to open \"%@\": %s (%d)", [dirUrl path], buffer, errno);
return;
}
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd,
DISPATCH_VNODE_WRITE | DISPATCH_VNODE_DELETE, DISPATCH_TARGET_QUEUE_DEFAULT);
dispatch_source_set_event_handler(source, ^(){
unsigned long const data = dispatch_source_get_data(source);
if (data & DISPATCH_VNODE_WRITE) {
NSLog(@"The directory changed.");
}
if (data & DISPATCH_VNODE_DELETE) {
NSLog(@"The directory has been deleted.");
}
});
dispatch_source_set_cancel_handler(source, ^(){
close(fd);
});
dirSource = source;
dispatch_activate(dirSource);
完整示例见:用Dispatch Source监控文件系统