——————————NSThread————————
- (void)viewDidLoad {
[superviewDidLoad];
//不要把耗时操作,比如网络请求,线程睡眠代码,加载文件,大量运算放到主线程
//不要把修改页面的代码放在子线程,不会得到及时更新
//开辟一个子线程用来处理某些事
[NSThreaddetachNewThreadSelector:@selector(NewThread)toTarget:selfwithObject:nil];
}
- (void)NewThread{
UIView * view = [[UIViewalloc]initWithFrame:CGRectMake(0,0,100,100)];
view.backgroundColor = [UIColorpurpleColor];
view.center =self.view.center;
//线程睡眠线程进入阻塞状态
[NSThreadsleepForTimeInterval:2];
//回到主线程操作UI
[selfperformSelectorOnMainThread:@selector(backMainThread:)withObject:viewwaitUntilDone:NO];
}
- (void)backMainThread:(UIView *)view{
[self.viewaddSubview:view];
}
- (void)nsthread{
/*
NSThread 创建一个线程相对来说还是比较方便的
NSThread 管理多个线程比较困难,所以不太推荐使用
苹果说了,现在推荐用GCD和NSOperation,也就是说其他的都不让(建议)用了
[NSTread currentThread] 跟踪任务所在线程,适用于NSTread,NSOperation,和GCD
使用NSThread的线程,不会自动添加autoreleasepool
*/
//iOS中隐式中创建线程的方法(NSObject的方法):
/*
//(NSObject)
//1.waitUntilDone:在主线程中运行方法,wait表示是否阻塞这个方法的调用,如果为yes则等待主线程中的运行方法结束。一般可用于在子线程中调用ui方法。此规则针对其他线程也适用。
//2. modes:关于这个参数,暂时不做讲解,下次再说,这里先说一下,如果modes参数为kCFRunLoopCommonModes的话可以结局滑动过程中图片赋值引起的页面卡顿问题,等.
//3.此方法不能够自动回收线程,如果并发数量多,会建立大量子线程。
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg
//延迟执行这里,用到runloop的东西,不太懂runloop的同学,可以去学习一下,我下次也会说到
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray
//取消线程队列中还没有执行的方法
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
*/
//NSThread各个属性的意义
/*
@property double threadPriority //线程优先级(double)0.0~1.0,默认0.5,优先级和执行顺序成正比
@property (nullable, copy) NSString *name //线程名字
@property NSUInteger stackSize //线程栈大小,默认主线程1m ,子线程512k,次属性可读写,但是写入大小必须为4k的倍数,最小为16k
@property (readonly) BOOL isMainThread // 是否是主线程
@property (readonly, getter=isExecuting) BOOL executing //是否正在执行
@property (readonly, getter=isFinished) BOOL finished //是否已经完成
@property (readonly, getter=isCancelled) BOOL cancelled //是否已经取消
*/
//NSThead类方法,作用域:当前线程
/*
+ (NSThread *)currentThread; //返回当前线程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument; //开辟一个新的线程
+ (void)sleepUntilDate:(NSDate *)date;//休眠到什么时候(具体日期)
+ (void)sleepForTimeInterval:(NSTimeInterval)ti; //休眠一段时间单位秒
+ (void)exit; //结束当前线程
+ (double)threadPriority; //返回当前线程优先级
+ (BOOL)setThreadPriority:(double)p; //设置当前线程优先级 0.0~1.0
+ (NSArray
+ (NSArray
+ (BOOL)isMainThread //返回当前线程是否是主线程
+ (NSThread *)mainThread //返回主线程
*/
//NSThead实例方法
/*
- (instancetype)init //总共五个实例方法中没有给NSThread加 selector的方法,那这个方法是干什么用的呢?目测是用来继承然后重写main方法来用的
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument //通过selector初始化
- (void)main //主方法,用于子类继承重写
- (void)start //开始线程
- (void)cancel //取消线程
*/
//NSThread的一些注意点
/*
在子类重写父类的方法中,start方法先于main方法执行;
线程中的自动释放池:
@autoreleasepool{}自动释放池
主线程中是有自动释放池的,使用gcd和nsoperation也会自动添加自动释放池,但是nsthread和nsobject不会,如果在后台线程中创建了autorelease的对象,需要使用自动释放池,否则会出现内存泄露
当自动释放池销毁时,对池中的所有对象发送release消息,清空自动释放池
当所有autorelease的对象,在出了作用域之后,会自动添加到(最近一次创建的自动释放池中)自动释放池中
*/
}
@end
——————————GCD————————————
- (void)viewDidLoad {
[superviewDidLoad];
/*
多线程相关概念
多线程编程技术的优缺点比较?
GCD中的三种队列类型
The main queue(主线程串行队列)
Global queue(全局并发队列)注意全局队列是并发队列
Custom queue (自定义队列)
Group queue (队列组)
GCD中一些系统提供的常用dispatch方法
欢迎访问作者个人博客www.dullgrass.com,更多好的文章与您分享
多线程相关概念
进程与线程
进程概念:进程是程序在计算机上的一次执行活动,打开一个app,就开启了一个进程,可包含多个线程。
线程概念:独立执行的代码段,一个线程同时间只能执行一个任务,反之多线程并发就可以在同一时间执行多个任务。
iOS程序中,主线程(又叫作UI线程)主要任务是处理UI事件,显示和刷新UI,(只有主线程有直接修改UI的能力)耗时的操作放在子线程(又叫作后台线程、异步线程)。在iOS中开子线程去处理耗时的操作,可以有效提高程序的执行效率,提高资源利用率。但是开启线程会占用一定的内存,(主线程的堆栈大小是1M,第二个线程开始都是512KB,并且该值不能通过编译器开关或线程API函数来更改)降低程序的性能。所以一般不要同时开很多线程。
线程相关
同步线程:同步线程会阻塞当前线程去执行线程内的任务,执行完之后才会反回当前线程。
异步线程:异步线程不会阻塞当前线程,会开启其他线程去执行线程内的任务。
串行队列:线程任务按先后顺序逐个执行(需要等待队列里面前面的任务执行完之后再执行新的任务)。
并发队列:多个任务按添加顺序一起开始执行(不用等待前面的任务执行完再执行新的任务),但是添加间隔往往忽略不计,所以看着像是一起执行的。
并发VS并行:并行是基于多核设备的,并行一定是并发,并发不一定是并行。
多线程中会出现的问题
Critical Section(临界代码段)
指的是不能同时被两个线程访问的代码段,比如一个变量,被并发进程访问后可能会改变变量值,造成数据污染(数据共享问题)。
Race Condition (竞态条件)
当多个线程同时访问共享的数据时,会发生争用情形,第一个线程读取改变了一个变量的值,第二个线程也读取改变了这个变量的值,两个线程同时操作了该变量,此时他们会发生竞争来看哪个线程会最后写入这个变量,最后被写入的值将会被保留下来。
Deadlock (死锁)
两个(多个)线程都要等待对方完成某个操作才能进行下一步,这时就会发生死锁。
Thread Safe(线程安全)
一段线程安全的代码(对象),可以同时被多个线程或并发的任务调度,不会产生问题,非线程安全的只能按次序被访问。
所有Mutable对象都是非线程安全的,所有Immutable对象都是线程安全的,使用Mutable对象,一定要用同步锁来同步访问(@synchronized)。
互斥锁:能够防止多线程抢夺造成的数据安全问题,但是需要消耗大量的资源
原子属性(atomic)加锁
atomic: 原子属性,为setter方法加锁,将属性以atomic的形式来声明,该属性变量就能支持互斥锁了。
nonatomic: 非原子属性,不会为setter方法加锁,声明为该属性的变量,客户端应尽量避免多线程争夺同一资源。
Context Switch (上下文切换)
当一个进程中有多个线程来回切换时,context switch用来记录执行状态,这样的进程和一般的多线程进程没有太大差别,但会产生一些额外的开销。
多线程编程技术的优缺点比较
NSThread (抽象层次:低)
优点:轻量级,简单易用,可以直接操作线程对象
缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销。
Cocoa NSOperation (抽象层次:中)
优点:不需要关心线程管理,数据同步的事情,可以把精力放在学要执行的操作上。基于GCD,是对GCD的封装,比GCD更加面向对象
缺点: NSOperation是个抽象类,使用它必须使用它的子类,可以实现它或者使用它定义好的两个子类NSInvocationOperation、NSBlockOperation.
GCD 全称Grand Center Dispatch (抽象层次:高)
优点:是 Apple开发的一个多核编程的解决方法,简单易用,效率高,速度快,基于C语言,更底层更高效,并且不是Cocoa框架的一部分,自动管理线程生命周期(创建线程、调度任务、销毁线程)。
缺点:使用GCD的场景如果很复杂,就有非常大的可能遇到死锁问题。
GCD抽象层次最高,使用也简单,因此,苹果也推荐使用GCD
GCD中的三种队列类型
GCD编程的核心就是dispatch队列,dispatch block的执行最终都会放进某个队列中去进行。
The main queue(主线程串行队列):与主线程功能相同,提交至Main queue的任务会在主线程中执行,
Main queue 可以通过dispatch_get_main_queue()来获取。
Global queue(全局并发队列):全局并发队列由整个进程共享,有高、中(默认)、低、后台四个优先级别。
Global queue 可以通过调用dispatch_get_global_queue函数来获取(可以设置优先级)
Custom queue (自定义队列):可以为串行,也可以为并发。
Custom queue 可以通过dispatch_queue_create()来获取;
Group queue (队列组):将多线程进行分组,最大的好处是可获知所有线程的完成情况。
Group queue 可以通过调用dispatch_group_create()来获取,通过dispatch_group_notify,可以直接监听组里所有线程完成情况。
gcd中相关函数的使用一般都是以dispatch开头
The main queue(主线程串行队列)
dispatch_sync 同步执行任务函数,不会开启新的线程,dispatch_async异步执行任务函数,会开启新的线程
获取主线程串行队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
主线程串行队列同步执行任务,在主线程运行时,会产生死锁
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(mainQueue,^{
NSLog("MainQueue");
});
程序一直处于等待状态,block中的代码将执行不到
主线程串行队列异步执行任务,在主线程运行,不会产生死锁。
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue,^{
NSLog("MainQueue");
});
程序正常运行,block中的代码正常运行
从子线程,异步返回主线程更新UI<这种使用方式比较多>
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
//子线程异步执行下载任务,防止主线程卡顿
NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
NSError *error;
NSString *htmlData = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
if (htmlData != nil) {
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//异步返回主线程,根据获取的数据,更新UI
dispatch_async(mainQueue, ^{
NSLog(@"根据更新UI界面");
});
} else {
NSLog(@"error when download:%@",error);
}
});
主线程串行队列由系统默认生成的,所以无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。
Global queue(全局并发队列)
耗时的操作,比如读取网络数据,IO,数据库读写等,我们会在另外一个线程中处理这些操作,然后通知主线程更新界面
获取全局并发队列
//程序默认的队列级别,一般不要修改,DISPATCH_QUEUE_PRIORITY_DEFAULT == 0
dispatch_queue_t globalQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//HIGH
dispatch_queue_t globalQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//LOW
dispatch_queue_t globalQueue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
//BACKGROUND
dispatch_queue_t globalQueue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
全局并发队列同步执行任务,在主线程执行会导致页面卡顿。
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"current task");
dispatch_sync(globalQueue, ^{
sleep(2.0);
NSLog(@"sleep 2.0s");
});
NSLog(@"next task");
控制台输出如下:
2015-11-18 15:51:45.550 Whisper[33152:345023] current task
2015-11-18 15:51:47.552 Whisper[33152:345023] sleep 2.0s
2015-11-18 15:51:47.552 Whisper[33152:345023] next task
2s钟之后,才会执行block代码段下面的代码。
全局并发队列异步执行任务,在主线程运行,会开启新的子线程去执行任务,页面不会卡顿。
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"current task");
dispatch_async(globalQueue, ^{
sleep(2.0);
NSLog(@"sleep 2.0s");
});
NSLog(@"next task");
控制台输出如下:
2015-11-18 15:50:14.999 Whisper[33073:343781] current task
2015-11-18 15:50:15.000 Whisper[33073:343781] next task
2015-11-18 15:50:17.004 Whisper[33073:343841] sleep 2.0s
主线程不用等待2s钟,继续执行block代码段后面的代码。
多个全局并发队列,异步执行任务。
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"current task");
dispatch_async(globalQueue, ^{
NSLog(@"最先加入全局并发队列");
});
dispatch_async(globalQueue, ^{
NSLog(@"次加入全局并发队列");
});
NSLog(@"next task");
控制台输出如下:
2015-11-18 16:54:52.202 Whisper[39827:403208] current task
2015-11-18 16:54:52.203 Whisper[39827:403208] next task
2015-11-18 16:54:52.205 Whisper[39827:403309] 最先加入全局并发队列
2015-11-18 16:54:52.205 Whisper[39827:403291] 次加入全局并发队列
异步线程的执行顺序是不确定的。几乎同步开始执行
全局并发队列由系统默认生成的,所以无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。
Custom queue (自定义队列)
自定义串行队列
获取自定义串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.dullgrass.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"%s",dispatch_queue_get_label(conCurrentQueue)) ;
控制台输出:
2015-11-19 11:05:34.469 Whisper[1223:42960] com.dullgrass.serialQueue
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)函数中第一个参数是给这个queue起的标识,这个在调试的可以看到是哪个队列在执行,或者在crash日志中,也能做为提示。第二个是需要创建的队列类型,是串行的还是并发的
自定义串行队列同步执行任务
dispatch_queue_t serialQueue = dispatch_queue_create("com.dullgrass.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"current task");
dispatch_sync(serialQueue, ^{
NSLog(@"最先加入自定义串行队列");
sleep(2);
});
dispatch_sync(serialQueue, ^{
NSLog(@"次加入自定义串行队列");
});
NSLog(@"next task");
控制台输出:
2015-11-18 17:09:40.025 Whisper[40241:416296] current task
2015-11-18 17:09:40.027 Whisper[40241:416296] 最先加入自定义串行队列
2015-11-18 17:09:43.027 Whisper[40241:416296] 次加入自定义串行队列
2015-11-18 17:09:43.027 Whisper[40241:416296] next task
当前线程等待串行队列中的子线程执行完成之后再执行,串行队列中先进来的子线程先执行任务,执行完成后,再执行队列中后面的任务。
自定义串行队列嵌套执行同步任务,产生死锁
dispatch_queue_t serialQueue = dispatch_queue_create("com.dullgrass.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{ //该代码段后面的代码都不会执行,程序被锁定在这里
NSLog(@"会执行的代码");
dispatch_sync(serialQueue, ^{
NSLog(@"代码不执行");
});
});
异步执行串行队列,嵌套同步执行串行队列,同步执行的串行队列中的任务将不会被执行,其他程序正常执行
dispatch_queue_t serialQueue = dispatch_queue_create("com.dullgrass.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
NSLog(@"会执行的代码");
dispatch_sync(serialQueue, ^{
NSLog(@"代码不执行");
});
});
注意不要嵌套使用同步执行的串行队列任务
自定义并发队列
获取自定义并发队列
dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.dullgrass.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
自定义并发队列执行同步任务
dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.dullgrass.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"current task");
dispatch_sync(conCurrentQueue, ^{
NSLog(@"先加入队列");
});
dispatch_sync(conCurrentQueue, ^{
NSLog(@"次加入队列");
});
NSLog(@"next task");
控制台输出如下:
2015-11-19 10:36:23.259 Whisper[827:20596] current task
2015-11-19 10:36:23.261 Whisper[827:20596] 先加入队列
2015-11-19 10:36:23.261 Whisper[827:20596] 次加入队列
2015-11-19 10:36:23.261 Whisper[827:20596] next task
自定义并发队列嵌套执行同步任务(不会产生死锁,程序正常运行)
dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.dullgrass.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"current task");
dispatch_sync(conCurrentQueue, ^{
NSLog(@"先加入队列");
dispatch_sync(conCurrentQueue, ^{
NSLog(@"次加入队列");
});
});
NSLog(@"next task");
控制台输出如下:
2015-11-19 10:39:21.301 Whisper[898:22273] current task
2015-11-19 10:39:21.303 Whisper[898:22273] 先加入队列
2015-11-19 10:39:21.303 Whisper[898:22273] 次加入队列
2015-11-19 10:39:21.303 Whisper[898:22273] next task
自定义并发队列执行异步任务
dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.dullgrass.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"current task");
dispatch_async(conCurrentQueue, ^{
NSLog(@"先加入队列");
});
dispatch_async(conCurrentQueue, ^{
NSLog(@"次加入队列");
});
NSLog(@"next task");
控制台输出如下:
2015-11-19 10:45:22.290 Whisper[1050:26445] current task
2015-11-19 10:45:22.290 Whisper[1050:26445] next task
2015-11-19 10:45:22.290 Whisper[1050:26505] 次加入队列
2015-11-19 10:45:22.290 Whisper[1050:26500] 先加入队列
异步执行任务,开启新的子线程,不影响当前线程任务的执行,并发队列中的任务,几乎是同步执行的,输出顺序不确定
Group queue (队列组)
当遇到需要执行多个线程并发执行,然后等多个线程都结束之后,再汇总执行结果时可以用group queue
使用场景:同时下载多个图片,所有图片下载完成之后去更新UI(需要回到主线程)或者去处理其他任务(可以是其他线程队列)。
原理:使用函数dispatch_group_create创建dispatch group,然后使用函数dispatch_group_async来将要执行的block任务提交到一个dispatch queue。同时将他们添加到一个组,等要执行的block任务全部执行完成之后,使用dispatch_group_notify函数接收完成时的消息。
使用示例:
dispatch_queue_t conCurrentGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_group_t groupQueue = dispatch_group_create();
NSLog(@"current task");
dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{
NSLog(@"并行任务1");
});
dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{
NSLog(@"并行任务2");
});
dispatch_group_notify(groupQueue, mainQueue, ^{
NSLog(@"groupQueue中的任务都执行完成,回到主线程更新UI");
});
NSLog(@"next task");
控制台输出:
2015-11-19 13:47:55.117 Whisper[1645:97116] current task
2015-11-19 13:47:55.117 Whisper[1645:97116] next task
2015-11-19 13:47:55.119 Whisper[1645:97178] 并行任务1
2015-11-19 13:47:55.119 Whisper[1645:97227] 并行任务2
2015-11-19 13:47:55.171 Whisper[1645:97116] groupQueue中的任务都执行完成,回到主线程更新UI
在当前线程阻塞的同步等待dispatch_group_wait
dispatch_group_t groupQueue = dispatch_group_create();
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);
dispatch_queue_t conCurrentGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"current task");
dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{
long isExecuteOver = dispatch_group_wait(groupQueue, delayTime);
if (isExecuteOver) {
NSLog(@"wait over");
} else {
NSLog(@"not over");
}
NSLog(@"并行任务1");
});
dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{
NSLog(@"并行任务2");
});
控制台输出如下:
2015-11-19 14:37:29.514 Whisper[2426:126683] current task
2015-11-19 14:37:29.518 Whisper[2426:126791] 并行任务2
2015-11-19 14:37:39.515 Whisper[2426:126733] wait over
2015-11-19 14:37:39.516 Whisper[2426:126733] 并行任务1
dispatch_time(dispatch_time_t when, int64_t delta);
参数注释:
第一个参数一般是DISPATCH_TIME_NOW,表示从现在开始
第二个参数是延时的具体时间
延时1秒可以写成如下几种:
NSEC_PER_SEC----每秒有多少纳秒
dispatch_time(DISPATCH_TIME_NOW, 1*NSEC_PER_SEC);
USEC_PER_SEC----每秒有多少毫秒(注意是指在纳秒的基础上)
dispatch_time(DISPATCH_TIME_NOW, 1000*USEC_PER_SEC); //SEC---毫秒
NSEC_PER_USEC----每毫秒有多少纳秒。
dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC*NSEC_PER_USEC);SEC---纳秒
GCD中一些系统提供的常用dispatch方法
dispatch_after延时添加到队列
使用示例:
dispatch_time_t delayTime3 = dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC);
dispatch_time_t delayTime2 = dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
NSLog(@"current task");
dispatch_after(delayTime3, mainQueue, ^{
NSLog(@"3秒之后添加到队列");
});
dispatch_after(delayTime2, mainQueue, ^{
NSLog(@"2秒之后添加到队列");
});
NSLog(@"next task");
控制台输出如下:
2015-11-19 15:50:19.369 Whisper[2725:172593] current task
2015-11-19 15:50:19.370 Whisper[2725:172593] next task
2015-11-19 15:50:21.369 Whisper[2725:172593] 2秒之后添加到队列
2015-11-19 15:50:22.654 Whisper[2725:172593] 3秒之后添加到队列
dispatch_after只是延时提交block,并不是延时后立即执行,并不能做到精确控制,需要精确控制的朋友慎用哦
dispatch_apply在给定的队列上多次执行某一任务,在主线程直接调用会阻塞主线程去执行block中的任务。
dispatch_apply函数的功能:把一项任务提交到队列中多次执行,队列可以是串行也可以是并行,dispatch_apply不会立刻返回,在执行完block中的任务后才会返回,是同步执行的函数。
dispatch_apply正确使用方法:为了不阻塞主线程,一般把dispatch_apply放在异步队列中调用,然后执行完成后通知主线程
使用示例:
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
NSLog(@"current task");
dispatch_async(globalQueue, ^{
dispatch_queue_t applyQueue = dispatch_get_global_queue(0, 0);
//第一个参数,3--block执行的次数
//第二个参数,applyQueue--block任务提交到的队列
//第三个参数,block--需要重复执行的任务
dispatch_apply(3, applyQueue, ^(size_t index) {
NSLog(@"current index %@",@(index));
sleep(1);
});
NSLog(@"dispatch_apply 执行完成");
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
NSLog(@"回到主线程更新UI");
});
});
NSLog(@"next task");
控制台输出如下:
2015-11-19 16:24:45.015 Whisper[4034:202269] current task
2015-11-19 16:24:45.016 Whisper[4034:202269] next task
2015-11-19 16:24:45.016 Whisper[4034:202347] current index 0
2015-11-19 16:24:45.016 Whisper[4034:202344] current index 1
2015-11-19 16:24:45.016 Whisper[4034:202345] current index 2
2015-11-19 16:24:46.021 Whisper[4034:202347] dispatch_apply 执行完成
2015-11-19 16:24:46.021 Whisper[4034:202269] 回到主线程更新UI
嵌套使用dispatch_apply会导致死锁。
dispatch_once保证在app运行期间,block中的代码只执行一次
经典使用场景---单例
单例对象ShareManager的定义:
ShareManager的.h文件
#import
@interface ShareManager : NSObject
@property (nonatomic, copy) NSString *someProperty;
+ (ShareManager *)shareManager;
+ (ShareManager *)sharedManager;
@end
ShareManager的.m文件
#import "ShareManager.h"
@implementation ShareManager
static ShareManager *sharedManager = nil;
//GCD实现单例功能
+ (ShareManager *)shareManager
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[self alloc] init];
});
return sharedManager;
}
//在ARC下,非GCD,实现单例功能
+ (ShareManager *)sharedManager
{
@synchronized(self) {
if (!sharedManager) {
sharedManager = [[self alloc] init];
}
}
return sharedManager;
}
- (instancetype)init{
self = [super init];
if (self) {
_someProperty =@"Default Property Value";
}
return self;
}
@end
ShareManager的使用
#import "ShareManager.h"
在需要使用的函数中,直接调用下面的方法
ShareManager *share = [ShareManager sharedManager];
NSLog(@"share is %@",share.someProperty);
dispatch_barrier_async 栅栏的作用
功能:是在并行队列中,等待在dispatch_barrier_async之前加入的队列全部执行完成之后(这些任务是并发执行的)再执行dispatch_barrier_async中的任务,dispatch_barrier_async中的任务执行完成之后,再去执行在dispatch_barrier_async之后加入到队列中的任务(这些任务是并发执行的)。
使用示例:
dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.dullgrass.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(conCurrentQueue, ^{
NSLog(@"dispatch 1");
});
dispatch_async(conCurrentQueue, ^{
NSLog(@"dispatch 2");
});
dispatch_barrier_async(conCurrentQueue, ^{
NSLog(@"dispatch barrier");
});
dispatch_async(conCurrentQueue, ^{
NSLog(@"dispatch 3");
});
dispatch_async(conCurrentQueue, ^{
NSLog(@"dispatch 4");
});
控制台输出如下:
2015-11-19 18:12:34.125 Whisper[22633:297257] dispatch 1
2015-11-19 18:12:34.125 Whisper[22633:297258] dispatch 2
2015-11-19 18:12:34.126 Whisper[22633:297258] dispatch barrier
2015-11-19 18:12:34.127 Whisper[22633:297258] dispatch 3
2015-11-19 18:12:34.127 Whisper[22633:297257] dispatch 4
*/
/////////////////////////////////////////////////////////////////////////////////
[selfzhalanhanshu];
}
#pragma mark -- 获取主线程串行队列(主队列)
- (void)huoquzhuduilie{
dispatch_queue_t mainqueue =dispatch_get_main_queue();
NSLog(@"主队列 = %@",mainqueue);
}
#pragma mark -- 在同步函数中的主队列中执行任务会产生死锁
- (void)sisuo{
//两个(多个)线程都要等待对方完成某个操作才能进行下一步,这时就会发生死锁。
dispatch_queue_t mainqueue =dispatch_get_main_queue();
dispatch_sync(mainqueue, ^{
NSLog(@"死锁");
});
}
#pragma mark -- 在异步函数中主队列中执行任务不会产生死锁
- (void)buchanshengsisuo{
dispatch_queue_t mainqueue =dispatch_get_main_queue();
dispatch_async(mainqueue, ^{
NSLog(@"不会产生死锁");
});
}
#pragma mark -- 回到主线程更新UI
- (void)huizhuxianchenggengxinUI{
dispatch_queue_t glableaqueue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_async(glableaqueue, ^{
NSURL * url = [NSURLURLWithString:@"https://www.baidu.com"];
NSError * error;
NSString * htmlData = [NSStringstringWithContentsOfURL:urlencoding:NSUTF8StringEncodingerror:&error];
if (htmlData !=nil) {
//获取主队列主线程串行队列由系统默认生成的,所以无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。
dispatch_queue_t mainqueue =dispatch_get_main_queue();
//异步回到主线程
dispatch_async(mainqueue, ^{
NSLog(@"回到主线程刷新UI%@",htmlData);
});
}else{
NSLog(@"没有下载下来数据%@",error);
}
});
}
#pragma mark -- 全局并发队列
- (void)quanjubingfaduilia{
#if 0
#endif
//程序默认的队列级别,一般不要修改,DISPATCH_QUEUE_PRIORITY_DEFAULT == 0
/*
DISPATCH_QUEUE_PRIORITY_HIGH 高
DISPATCH_QUEUE_PRIORITY_DEFAULT 默认
DISPATCH_QUEUE_PRIORITY_LOW 低
DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台
*/
dispatch_queue_t glalblequeue1 =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_queue_t glalblequeue2 =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
dispatch_queue_t glalblequeue3 =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0);
dispatch_queue_t glalblequeue4 =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
//在同步函数中执行全局并发队列不会开启子线程
dispatch_sync(glalblequeue1, ^{
NSLog(@"%@",[NSThreadcurrentThread]);
});
//在异步函数中执行全局并发队列会开启子线程
dispatch_async(glalblequeue1, ^{
NSLog(@"%@",[NSThreadcurrentThread]);
});
//异步函数在全局并发队列中添加多个任务
dispatch_async(glalblequeue1, ^{
NSLog(@"%@",[NSThreadcurrentThread]);
});
dispatch_async(glalblequeue1, ^{
NSLog(@"%@",[NSThreadcurrentThread]);
});
//自定义队列
//1自定义串行队列
//函数中第一个参数是给这个queue起的标识,这个在调试的可以看到是哪个队列在执行,或者在crash日志中,也能做为提示。第二个是需要创建的队列类型,是串行的还是并发的
dispatch_queue_t custeme =dispatch_queue_create("custeme1",DISPATCH_QUEUE_SERIAL);
NSLog(@"%@",custeme);
//同步函数中执行串行队列中的任务不会开启线程
dispatch_sync(custeme, ^{
NSLog(@"同步函数中执行串行队列中的任务 %@",[NSThreadcurrentThread]);
});
//异步函数中执行串行队列中的任务会开启子线程
dispatch_async(custeme, ^{
NSLog(@"异步函数中执行串行队列中的任务 %@",[NSThreadcurrentThread]);
});
//同步函数中将多个任务添加到串行队列中只会开启一条子线程
dispatch_sync(custeme, ^{
NSLog(@"同步函数中将多个任务添加到串行队列中 %@",[NSThreadcurrentThread]);
});
dispatch_sync(custeme, ^{
NSLog(@"同步函数中将多个任务添加到串行队列中 %@",[NSThreadcurrentThread]);
});
//异步函数中将多个任务添加到串行对列中只会开启一条子线程
dispatch_async(custeme, ^{
NSLog(@"异步函数中将多个任务添加到子线程中 %@",[NSThreadcurrentThread]);
});
dispatch_async(custeme, ^{
NSLog(@"异步函数中将多个任务添加到子线程中 %@",[NSThreadcurrentThread]);
});
//自定义串行队列嵌套执行同步任务,产生死锁
dispatch_queue_t serialQueue1 =dispatch_queue_create("com.dullgrass.serialQueue",DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue1, ^{ //该代码段后面的代码都不会执行,程序被锁定在这里
NSLog(@"会执行的代码");
// dispatch_sync(serialQueue1, ^{
// NSLog(@"代码不执行");
// });
});
//异步执行串行队列,嵌套同步执行串行队列,同步执行的串行队列中的任务将不会被执行,其他程序正常执行
dispatch_queue_t serialQueue2 =dispatch_queue_create("com.dullgrass.serialQueue",DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue2, ^{
NSLog(@"会执行的代码");
// dispatch_sync(serialQueue2, ^{
// NSLog(@"代码不执行");
// });
});
//2自定义并发队列
dispatch_queue_t bingfaQueue =dispatch_queue_create("bingfaqueue",DISPATCH_QUEUE_CONCURRENT);
NSLog(@"%@",bingfaQueue);
//在同步函数中加入并发对列执行多个任务不会开启子线程
dispatch_sync(bingfaQueue, ^{
NSLog(@"在同步函数中加入并发对列执行任务 %@",[NSThreadcurrentThread]);
});
dispatch_sync(bingfaQueue, ^{
NSLog(@"在同步函数中加入并发对列执行任务 %@",[NSThreadcurrentThread]);
});
//在异步函数中加入并发队列执行对个任务会开启多条子线程
dispatch_async(bingfaQueue, ^{
NSLog(@"在异步函数中加入并发队列执行对个任务 %@",[NSThreadcurrentThread]);
});
dispatch_async(bingfaQueue, ^{
NSLog(@"在异步函数中加入并发队列执行对个任务 %@",[NSThreadcurrentThread]);
});
//将并发对列加入到同步函数和异步函数中嵌套执行
dispatch_async(bingfaQueue, ^{
NSLog(@"将并发对列加入到同步函数和异步函数中嵌套执行 %@",[NSThreadcurrentThread]);
dispatch_async(bingfaQueue, ^{
NSLog(@"将并发对列加入到同步函数和异步函数中嵌套执行 %@",[NSThreadcurrentThread]);
});
});
dispatch_async(bingfaQueue, ^{
NSLog(@"将并发对列加入到同步函数和异步函数中嵌套执行 %@",[NSThreadcurrentThread]);
dispatch_sync(bingfaQueue, ^{
NSLog(@"将并发对列加入到同步函数和异步函数中嵌套执行 %@",[NSThreadcurrentThread]);
});
});
dispatch_sync(bingfaQueue, ^{
NSLog(@"将并发对列加入到同步函数和异步函数中嵌套执行 %@",[NSThreadcurrentThread]);
dispatch_async(bingfaQueue, ^{
NSLog(@"将并发对列加入到同步函数和异步函数中嵌套执行 %@",[NSThreadcurrentThread]);
});
});
}
- (void)queueGroup{
//队列组
/*
使用场景:同时下载多个图片,所有图片下载完成之后去更新UI(需要回到主线程)或者去处理其他任务(可以是其他线程队列)。
原理:使用函数dispatch_group_create创建dispatch group,然后使用函数dispatch_group_async来将要执行的block任务提交到一个dispatch queue。同时将他们添加到一个组,等要执行的block任务全部执行完成之后,使用dispatch_group_notify函数接收完成时的消息。
*/
dispatch_queue_t galaobloeququ =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_queue_t mainququqe =dispatch_get_main_queue();
dispatch_group_t groupququ =dispatch_group_create();
//对列组只有异步队列组队列组中的任务会开启多条线程执行
dispatch_group_async(groupququ, galaobloeququ, ^{
//NSLog(@"队列组中第一个任务 - %@",[NSThread currentThread]);
});
dispatch_group_async(groupququ, galaobloeququ, ^{
//NSLog(@"队列组中第二个任务 - %@",[NSThread currentThread]);
});
//以上线程中的任务执行完发出通知
dispatch_group_notify(groupququ, mainququqe, ^{
//NSLog(@"回到主队列 - %@",[NSThread currentThread]);
});
}
#pragma mark -- 线程等待(延时)
- (void)xianchengdengdai{
dispatch_queue_t galaobloeququ =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_group_t groupququ =dispatch_group_create();
//队列组中的线程等待延时
/*
第一个参数一般是DISPATCH_TIME_NOW,表示从现在开始
第二个参数是延时的具体时间
延时1秒可以写成如下几种:
NSEC_PER_SEC----每秒有多少纳秒
dispatch_time(DISPATCH_TIME_NOW, 1*NSEC_PER_SEC);
USEC_PER_SEC----每秒有多少毫秒(注意是指在纳秒的基础上)
dispatch_time(DISPATCH_TIME_NOW, 1000*USEC_PER_SEC); //SEC---毫秒
NSEC_PER_USEC----每毫秒有多少纳秒。
dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC*NSEC_PER_USEC);SEC---纳秒
*/
dispatch_time_t waitg =dispatch_time(DISPATCH_TIME_NOW,10 *NSEC_PER_SEC);
dispatch_group_async(groupququ, galaobloeququ, ^{
long ccc =dispatch_group_wait(groupququ, waitg);
if (ccc) {
NSLog(@"队列组中的线程等待1 - %@%ld",[NSThreadcurrentThread],ccc);
}else{
NSLog(@"队列组中的线程等待2 - %@%ld",[NSThreadcurrentThread],ccc);
}
});
dispatch_group_async(groupququ, galaobloeququ, ^{
NSLog(@"队列组中的线程等待3 - %@",[NSThreadcurrentThread]);
});
}
#pragma mark -- //将任务延时添加到队列
- (void)jiangrenwuyanshitianjiaduilie{
dispatch_time_t time1 =dispatch_time(DISPATCH_TIME_NOW,5 *NSEC_PER_SEC);
dispatch_time_t time2 =dispatch_time(DISPATCH_TIME_NOW,10 * NSEC_PER_SEC);
dispatch_queue_t mainquequ =dispatch_get_main_queue();
dispatch_after(time1, mainquequ, ^{
NSLog(@" %@ time1 - %llu",[NSThreadcurrentThread],time1);
});
dispatch_after(time2, mainquequ, ^{
NSLog(@" %@ time2 - %llu",[NSThreadcurrentThread],time2);
});
}
#pragma mark -- 在给定的队列上多次执行某任务
- (void)duocizhixing{
/*
dispatch_apply在给定的队列上多次执行某一任务,在主线程直接调用会阻塞主线程去执行block中的任务。
dispatch_apply函数的功能:把一项任务提交到队列中多次执行,队列可以是串行也可以是并行,dispatch_apply不会立刻返回,在执行完block中的任务后才会返回,是同步执行的函数。
dispatch_apply正确使用方法:为了不阻塞主线程,一般把dispatch_apply放在异步队列中调用,然后执行完成后通知主线程
*/
dispatch_queue_t glaobleququ =dispatch_get_global_queue(0,0);
dispatch_async(glaobleququ, ^{
NSLog(@"1%@",[NSThreadcurrentThread]);
dispatch_queue_t deplayququ =dispatch_get_global_queue(0,0);
/*
//第一个参数,3--block执行的总次数
//第二个参数,applyQueue--block任务提交到的队列
//第三个参数,block--需要重复执行的任务
// t 是跟踪任务的执行次数
*/
dispatch_apply(3, deplayququ, ^(size_t t) {
NSLog(@"2%@%zu",[NSThreadcurrentThread],t);
});
});
dispatch_queue_t mainququ =dispatch_get_main_queue();
dispatch_async(mainququ, ^{
NSLog(@"回到主线程 - %@",[NSThreadcurrentThread]);
});
//嵌套使用dispatch_apply会导致死锁。
}
#pragma mark -- 只执行一次
- (void)zhizhixingyici{
//dispatch_once保证在app运行期间,block中的代码只执行一次
//经典使用场景---单例
staticdispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//code to be executed once
//此处代码只会执行一次
});
}
#pragma mark -- 栅栏函数
- (void)zhalanhanshu{
/*
dispatch_barrier_async 栅栏的作用
功能:是在并行队列中,等待在dispatch_barrier_async之前加入的队列全部执行完成之后(这些任务是并发执行的)再执行dispatch_barrier_async中的任务,dispatch_barrier_async中的任务执行完成之后,再去执行在dispatch_barrier_async之后加入到队列中的任务(这些任务是并发执行的)
*/
dispatch_queue_t zidingyibingfaququ =dispatch_queue_create("bingfaququ",DISPATCH_QUEUE_CONCURRENT);
dispatch_async(zidingyibingfaququ, ^{
NSLog(@"1 %@",[NSThreadcurrentThread]);
});
dispatch_async(zidingyibingfaququ, ^{
NSLog(@"2 %@",[NSThreadcurrentThread]);
});
// //栅栏函数异步
// dispatch_barrier_async(zidingyibingfaququ, ^{
// NSLog(@"3 %@",[NSThread currentThread]);
// });
// //栅栏函数同步
// dispatch_barrier_sync(zidingyibingfaququ, ^{
// NSLog(@"3 %@",[NSThread currentThread]);
// });
dispatch_async(zidingyibingfaququ, ^{
NSLog(@"4 %@",[NSThreadcurrentThread]);
});
dispatch_async(zidingyibingfaququ, ^{
NSLog(@"5 %@",[NSThreadcurrentThread]);
});
}
@end
——————————nsoperation——————————
- (void)viewDidLoad {
[superviewDidLoad];
/*
NSOperation 是对GCD进行了一层面向对象的封装,底层是GCD
*/
[selfnsoeration];
}
- (void)nsoeration{
//基本属性:
/*
@property (nullable, copy) NSString *name //该操作的名称
@property (readonly, getter=isCancelled) BOOL cancelled; // 该操作是否已经取消
@property (readonly, getter=isExecuting) BOOL executing; //该操作是否正在执行
@property (readonly, getter=isFinished) BOOL finished; //该操作是否已经完成
@property (readonly, getter=isConcurrent) BOOL concurrent; //该操作是否是并行操作
@property (readonly, getter=isAsynchronous) BOOL asynchronous //该操作是否是异步的
@property (readonly, copy) NSArray
@property NSOperationQueuePriority queuePriority; //该操作的优先级
@property (nullable, copy) void (^completionBlock)(void) //该操作的完成回调block
@property double threadPriority // 该操作的优先级
*/
//实例方法:
/*
- (void)start; //开始方法,被加入队列或手动开始的时候会被调用
- (void)main; // 队列的主方法,start后执行,子类应重写此方法相比于start方法
- (void)cancel; // 取消操作
- (void)addDependency:(NSOperation *)op; //添加依赖,依赖只是设置先后执行的顺序关系,可以跨队列依赖,不可以循环依赖。添加依赖以后会顺序执行任务,但是不一定开一个线程,可能会开多个线程,但是不会太多。
- (void)removeDependency:(NSOperation *)op; //移除依赖关系
- (void)waitUntilFinished //
*/
//NSOperation的子类 NSInvocationOperation
//属性:
/*@property (readonly, retain) NSInvocation *invocation; //此队列的NSInvocation参数,注意此参数只读
@property (nullable, readonly, retain) id result; //
*/
//实例方法:
/*
- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;//通过@selector初始化
- (instancetype)initWithInvocation:(NSInvocation *)inv //通过NSInvocation初始化
*/
//一些注意点:
/*
调用方式,可以调用start方法,也可以添加到队列调用操作但是:调用start和添加到队列不可以同时使用
*/
/*
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(test1)
object:@"Invocation"];
//添加到队列方式启动
NSOperationQueue *queue = nil;
[queue addOperation:op];
//调用start方式启动
[op start];
//调用start开启任务,会在当前线程开启任务,如果几个操作一起调用start则在当前线程串行完成任务
//把操作放到队列queue中开启任务会在其他线程去执行任务
*/
//NSOperation的子类 NSInvocationOperation
//属性:
/*
@property (readonly, copy) NSArray
*/
//类方法:
/*
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;//通过block初始化操作,该block没有参数没有返回值,
*/
//实例方法:
/*
- (void)addExecutionBlock:(void (^)(void))block;//添加block,该block没有参数没有返回值
*/
//一些注意点:
/*
关于循环引用:
(前者)我们平时用的适合基本类和队列和操作的关系为 self -(持有)->queue -(持有)->block -(持有)->self这种关系不会造成循环引用
(后者)self-(持有)->block -(持有)->self这种会造成循环引用
下面分别解释两者区别,以下就称之为前者和后者:前者为什么不会造成循环引用?因为self持有queue ,然后queue持有block ,因为queue执行每个block都是一个线程,线程是一条直线,这个线程执行完了这个block也就随之消失了,所以这时候block所引用的self也会随之消失.当queue里的任务执行完了,那持有关系就会变成 self 持有queue而已,后面就没有了.然而后者.self持有block ,block持有self,任务执行完后block还在,那么它引用的self也还在,这时候就造成了循环引用,但是如果给前者的线程都加上runloop(也就是把线程的线性改为环性,也就是不让线程执行完任务就死掉。主线程系统默认创建runloop,子线程默认不创建)那么前者也会造成循环引用.
(1).单纯在操作中使用(也就是操作和self之前没有引用关系)self不会造成循环引用
(2).如果self对象持有操作对象的引用,同时操作对象中又直接访问了self时会造成循环引用.
(3).只有self直接强引用block才会出现循环引用.
(4).block 的管理以及线程的创建和销毁是由队列负责的,直接再block中使用self没有关系.
(5).weakSelf 也不是可以在任何block中都能使用,详情去讯息runloop.
*/
//NSOperationQueue
//属性:
/*
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations; //当前队列的所有操作
@property (readonly) NSUInteger operationCount;// 当前队列的所有操作的数量
@property NSInteger maxConcurrentOperationCount; //设置最大并发量
@property (getter=isSuspended) BOOL suspended; //挂起任务和开始任务,挂起的队列不会影响已执行中和执行完的任务,队列暂停再添加任务也不会自动执行任务了
@property (nullable, copy) NSString *name ;//队列的名字(不只是操作,队列也可以有名字)
@property (nullable, assign // actually retain ) dispatch_queue_t underlyingQueue
*/
//实例方法:
/*
- (void)addOperation:(NSOperation *)op; //添加操作
- (void)addOperations:(NSArray
- (void)addOperationWithBlock:(void (^)(void))block;//添加操作块
- (void)cancelAllOperations; //取消所有操作,已经执行的不会被取消,这时候会设置所有子操作对象的isCanceled为yes,自己可以在子类操作里代码设置操作的取消,不会影响挂起的状态
- (void)waitUntilAllOperationsAreFinished; //暂时不知道这个方法的作用
*/
//类方法
/*
+ (nullable NSOperationQueue *)currentQueue;//返回当前线程所在的队列
+ (NSOperationQueue *)mainQueue;//返回主队列
*/
//一些注意点:
/*
所有添加到非主队列执行的操作都将在非主线程执行
自定义NSOperation:
原理:重写start或 main方法,main方法由系统自动调用(注意 main方法内一定要用 @autoreleasepool)
自定义操作通过start开始会在当前线程执行操作,通过添加到队列的线程会在其他线程执行
父类的 setCompletionBlock属性设置以后会被系统自动调用,但是调用的回调block不会是主线程也不会是当前线程而是其他线程(所以如果要用系统的回掉方法一定要手动添加在主线程回调的方法)这个block不可以传递参数
关于自定义操作的取消,队列可以取消全部操作,操作也可以取消自己,但是只可以取消还没有开始的操作,如果操作已经开始了就不能取消了,因为取消的原理就是判断操作的属性 isCanceled ,操作在执行时会判断这个值,没有开始的操作判断操作被取消了就不会执行了,但是对于已经执行了的操作只能手动代码改,一般情况下会在自定义操作的main方法里和合适地方(一般都是回调方法前面)判断isCanceled的值,即使是操作已经执行完了,在这时候也可以回调失败方法或者是直接不回调造成正在执行的操作也被取消了的假象.
*/
/*
关于NSOperation的子类在NSOperationQueue中执行完无法释放的问题
*/
}
——————————————多线程——————————
- (void)viewDidLoad {
[superviewDidLoad];
/*****************************多线程*********************/
/*
一、概述
在iOS中每个进程启动后都会建立一个主线程(UI线程),这个线程是其他线程的父线程。由于在iOS中除了主线程,其他子线程是独立于Cocoa Touch的,所以只有主线程可以更新UI界面。iOS中多线程使用并不复杂,关键是如何控制好各个线程的执行顺序、处理好资源竞争问题。
多线程的实现有以下几种方式:
NSThread:
(1)使用NSThread对象建立一个线程非常方便
(2)但是!要使用NSThread管理多个线程非常困难,不推荐使用
(3)技巧!使用[NSThread currentThread]获得任务所在线程,适用于这三种技术
(4)使线程休眠3秒:[NSThread sleepForTimeInterval:0.3f];
GCD —— Grand Central Dispatch:
(1)是基于C语言的底层API
(2)用Block定义任务,使用起来非常灵活便捷
(3)提供了更多的控制能力以及操作队列中所不能使用的底层函数
NSOperation/NSOperationQueue:
(1)是使用GCD实现的一套Objective-C的API
(2)是面向对象的线程技术
(3)提供了一些在GCD中不容易实现的特性,如:限制最大并发数量、操作之间的依赖关系
二、线程与进程
1、进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,每一个进程都有自己独立的虚拟内存空间。
简单来说,进程是指在系统中正在运行的一个应用程序,每一个程序都是一个进程,并且进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。
2、线程
线程,是程序执行流的最小单元线程是程序中一个单一的顺序控制流程。是进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。
简单来说,1个进程要想执行任务,必须得有线程。
线程中任务的执行是串行的,要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务,也就是说,在同一时间内,1个线程只能执行1个任务,由此可以理解线程是进程中的1条执行路径。
一个进程中至少包含一条线程,即主线程,创建线程的目的就是为了开启一条新的执行路径,运行指定的代码,与主线程中的代码实现同时运行。
3、多线程
多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。
原理:
同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)
多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)
如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
注意:多线程并发,并不是cpu在同一时刻同时执行多个任务,只是CPU调度足够快,造成的假象。
优点:
能适当提高程序的执行效率
能适当提高资源利用率(CPU、内存利用率)
缺点:
开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
线程越多,CPU在调度线程上的开销就越大
图片 1
主线程栈区的1M,非常非常宝贵。一个进程,至少有一个线程(主线程),不能杀掉一个线程!但是可以暂停、休眠。
三、NSThread的使用
1、线程的创建
NSThread创建线程有如下三种方法:
//参数1:要执行的方法,参数2:提供selector的对象,通常是self,参数3传递给selector的参数
[NSThread detachNewThreadSelector:(nonnull SEL)> toTarget:(nonnull id) withObject:(nullable id)]
//参数一:提供selector的对象,通常是self,参数2:要执行的方法,参数3:传递给selector的参数(如果selector方法不带参数,就使用nil)
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(doSomething) object:nil];
//隐式创建并启动线程,第一个参数为调用的方法,第二个参数为传给selector方法的参数
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
NSThread对象的常见属性:
//只读属性,线程是否在执行
thread.isExecuting;
//只读属性,线程是否被取消
thread.isCancelled;
//只读属性,线程是否完成
thread.isFinished;
//是否是主线程
thread.isMainThread;
//线程的优先级,取值范围0.0到1.0,默认优先级0.5,1.0表示最高优 //先级,优先级高,CPU调度的频率高
thread.threadPriority;
//线程的堆栈大小,线程执行前堆栈大小为512K,线程完成后堆栈大小 为0K
//注意:线程执行完毕后,由于内存空间被释放,不能再次启动
thread.stackSize;
NSThread对象的方法:
//线程开始,线程加入线程池等待CPU调度(并非真正开始执行,只是通常等待时间都非常短,看不出效果)
[thread start];
if(!thread.isCancelled){//在执行之前需要先确认线程状态,如果已经取消就直接返回
[thread cancel]; //通知线程取消,可以在外不终止线程执行
}else{
return;
}
NSThread的类方法:
类方法都用在线程内部,也就是说类方法作用于相关类方法的线程。
(1)当前线程,在开发中常用于调试,适用于所有多线程计数,返回一个线程号码
//number == 1 表示主线程,number!= 1表示后台线程
int number = [NSThread currentThread];
(2)阻塞方法
//休眠到指定时间
[NSThread sleepUntilDate:[NSDate date]];
//休眠指定时长
[NSThread sleepForTimeInterval:4.5];
(3)其他类方法
//退出线程
[NSThread exit];
//当前线程是否为主线程
[NSThread isMainThread];
//是否多线程
[NSThread isMultiThreaded];
//返回主线程的对象
NSThread *mainThread = [NSThread mainThread];
2、线程的状态
线程的状态如下图:
Snip20170209_1
(1)新建:实例化对象
(2)就绪:向线程对象发送 start消息,线程对象被加入“可调度线程池”等待 CPU 调度;detach方法和 performSelectorInBackground方法会直接实例化一个线程对象并加入“可调度线程池”
(3)运行:CPU负责调度“可调度线程池”中线程的执行,线程执行完成之前,状态可能会在“就绪”和“运行”之间来回切换,“就绪”和“运行”之间的状态变化由 CPU 负责,程序员不能干预
(4)阻塞:当满足某个预定条件时,可以使用休眠或锁阻塞线程执行,影响的方法有:sleepForTimeInterval,sleepUntilDate,@synchronized(self)x线程锁;
线程对象进入阻塞状态后,会被从“可调度线程池”中移出,CPU不再调度
(5)死亡
死亡方式:
正常死亡:线程执行完毕
非正常死亡:线程内死亡--->[NSThread exit]:强行中止后,后续代码都不会在执行
线程外死亡:[threadObj cancel]--->通知线程对象取消,在线程执行方法中需要增加 isCancelled 判断,如果 isCancelled == YES,直接返回
死亡后线程对象的 isFinished属性为 YES;如果是发送 calcel消息,线程对象的 isCancelled属性为YES;死亡后 stackSize == 0,内存空间被释放。
3、多线程的安全问题
多个线程访问同一块资源进行读写,如果不加控制随意访问容易产生数据错乱从而引发数据安全问题。为了解决这一问题,就有了加锁的概念。加锁的原理就是当有一个线程正在访问资源进行写的时候,不允许其他线程再访问该资源,只有当该线程访问结束后,其他线程才能按顺序进行访问。对于读取数据,有些程序设计是允许多线程同时读的,有些不允许。UIKit中几乎所有控件都不是线程安全的,因此需要在主线程上更新UI。
解决多线程安全问题:
(1)互斥锁
// 注意:锁定1份代码只用1把锁,用多把锁是无效的
@synchronized(锁对象) { //需要锁定的代码 }
使用互斥锁,在同一个时间,只允许一条线程执行锁中的代码。因为互斥锁的代价非常昂贵,所以锁定的代码范围应该尽可能小,只要锁住资源读写部分的代码即可。使用互斥锁也会影响并发的目的。
(2)使用NSLock对象
_lock = [[NSLock alloc] init];
- (void)synchronizedMethod {
[_lock lock];
//safe
[_lock unlock];
}
(3)atomic加锁
OC在定义属性时有nonatomic和atomic两种选择。
atomic:原子属性,为setter方法加锁(默认就是atomic)。
nonatomic:非原子属性,不会为setter方法加锁。
atomic加锁原理:
@property (assign, atomic) int age;
- (void)setAge:(int)age
{
@synchronized(self) {
_age = age;
}
}
atomic:线程安全,需要消耗大量的资源
nonatomic:非线程安全,适合内存小的移动设备
iOS开发的建议:
(1)所有属性都声明为nonatomic
(2)尽量避免多线程抢夺同一块资源
(3)尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
4、线程间通信
//在主线程上执行操作,例如给UIImageVIew设置图片
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
//在指定线程上执行操作
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thread withObject:(id)arg waitUntilDone:(BOOL)wai
四、GCD的使用
GCD(Grand Central Dispatch)伟大的中央调度系统,是苹果为多核并行运算提出的C语言并发技术框架。
GCD会自动利用更多的CPU内核;
会自动管理线程的生命周期(创建线程,调度任务,销毁线程等);
程序员只需要告诉 GCD想要如何执行什么任务,不需要编写任何线程管理代码。
一些专业术语:
dispatch :派遣/调度
queue:队列
用来存放任务的先进先出(FIFO)的容器
sync:同步
只是在当前线程中执行任务,不具备开启新线程的能力
async:异步
可以在新的线程中执行任务,具备开启新线程的能力
concurrent:并发
多个任务并发(同时)执行
串行:
一个任务执行完毕后,再执行下一个任务
1、GCD中的核心概念
(1)任务
任务就是要在线程中执行的操作。我们需要将要执行的代码用block封装好,然后将任务添加到队列并指定任务的执行方式,等待CPU从队列中取出任务放到对应的线程中执行。
- queue:队列
- block:任务
// 1.用同步的方式执行任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
// 2.用异步的方式执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
// 3.GCD中还有个用来执行任务的函数
// 在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
(2)队列
串行队列:串行队列一次只调度一个任务,一个任务完成后再调度下一个任务。
// 1.使用dispatch_queue_create函数创建串行队列
// 创建串行队列(队列类型传递NULL或者DISPATCH_QUEUE_SERIAL)
dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
// 2.使用dispatch_get_main_queue()获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
注意:主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行。
并发队列:并发队列可以同时调度多个任务,调度任务的方式,取决于执行任务的函数;并发功能只有在异步的(dispatch_async)函数下才有效;异步状态下,开启的线程上线由GCD底层决定。
/ 1.使用dispatch_queue_create函数创建队列
dispatch_queue_t
dispatch_queue_create(const char *label, // 队列名称,该名称可以协助开发调试以及崩溃分析报告
dispatch_queue_attr_t attr); // 队列的类型
// 2.创建并发队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
以上自定义队列在MRC开发时需要使用dispatch_release释放队列:
#if !__has_feature(objc_arc)
dispatch_release(queue);
#endif
主队列:主队列负责在主线程上调度任务,如果在主线程上有任务执行,会等待主线程空闲后再调度任务执行。主队列用于UI以及触摸事件等的操作,我们在进行线程间通信,通常是返回主线程更新UI的时候使用到。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 耗时操作
// ...
//放回主线程的函数
dispatch_async(dispatch_get_main_queue(), ^{
// 在主线程更新 UI
});
});
全局并发队列:全局并发队列是由苹果API提供的,方便程序员使用多线程。
//使用dispatch_get_global_queue函数获得全局的并发队列
dispatch_queue_t dispatch_get_global_queue(dispatch_queue_priority_t priority, unsigned long flags);
// dispatch_queue_priority_t priority(队列的优先级 )
// unsigned long flags( 此参数暂时无用,用0即可 )
//获得全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
全局并发队列有优先级:
//全局并发队列的优先级
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高优先级
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)优先级
//注意,自定义队列的优先级都是默认优先级
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低优先级
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台优先级
然而,iOS8开始使用 QOS(服务质量)替代了原有的优先级。获取全局并发队列时,直接传递 0,可以实现 iOS 7 & iOS 8 later的适配。
1
2
//像这样
dispatch_get_global_queue(0, 0);
全局并发队列与并发队列的区别:
(1)全局并发队列与并发队列的调度方法相同
(2)全局并发队列没有队列名称
(3)在MRC开发中,全局并发队列不需要手动释放
2、执行任务的函数
(1)同步(dispatch_sync)
任务被添加到队列后,会当前线程被调度;队列中的任务同步执行完成后,才会调度后续任务。
在主线程中,向主队列添加同步任务,会造成死锁。
在其他线程中,向主队列向主队列添加同步任务,则会在主线程中同步执行。
我们可以利用同步的机制,建立任务之间的依赖关系。如用户登录后,才能够并发下载多部小说等情况。
例如:
dispatch_queue_t q = dispatch_queue_create("com.imlifengfeng.ios", DISPATCH_QUEUE_SERIAL);
// [NSThread currentThread]获得当前线程
for (int i = 0; i < 10; ++i) {
// 异步任务顺序执行,但是如果用在串行队列中,仍然会依次顺序执行
dispatch_async(q, ^{
NSLog(@"%@%d ", [NSThread currentThread],i);
});
}
(2)异步(dispatch_async)
异步是多线程的代名词,当任务被添加到主队列后,会等待主线程空闲时才会调度该任务;添加到其他线程时,会开启新的线程调度任务。
例如:
dispatch_queue_t q = dispatch_queue_create("com.imlifengfeng.ios", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; ++i) {
// 异步任务顺序执行
dispatch_async(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
(3)以函数指针的方式调度任务
函数指针的调用方式有两种,同样是同步和异步;函数指针的传递类似于 pthread。
dispatch_sync_f
dispatch_async_f
函数指针调用在实际开发中几乎不用,只是有些面试中会问到,dispatch + block才是 gcd的主流!
3、开发中如何选择队列
选择队列当然是要先了解队列的特点:
串行队列:对执行效率要求不高,对执行顺序要求高,性能消耗小
并发队列:对执行效率要求高,对执行顺序要求不高,性能消耗大
如果不想兼顾 MRC中队列的释放,建议选择使用全局队列 +异步任务。
4、GCD的其他用法
(1)延时执行
//参数1:从现在开始经过多少纳秒,参数2:调度任务的队列,参数3:异步执行的任务
dispatch_after(when, queue, block)
例如:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2秒后异步执行这里的代码...
});
(2)一次性执行
应用场景:保证某段代码在程序运行过程中只被执行一次,在单例设计模式中被广泛使用。
// 使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});
(3)调度组(队列组)
应用场景:需要在多个耗时操作执行完毕之后,再统一做后续处理。
//创建调度组
dispatch_group_t group = dispatch_group_create();
//将调度组添加到队列,执行 block任务
dispatch_group_async(group, queue, block);
//当调度组中的所有任务执行结束后,获得通知,统一做后续操作
dispatch_group_notify(group, dispatch_get_main_queue(), block);
例如:
// 分别异步执行2个耗时的操作、2个异步操作都执行完毕后,再回到主线程执行操作
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的异步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的异步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程...
});
5、应用案例:单例模式
// 1.在.m中保留一个全局的static的实例
static id _instance;
// 2.重写allocWithZone:方法,在这里创建唯一的实例(注意线程安全)
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
// 3.提供1个类方法让外界访问唯一的实例
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
// 4.实现copyWithZone:方法
- (id)copyWithZone:(struct _NSZone *)zone
{
return _instance;
}
五、NSOperation
NSOperation是苹果推荐使用的并发技术,它提供了一些用GCD不是很好实现的功能。NSOperation是基于GCD的面向对象的使用OC语言的封装。相比GCD,NSOperation的使用更加简单。NSOperation是一个抽象类,也就是说它并不能直接使用,而是应该使用它的子类。使用它的子类的方法有三种,使用苹果为我们提供的两个子类 NSInvocationOperation,NSBlockOperation和自定义继承自NSOperation的子类。
NSOperation的使用常常是配合NSOperationQueue来进行的。只要是使用NSOperation的子类创建的实例就能添加到NSOperationQueue操作队列之中,一旦添加到队列,操作就会自动异步执行(注意是异步)。如果没有添加到队列,而是使用start方法,则会在当前线程执行。
我们知道,线程间的通信主要是主线程与分线程之间进行的。主线程到分线程,NSOperation子类也有相应带参数的方法;而分线程到主线程,比如更新UI,它也有很方便的获取主队列(被添加到主队列的操作默认会在主线程执行)的方法:[NSOperationQueue mainQueue]。
1、NSInvocationOperation
(1)单个NSInvocationOperation
直接创建一个NSInvocationOperation的对象,然后调用start方法会直接在主线程执行:
//1.创建
NSOperation *op = [[NSInvocationOperation alloc]initWithTarget:self
selector:@selector(downloadImage:) object:@"Invocation"];
//2.start方法,直接在当前线程执行
[op start];
#pragma mark - 调用的耗时操作,后面调用的耗时操作都是这个
- (void)downloadImage:(id)obj{
NSLog(@"%@-----%@",[NSThread currentThread],obj);
}
运行结果:
[1151:50868]
添加到NSOperationQueue:
//1.创建
NSOperation *op = [[NSInvocationOperation alloc]initWithTarget:self
selector:@selector(downloadImage:) object:@"Invocation"];
//2.放到队列里面去
NSOperationQueue *q = [[NSOperationQueue alloc]init];
//只要把操作放到队列,会自动异步执行调度方法
[q addOperation:op];
运行结果:
1
[1192:55469]
在number为3,name为空的子线程执行。
(2)多个NSInvocationOperation
//队列,GCD里面的并发队列使用最多,所以NSOperation技术直接把GCD里面的并发队列封装起来
//NSOperationQueue本质就是GCD里面的并发队列
//操作就是GCD里面异步执行的任务
NSOperationQueue *q = [[NSOperationQueue alloc]init];
//把多个操作放到队列里面
for (int i = 0; i < 100; i++) {
NSOperation *op = [[NSInvocationOperation alloc]initWithTarget:self
selector:@selector(downloadImage:) object:[NSString stringWithFormat:@"Invocation%d",i]];
[q addOperation:op];
}
运行结果:
**[1222:58476]
**[1222:58478]
**[1222:58307]
**[1222:58477]
**[1222:58481]
**[1222:58475]
**[1222:58480]
**[1222:58306]
··· ···
线程名与输出均没有规律,很明显就是并发队列。
2、NSBlockOperation
NSBlockOperation的用法与NSInvocationOperation相同,只是创建的方式不同,它不需要去调用方法,而是直接使用代码块,显得更方便。这也使得NSBlockOperation比NSInvocationOperation更加流行。
//跟GCD中的并发队列一样
NSOperationQueue *q = [[NSOperationQueue alloc]init];
//跟GCD中的主队列一样
// NSOperationQueue *q = [NSOperationQueue mainQueue];
//把多个操作放到队列里面
for (int i = 0; i < 100; i++) {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@------%d",[NSThread currentThread],i);
}];
//把Block操作放到队列
[q addOperation:op];
}
NSLog(@"完成");
并发队列输出结果:
**[1378:72440]
**[1378:72442]
**[1378:72441]
**[1378:72325]
**[1378:72320]
**[1378:72313]
**[1378:72276] 完成
**[1378:72444]
**[1378:72326]
**[1378:72440]
... ...
主队列输出结果:
**[1417:76086] 完成
**[1417:76086]
**[1417:76086]
**[1417:76086]
**[1417:76086]
**[1417:76086]
**[1417:76086]
**[1417:76086]
**[1417:76086]
... ...
事实上NSBlockOperation有更简单的使用方法:
NSOperationQueue *q = [[NSOperationQueue alloc]init];
for (int i = 0; i < 10; i++) {
[q addOperationWithBlock:^{
NSLog(@"%@------%d",[NSThread currentThread],i);
}];
}
3、线程间通信
主线程到子线程传对象,前面的例子里面已经有了,不再缀述。下面的例子就是回到主线程更新UI。
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; c
NSOperationQueue *q = [[NSOperationQueue alloc]init];
[q addOperationWithBlock:^{
NSLog(@"耗时操作--%@",[NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"更新UI-----%@",[NSThread currentThread]);
}];
}];
4、NSOperationQueue的一些高级操作
NSOperationQueue支持的高级操作有:队列的挂起,队列的取消,添加操作的依赖关系和设置最大并发数。
(1)最大并发数
@property (nonatomic,strong)NSOperationQueue *opQueue;
//重写getter方法实现懒加载
- (NSOperationQueue*)opQueue{
if (_opQueue == nil) {
_opQueue = [[NSOperationQueue alloc]init];
}
return _opQueue;
}
#pragma mark - 高级操作:最大并发数
//设置最大的并发数量(并非线程的数量)
self.opQueue.maxConcurrentOperationCount = 2;
//把多个操作放到队列里面
for (int i = 0; i < 10; i++) {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:3.0];
NSLog(@"%@------%d",[NSThread currentThread],i);
}];
//把Block操作放到队列
[self.opQueue addOperation:op];
}
(2)线程的挂起
#pragma mark - 高级操作:线程的挂起
//暂停继续(对队列的暂停和继续),挂起的是队列,不会影响已经在执行的操作
- (IBAction)pause:(UIButton *)sender {
//判断操作的数量,当前队列里面是不是有操作?
if (self.opQueue.operationCount == 0) {
NSLog(@"当前队列没有操作");
return;
}
self.opQueue.suspended = !self.opQueue.isSuspended;
if (self.opQueue.suspended) {
NSLog(@"暂停");
}else{
NSLog(@"继续");
}
}
(3)取消队列里的所有操作
#pragma mark - 高级操作:取消队列里的所有操作
- (IBAction)cancelAll:(UIButton *)sender {
//只能取消所有队列的里面的操作,正在执行的无法取消
//取消操作并不会影响队列的挂起状态
[self.opQueue cancelAllOperations];
NSLog(@"取消队列里所有的操作");
//取消队列的挂起状态
//(只要是取消了队列的操作,我们就把队列处于不挂起状态,以便于后续的开始)
self.opQueue.suspended = NO;
}
(4)依赖关系
例子
1.下载一个小说压缩包
2.解压缩,删除压缩包
3.更新UI
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1.下载一个小说压缩包,%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2.解压缩,删除压缩包,%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3.更新UI,%@",[NSThread currentThread]);
}];
//指定任务之间的依赖关系 --依赖关系可以跨队列(可以再子线程下载,在主线程更新UI)
[op2 addDependency:op1];
[op3 addDependency:op2];
// [op1 addDependency:op3]; 一定不能出现循环依赖
//waitUntilFinished 类似GCD中的调度组的通知
//NO不等待,直接执行输出come here
//YES等待任务执行完再执行输出come here
[self.opQueue addOperations:@[op1,op2] waitUntilFinished:YES];
//在主线程更新UI
[[NSOperationQueue mainQueue] addOperation:op3];
[op3 addDependency:op2];
NSLog(@"come here");
还有一个NSOperationQueuePriority,队列优先级的概念,因为用的极少,所以这里不做介绍。
六、三种多线程技术比较
1、NSThread
优点:NSThread比其他两个轻量级,使用简单
缺点:需要自己管理线程的生命周期、线程同步、加锁、睡眠以及唤醒等。线程同步对数据的加锁会有一定的系统开销
2、GCD
GCD 是iOS 4.0以后才出现的并发技术
使用方式:将任务添加到队列(串行/并行(全局)),指定执行任务的方法,(同步(阻塞)/异步)
拿到主队列:dispatch_get_main_queu()
NSOperation无法做到的:1.一次性执行,2.延迟执行,3.调度组(op实现要复杂的多)
3、NSOperation
NSOperation iOS2.0的时候就出现了(当时不好用,后来苹果对其进行改造)
使用方式:将操作(异步执行)添加到队列(并发/全局)
拿到主队列:[NSOperationQueue mainQueue]主队列,任务添加到主队列就会在主线程执行
提供了GCD不好实现的:1.最大并发数,2.暂停和继续,3.取消所有任务,4.依赖关系
GCD是比较底层的封装,我们知道较低层的代码一般性能都是比较高的,相对于NSOperationQueue。所以追求性能,而功能够用的话就可以考虑使用GCD。如果异步操作的过程需要更多的用户交互和被UI显示出来,NSOperationQueue会是一个好选择。如果任务之间没有什么依赖关系,而是需要更高的并发能力,GCD则更有优势。
高德纳的教诲:“在大概97%的时间里,我们应该忘记微小的性能提升。过早优化是万恶之源。”只有Instruments显示有真正的性能提升时才有必要用低级的GCD。
*/
}
- (void)viewDidLoad {
[superviewDidLoad];
/***********************多线程安全********************/
/*
一、概述
在多线程操作过程中,往往一个数据同时被多个线程读写,在这种情况下,如果没有相应的机制对数据进行保护,就很可能会发生数据污染的的问题,给程序造成各种难以重现的潜在bug。
多线程安全中相关术语及概念(假设操作的是数据库):
(1)脏读
指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中。这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
(2)不可重复读
指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
(3)幻觉读
指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。例如:
目前工资为5000的员工有10人,事务A读取所有工资为5000的人数为10人。此时,事务B插入一条工资也为5000的记录。这时,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。
二、多线程的安全问题
线程不安全:就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。
线程安全:简单来说就是多个线程同时对共享资源进行访问时,采用了加锁机制,当一个线程访问共享资源,对该资源进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。
三、iOS多线程中的“锁”
1、互斥锁:@synchronized(id anObject)
- (void)myMethod:(id)anObj
{
@synchronized(anObj)
{
//do something here
}
}
2、atomic
OC在定义属性时有nonatomic和atomic两种选择。
atomic:原子属性,为setter方法加锁(默认就是atomic)。
nonatomic:非原子属性,不会为setter方法加锁。
atomic加锁原理:
@property (assign, atomic) int age;
- (void)setAge:(int)age
{
@synchronized(self) {
_age = age;
}
}
3、NSLock
NSLock对象实现了NSLocking protocol,包含几个方法:
lock——加锁
unlock——解锁
tryLock——尝试加锁,如果失败了,并不会阻塞线程,只是立即返回NO
lockBeforeDate:——在指定的date之前暂时阻塞线程(如果没有获取锁的话),如果到期还没有获取锁,则线程被唤醒,函数立即返回NO。
比如:
NSLock *theLock = [[NSLock alloc] init];
if ([theLock lock])
{
//do something here
[theLock unlock];
}
4、递归锁:NSRecursiveLock
多次调用不会阻塞已获取该锁的线程。
NSRecursiveLock *rcsLock = [[NSRecursiveLock alloc] init];
void recursiveLockTest(int value)
{
[rcsLock lock];
if (value != 0)
{
--value;
recursiveLockTest(value);
}
[rcsLock unlock];
}
recursiveLockTest(5);
上面如果直接使用NSLock就会造成死锁。NSRecursiveLock类定义的锁可以在同一线程多次lock,而不会造成死锁。递归锁会跟踪它被多少次lock。每次成功的lock都必须平衡调用unlock操作。只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。
5、条件锁:NSConditionLock
有时一把只会lock和unlock的锁未必就能完全满足我们的使用。因为普通的锁只能关心锁与不锁,而不在乎用什么钥匙才能开锁,而我们在处理资源共享的时候,多数情况是只有满足一定条件的情况下才能打开这把锁:
//主线程中
NSConditionLock *theLock = [[NSConditionLock alloc] init];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i=0;i<=2;i++)
{
[theLock lock];
NSLog(@"thread1:%d",i);
sleep(2);
[theLock unlockWithCondition:i];
}
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[theLock lockWhenCondition:2];
NSLog(@"thread2");
[theLock unlock];
});
在线程1中的加锁使用了lock,是不需要条件的,所以顺利的就锁住了。但在unlock的使用了一个整型的条件,它可以开启其它线程中正在等待这把钥匙的临界地,而线程2则需要一把被标识为2的钥匙,所以当线程1循环到最后一次的时候,才最终打开了线程2中的阻塞。但即便如此,NSConditionLock也跟其它的锁一样,是需要lock与unlock对应的,只是lock、lockWhenCondition:与unlock,unlockWithCondition:是可以随意组合的,当然这是与你的需求相关的。
上述代码运行结果:
2017-03-04 22:21:29.031 LockDemo[87455:3031878] thread1:0
2017-03-04 22:21:31.105 LockDemo[87455:3031878] thread1:1
2017-03-04 22:21:33.175 LockDemo[87455:3031878] thread1:2
2017-03-04 22:21:35.249 LockDemo[87455:3031879] thread2
如果上面线程2的代码中将[theLock lockWhenCondition:2];改为[theLock lockWhenCondition:3];,则运行时就不会再打印出thread2了。
6、分布锁:NSDistributedLock
以上所有的锁都是在解决多线程之间的冲突,但如果遇上多个进程或多个程序之间需要构建互斥的情景该怎么办呢?这个时候我们就需要使用到NSDistributedLock了,从它的类名就知道这是一个分布式的Lock,NSDistributedLock的实现是通过文件系统的,所以使用它才可以有效的实现不同进程之间的互斥,但NSDistributedLock并非继承于NSLock,它没有lock方法,它只实现了tryLock,unlock,breakLock,所以如果需要lock的话,你就必须自己实现一个tryLock的轮询。
例如:
程序A:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
lock = [[NSDistributedLock alloc] initWithPath:@"/Users/lifengfeng/Desktop/locktest.txt"];
[lock breakLock];
[lock tryLock];
sleep(10);
[lock unlock];
NSLog(@"appA: OK");
});
程序B:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
lock = [[NSDistributedLock alloc] initWithPath:@"/Users/lifengfeng/Desktop/locktest.txt"];
while (![lock tryLock]) {
NSLog(@"appB: waiting");
sleep(1);
}
[lock unlock];
NSLog(@"appB: OK");
});
先运行程序A,然后立即运行程序B。根据打印你可以清楚的发现,当程序A刚运行的时候,程序B一直处于等待中。当大概10秒过后,程序B便打印出了appB:OK的输出,以上便实现了两上不同程序之间的互斥。/Users/lifengfeng/Desktop/locktest.txt是一个文件或文件夹的地址,如果该文件或文件夹不存在,那么在tryLock返回YES时,会自动创建该文件/文件夹。在结束的时候该文件/文件夹会被清除,所以在选择的该路径的时候,应该选择一个不存在的路径,以防止误删了文件。
7、GCD中信号量:dispatch_semaphore
假设现在系统有两个空闲资源可以被利用,但同一时间却有三个线程要进行访问,这种情况下,该如何处理呢?这里,我们就可以方便的利用信号量来解决这个问题。同样我们也可以用它来构建一把”锁”(从本质上讲,信号量与锁是有区别的,具体的请自行查阅资料)。
信号量:就是一种可用来控制访问资源的数量的标识。设定了一个信号量,在线程访问之前,加上信号量的处理,则可告知系统按照我们指定的信号量数量来执行多个线程。
在GCD中有三个函数是semaphore的操作:
dispatch_semaphore_create 创建一个semaphore
dispatch_semaphore_signal 发送一个信号
dispatch_semaphore_wait 等待信号
dispatch_semaphore_create函数有一个整形的参数,我们可以理解为信号的总量,dispatch_semaphore_signal是发送一个信号,自然会让信号总量+1,dispatch_semaphore_wait等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1,根据这样的原理,我们便可以快速的创建一个并发控制来同步任务和有限资源访问控制。
具体使用如下:
//crate的value表示,最多几个资源可访问
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//任务1
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 1");
sleep(1);
NSLog(@"complete task 1");
dispatch_semaphore_signal(semaphore);
});
//任务2
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 2");
sleep(1);
NSLog(@"complete task 2");
dispatch_semaphore_signal(semaphore);
});
//任务3
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 3");
sleep(1);
NSLog(@"complete task 3");
dispatch_semaphore_signal(semaphore);
});
运行结果:
2017-03-04 22:59:52.915 LockDemo[89228:3095043] run task 1
2017-03-04 22:59:52.915 LockDemo[89228:3095041] run task 2
2017-03-04 22:59:53.983 LockDemo[89228:3095043] complete task 1
2017-03-04 22:59:53.984 LockDemo[89228:3095040] run task 3
2017-03-04 22:59:53.992 LockDemo[89228:3095041] complete task 2
2017-03-04 22:59:55.029 LockDemo[89228:3095040] complete task 3
由于设定的信号值为2,先执行两个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过2。
如果我们把信号值设为1
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1)
则运行结果为:
2017-03-04 23:01:56.468 LockDemo[89388:3100137] run task 1
2017-03-04 23:01:57.530 LockDemo[89388:3100137] complete task 1
2017-03-04 23:01:57.531 LockDemo[89388:3100135] run task 2
2017-03-04 23:01:58.531 LockDemo[89388:3100135] complete task 2
2017-03-04 23:01:58.532 LockDemo[89388:3100134] run task 3
2017-03-04 23:01:59.588 LockDemo[89388:3100134] complete task 3
8、GCD中“栅栏函数”:dispatch_barrier_async
dispatch_barrier_async函数的作用与barrier的意思相同,在进程管理中起到一个栅栏的作用,它等待所有位于barrier函数之前的操作执行完毕后执行,并且在barrier函数执行之后,barrier函数之后的操作才会得到执行,该函数需要同dispatch_queue_create函数生成的concurrent Dispatch Queue队列一起使用。
dispatch_barrier_async函数的作用:
(1)实现高效率的数据库访问和文件访问
(2)避免数据竞争
例如:
//同dispatch_queue_create函数生成的concurrent Dispatch Queue队列一起使用
dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"----1");
});
dispatch_async(queue, ^{
NSLog(@"----2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"----barrier");
});
dispatch_async(queue, ^{
NSLog(@"----3");
});
dispatch_async(queue, ^{
NSLog(@"----4");
});
上述代码打印结果总是1 2 –> barrier –>3 4,即1、2总在barrier之前打印,3、4总在barrier之后打印,其中1、2由于并行处理先后顺序不定,当然3、4也一样。
四、综合demo
先看下面代码:
- (void)viewDidLoad
{
[self configData];
}
- (void)configData
{
self.dataSource = [NSMutableArray array];
for (int i = 0; i < 100; i++) {
[self.dataSource addObject:[NSString stringWithFormat:@"Obj - %i", i]];
}
}
- (IBAction)start:(id)sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < self.dataSource.count; i++) {
[NSThread sleepForTimeInterval:0.05];
NSLog(@"%@", self.dataSource[i]);
}
});
}
- (IBAction)removeAllObjs:(id)sender
{
[self.dataSource removeAllObjects];
}
用户在点击start按钮后,会在一个全局的queue里面对构造的数据进行遍历,为了模拟实际场景中网络请求的时延,每次循环让当前线程休息0.05s。这样在遍历的过程中,如果用户点击了移除按钮,此时self.dataSource[i]执行时,因为数组已经被清空了,会报数组越界的错误。
解决办法:
(1)使用@synchronized修复
- (IBAction)start:(id)sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(self.dataSource) {
for (int i = 0; i < self.dataSource.count; i++) {
[NSThread sleepForTimeInterval:0.05];
NSLog(@"%@",self.dataSource[i]);
}
}
});
}
- (IBAction)removeAllObjs:(id)sender
{
@synchronized(self.dataSource) {
[self.dataSource removeAllObjects];
}
}
(2)使用NSLock修复
//声明一个全局变量
NSRecursiveLock* rLock = [[NSRecursiveLock alloc] init];
- (IBAction)start:(id)sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[rLock lock];
for (int i = 0; i < self.dataSource.count; i++) {
[NSThread sleepForTimeInterval:0.05];
NSLog(@"%@", self.dataSource[i]);
}
[rLock unlock];
});
}
- (IBAction)removeAllObjs:(id)sender
{
[rLock lock];
[self.dataSource removeAllObjects];
[rLock unlock];
}
(3)使用dispatch_semaphore_signal修复
//声明全局变量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
- (IBAction)start:(id)sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
for (int i = 0; i < self.dataSource.count; i++) {
[NSThread sleepForTimeInterval:0.05];
NSLog(@"%@",self.dataSource[i]);
}
dispatch_semaphore_signal(semaphore);
});
}
- (IBAction)removeAllObjs:(id)sender
{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[self.dataSource removeAllObjects];
dispatch_semaphore_signal(semaphore);
}
(4)使用dispatch_barrier_async修复
//声明全局变量
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.threadsafe.sing", DISPATCH_QUEUE_CONCURRENT);
- (IBAction)start:(id)sender
{
dispatch_async(concurrentQueue, ^{
for (int i = 0; i < self.dataSource.count; i++) {
[NSThread sleepForTimeInterval:0.05];
NSLog(@"%@", self.dataSource[i]);
}
});
}
- (IBAction)removeAllObjs:(id)sender
{
dispatch_barrier_async(concurrentQueue, ^{
[self.dataSource removeAllObjects];
});
}
*/
}
- (void)viewDidLoad {
[superviewDidLoad];
/********************************************/
/*
一、概述
在多线程中,无论在哪个线程注册了观察者,Notification接收和处理都是在发送Notification的线程中的。所以,当我们需要在接收到Notification后作出更新UI操作的话,就需要考虑线程的问题了,如果在子线程中发送Notification,想要在接收到Notification后更新UI的话就要切换回到主线程。先看一个例子:
- (void)viewDidLoad {
[super viewDidLoad];
NSString *NOTIFICATION_NAME = @"NOTIFICATION_NAME";
NSLog(@"Current thread = %@", [NSThread currentThread]);
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:NOTIFICATION_NAME object:nil];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"Post notification,Current thread = %@", [NSThread currentThread]);
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_NAME object:nil userInfo:nil];
});
}
- (void)handleNotification:(NSNotification *)notification {
NSLog(@"Receive notification,Current thread = %@", [NSThread currentThread]);
}
运行结果:
2017-03-11 17:56:33.898 NotificationTest[23457:1615587] Current thread =
2017-03-11 17:56:33.899 NotificationTest[23457:1615738] Post notification,Current thread =
2017-03-11 17:56:33.899 NotificationTest[23457:1615738] Receive notification,Current thread =
上面我们在主线程注册观察者,在子线程发送Notification,最后Notification的接收和处理也是在子线程。
二、重定向Notification到指定线程
当然,想要在子线程发送Notification、接收到Notification后在主线程中做后续操作,可以用一个很笨的方法,在 handleNotification 里面强制切换线程:
- (void)handleNotification:(NSNotification *)notification {
NSLog(@"Receive notification,Current thread = %@", [NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"Current thread = %@", [NSThread currentThread]);
});
}
在简单情况下可以使用这种方法,但是当我们发送了多个Notification并且有多个观察者的时候,难道我们要在每个地方都手动切换线程?所以,这种方法并不是一个有效的方法。
最好的方法是在Notification所在的默认线程中捕获发送的通知,然后将其重定向到指定的线程中。关于Notification的重定向官方文档给出了一个方法:
翻译成中文:
1
一种重定向的实现思路是自定义一个通知队列(不是NSNotificationQueue对象),让这个队列去维护那些我们需要重定向的Notification。我们仍然是像之前一样去注册一个通知的观察者,当Notification到达时,先看看post这个Notification的线程是不是我们所期望的线程,如果不是,就将这个Notification放到我们的队列中,然后发送一个信号(signal)到期望的线程中,来告诉这个线程需要处理一个Notification。指定的线程收到这个信号(signal)后,将Notification从队列中移除,并进行后续处理。
我们根据官方文档中的教程测试一下:
//
// ViewController.m
// NotificationTest
//
// Created by 李峰峰 on 2017/3/11.
// Copyright © 2017年李峰峰. All rights reserved.
//
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic) NSMutableArray *notifications; //通知队列
@property (nonatomic) NSThread *notificationThread; //想要处理通知的线程(目标线程)
@property (nonatomic) NSLock *notificationLock; //用于对通知队列加锁的锁对象,避免线程冲突
@property (nonatomic) NSMachPort *notificationPort; //用于向目标线程发送信号的通信端口
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString *NOTIFICATION_NAME = @"NOTIFICATION_NAME";
NSLog(@"Current thread = %@", [NSThread currentThread]);
[self setUpThreadingSupport];
//注册观察者
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:NOTIFICATION_NAME object:nil];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//发送Notification
NSLog(@"Post notification,Current thread = %@", [NSThread currentThread]);
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_NAME object:nil userInfo:nil];
});
}
//在注册任何通知之前,需要先初始化属性。下面方法初始化了队列和锁定对象,保留对当前线程对象的引用,并创建一个Mach通信端口,将其添加到当前线程的运行循环中。
//此方法运行后,发送到notificationPort的任何消息都会在首次运行此方法的线程的run loop中接收。如果接收线程的run loop在Mach消息到达时没有运行,则内核保持该消息,直到下一次进入run loop。接收线程的run loop将传入消息发送到端口delegate的handleMachMessage:方法。
- (void) setUpThreadingSupport {
if (self.notifications) {
return;
}
self.notifications = [[NSMutableArray alloc] init];
self.notificationLock = [[NSLock alloc] init];
self.notificationThread = [NSThread currentThread];
self.notificationPort = [[NSMachPort alloc] init];
[self.notificationPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:self.notificationPort
forMode:(__bridge NSString*)kCFRunLoopCommonModes];
}
//端口的代理方法
- (void)handleMachMessage:(void *)msg {
[self.notificationLock lock];
while ([self.notifications count]) {
NSNotification *notification = [self.notifications objectAtIndex:0];
[self.notifications removeObjectAtIndex:0];
[self.notificationLock unlock];
[self processNotification:notification];
[self.notificationLock lock];
};
[self.notificationLock unlock];
}
- (void)processNotification:(NSNotification *)notification {
//判断是不是目标线程,不是则转发到目标线程
if ([NSThread currentThread] != _notificationThread) {
// 将Notification转发到目标线程
[self.notificationLock lock];
[self.notifications addObject:notification];
[self.notificationLock unlock];
[self.notificationPort sendBeforeDate:[NSDate date]
components:nil
from:nil
reserved:0];
}else {
// 在此处理通知
NSLog(@"Receive notification,Current thread = %@", [NSThread currentThread]);
NSLog(@"Process notification");
}
}
@end
打印结果:
2017-03-11 18:28:55.788 NotificationTest[24080:1665269] Current thread =
2017-03-11 18:28:55.789 NotificationTest[24080:1665396] Post notification,Current thread =
2017-03-11 18:28:55.795 NotificationTest[24080:1665269] Receive notification,Current thread =
2017-03-11 18:28:55.795 NotificationTest[24080:1665269] Process notification
可以看到,运行结果结果我们想要的:在子线程中发送Notification,在主线程中接收与处理Notification。
上面的实现方法也不是绝对完美的,苹果官方指出了这种方法的限制:
(1)所有线程的Notification的处理都必须通过相同的方法(processNotification :)。
(2)每个对象必须提供自己的实现和通信端口。
更好但更复杂的方法是我们自己去子类化一个NSNotificationCenter,或者单独写一个类来处理这种转发。
除了上面苹果官方给我们提供的方法外,我们还可以利用基于block的NSNotification去实现,apple从 ios4之后提供了带有 block的 NSNotification。使用方式如下:
- (id
object:(id)obj
queue:(NSOperationQueue *)queue
usingBlock:(void (^)(NSNotification *note))block
其中:
观察者就是当前对象
queue 定义了 block执行的线程,nil则表示 block的执行线程和发通知在同一个线程
block 就是相应通知的处理函数
这个 API已经能够让我们方便的控制通知的线程切换。但是,这里有个问题需要注意。就是其 remove操作。
原来的 NSNotification的 remove方式如下:
- (void)removeObservers {
[[NSNotificationCenter defaultCenter] removeObserver:self name:POST_NOTIFICATION object:nil];
}
但是带 block方式的 remove便不能像上面这样处理了。其方式如下:
- (void)removeObservers {
if(_observer){
[[NSNotificationCenter defaultCenter] removeObserver:_observer];
}
}
其中 _observer是 addObserverForName方式的 api返回观察者对象。这也就意味着,你需要为每一个观察者记录一个成员对象,然后在 remove的时候依次删除。试想一下,你如果需要 10个观察者,则需要记录 10个成员对象,这个想想就是很麻烦,而且它还不能够方便的指定 observer。因此,理想的做法就是自己再做一层封装,将这些细节封装起来。
参考:Delivering Notifications To Particular Threads
*/
}