网络多线程学习笔记
一 .NSThread
1.NSThread的三种创建方式
1>直接创建!
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"Thread"];
[thread start];
2>detachNewThreadSelector
*这是在后台执行的方法
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"Detach"];
3>performSelectorInBackground:
- 是 NSObject 的一个分类方法,意味着所有的 NSObject 都可以使用此方法,在其他线程执行方法!
- 特点:没有thread字眼,一旦制定,就会立即在后台线程执行 selector 方法
- performSelectorInBackground 隐式的多线程方法,这种方法,在使用的时候更加灵活!
[对象 performSelectorInBackground:@selector(loadData) withObject:nil];
2.线程的状态
注意:在终止线程之前,应该注意释放之前分配的对象!
如果是 ARC 开发,需要注意,清理 C 语言框架创建的对象!否则会出现内存泄漏!
3.NSThread属性
* t1.name = @"Thread A";
* 用途:在大的商业项目中,通常希望程序崩溃的时候,能够获取到程序准确执行所在的线程!
* t1.threadPriority = 0.1;
* 优先级,是一个浮点数,从0~1.0,1.0表示优先级最高,默认优先级是0.5!优先级只是保证 CPU 调度的可能性会高!
* ![NSThread isMainThread
* 判断是否是主线程
4.多线程的目的
- 多线程的目的:将耗时的操作放在后台,不阻塞主线程和用户的交互!
- 个人建议,在开发的时候,不要修改优先级!优先级翻转!
- 多线程开发,有一个原则:尽量简单!
5.多线程会出现的问题
- 资源抢夺
- 互斥锁 - 保证锁内的代码,同一时间,只有一条线程能够执行!
- 多线程的目的:耗时的操作放在后台
- 多线程的优点:通过并发提高程序的执行效率!
- 互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!
实际上,原子属性内部也有一把锁,自旋锁
“`
- 自旋锁 & 互斥锁
- 共同点
都能够保证同一时间,只有一条线程执行锁定范围的代码
- 不同点
互斥锁:如果发现有其他线程正在执行锁定的代码,线程会进入休眠状态,等待其他线程执行完毕,打开锁之后,线程会被唤醒
自旋锁:如果发现有其他线程正在执行锁定的代码,线程会用死循环的方式,一直等待锁定代码执行完成!
自旋锁更适合执行非常短的代码!
- 自旋锁 & 互斥锁
无论什么锁,都是要付出代价的!
“`
6.线程间通信
performSelectorOnMainThread "线程间通讯"
[self performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
1. 在主线程执行的方法
2. 传递给方法的参数
3. 是否等待被调用方法执行完成,有可能也会等待调用方法的执行完成!几率极少!
7.一些问题
- 问题1:为什么scrollView是strong,imageView是weak?
在 OC 中如果一个对象没有其他对象做强引用,会被立即释放!
问题2: UIImage & UIImageView有什么区别
- UIImage 是图像 - 照片
- UIImageView 是视图 - 相框
8.定时器
以下两句两种添加时钟的方法是等价的
1. NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(updateTimer) userInfo:nil repeats:YES];
// 加入运行循环
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
2. [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateTimer) userInfo:nil repeats:YES];
* NSDefaultRunLoopMode - 时钟,网络事件;
* NSRunLoopCommonModes - 用户交互;
9. 运行循环
- 保证程序不退出!
- 负责监听事件,监听 iOS 中所有的事件:触摸,时钟,网络事件
- 如果没有事件发生,会让程序进入休眠状态
10.开发多线程程序的步骤
- 保证单个线程执行正常!
- 每一个售票逻辑(窗口)应该把所有的票卖完
- 添加线程
二 .02 -GCD
- GCD 核心概念:将任务添加到队列,指定任务执行的方法
- 任务
-使用 block 封装
-block就是一个提前准备好的代码块,在需要的时候执行
- 队列(负责调度任务)
- 串行队列:一个接一个的调度任务
- 并发队列:可以同时调度多个任务
- 主队列: 专门用来在主线程上调度任务的"队列"
主队列不能在其他线程中调度任务!
如果主线程上当前正在有执行的任务,主队列暂时不会调度任务的执行!
注意:主队列不是主线程
- 异步任务,会在主线程的方法执行完成后,被调度
- 同步任务,会造成死锁
- 全局队列,系统提供给程序员,方便程序员使用的全局队列
有关服务质量问题,使用以下代码能够做到 iOS7 & iOS8 的适配
dispatch_get_global_queue(0, 0);
- 执行任务的函数
- 同步执行:当前指令不完成,就不会执行下一条指令
- 异步执行:当前指令不完成,同样可以执行下一条指令
异步是多线程的代名词
小结:
- 开不开线程,取决于执行任务的函数,同步不开,异步开
- 开几条线程,取决于队列,串行开一条,并发开多条(异步)
同步任务在多线程中的用处:
- 可以队列调度多个异步任务前,指定一个同步任务,让所有的异步任务,等待同步任务执行完成,这就是所谓的依赖关系!
全局队列 & 并发队列
1> 名称,并发队列有名称,适合于商业级软件跟踪错误报告!
2> release, 在 MRC 开发时,并发队列需要使用
dispatch_release(q);
结论:目前绝大多数的软件都会使用全局队列。比较优秀的第三方框架会使用自定义的并发队列!
日常使用:用全局!
全局队列 & 串行队列选择
全局队列:并发,能够调度多个线程,执行效率高
-费电
串行队列:一个接一个,只能够开启一条线程,执行效率低(斯坦福大学)
-如果任务之间需要依赖,使用串行队列
-省电,省钱,省流量
判断依据:用户的上网方式
-WIFI,可以多开线程,6条
-3G/4G,尽量少开线程,2~3条
- 全局对列的参数
- dispatch_queue_t q = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
***<参数>
1. 涉及到系统适配
iOS 8 服务质量(让线程响应的更快还是更慢)
- QOS_CLASS_USER_INTERACTIVE 用户交互(用户迫切希望线程快点被执行,不要用耗时的操作)
- QOS_CLASS_USER_INITIATED 用户需要的(同样不要使用耗时操作)
- QOS_CLASS_DEFAULT 默认的(给系统用来重置队列的)
** QOS_CLASS_UTILITY 实用工具(用来做耗时操作)
- QOS_CLASS_BACKGROUND 后台
- QOS_CLASS_UNSPECIFIED 没有指定优先级
iOS 7 调度的优先级
- DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级
- DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级
- DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级
- DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台优先级
提示:尤其不要选择 BACKGROUND 优先级和服务质量,用户不需要知道线程什么时候执行完成!线程的执行会慢的令人发指!
有关服务质量的介绍,用在与 XPC 框架结合使用的,XPC 用在 MAC 平台上做进程间通讯的框架!
因为大家工作后,暂时会考虑 iOS7 & iOS8 的适配,无法使用服务质量,直接指定 0,能够做到 iOS7 & 8 的适配
dispatch_get_global_queue(0, 0);
2. 为未来使用保留的,应该始终传入0
同步任务的作用
利用同步任务,能够做到,任务依赖关系,前一个同步任务不执行完,队列就不会调度后面的任务!
3. demo
// MARK: 串行队列,同步执行
/**
不会开线程,会顺序执行
*/
- (void)gcdDemo1 {
// 1. 队列
/**
参数
1. 队列的名称
2. 队列的属性
DISPATCH_QUEUE_SERIAL(NULL) 表示串行
*/
// dispatch_queue_t q = dispatch_queue_create("itcast", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t q = dispatch_queue_create("itcast", NULL);
// 2. 执行任务
for (int i = 0; i<10; i++) {
dispatch_sync(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
}
MARK: 串行队列,异步执行
/**
串行队列,一个一个调度任务
执行的方法:异步,会开线程
问题:会开几条线程?顺序执行吗?come here?
猜测:不确定,会/不会顺序执行,一上来就执行
答案:只会开一条线程:是因为队列是按照先进先出的顺序调度任务,因此有一个线程就足够了
顺序执行,FIFO
*/
- (void)gcdDemo2 {
dispatch_queue_t q = dispatch_queue_create("itcast", NULL);
for (int i = 0; i<10; i++) {
// NSLog(@"%d------", i);
dispatch_async(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
NSLog(@"come here");
}
// MARK: 并发队列,异步执行
/**
队列:并发队列,可以调度多个任务
异步:可以开启线程
问题:会开几条线程?顺序执行吗?come here?
猜测:会,不会,不是最后
*/
- (void)gcdDemo3 {
// 1. 队列
dispatch_queue_t q = dispatch_queue_create("itcast", DISPATCH_QUEUE_CONCURRENT);
// 2. 异步执行
for (int i = 0; i<10; i++) {
dispatch_async(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
NSLog(@"come here");
}
// MARK: 并发队列,同步执行
/**
队列:并发队列,可以调度多个任务
同步:不能开线程
问题:会开几条线程?顺序执行吗?come here?
猜测:不会,会,最后
效果:和串行队列,同步执行效果一样!
*/
- (void)gcdDemo4 {
// 1. 队列
dispatch_queue_t q = dispatch_queue_create("itcast", DISPATCH_QUEUE_CONCURRENT);
// 2. 异步执行
for (int i = 0; i<10; i++) {
dispatch_sync(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
NSLog(@"come here");
}
// MARK: 主队列,异步执行
/**
队列:主队列,不可能在其他线程执行
任务:异步,开线程,不会阻塞线程执行,不用等待着一条语句执行完,就能执行下一句
猜测:不开线程,顺序执行,come here 出现在最后!!!!!
*/
- (void)gcdDemo5 {
// 1. 主队列 - 程序启动之后已经存在主线程,主队列同样存在
dispatch_queue_t q = dispatch_get_main_queue();
// 2. 安排一个任务
for (int i = 0; i<10; i++) {
dispatch_async(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
NSLog(@"睡会");
[NSThread sleepForTimeInterval:2.0];
NSLog(@"come here");
}
// MARK: 主队列,同步执行
/**
主队列:不开线程
同步任务:不开线程
猜测:come here??? | 卡死
*/
- (void)gcdDemo6 {
dispatch_queue_t q = dispatch_get_main_queue();
NSLog(@"卡死了吗?");
dispatch_sync(q, ^{
NSLog(@"我来了");
});
NSLog(@"come here");
}
// MARK: 同步任务的作用!
/**
在网络开发中,通常会把很多任务放在后台异步执行,有的时候,有些任务会彼此有"依赖"关系!
例子:小说的网站,用户登录,扣费,下载小说 A,扣费,下载小说 B...
利用同步任务,能够做到,任务依赖关系,前一个同步任务不执行完,队列就不会调度后面的任务!
come here 在用户登录后面!
*/
- (void)gcdDemo7 {
dispatch_queue_t q = dispatch_queue_create("itcast", DISPATCH_QUEUE_CONCURRENT);
// 1. 用户登录,必须要第一个执行
dispatch_sync(q, ^{
NSLog(@"用户登录 %@", [NSThread currentThread]);
});
// 2. 扣费
dispatch_async(q, ^{
NSLog(@"扣费 %@", [NSThread currentThread]);
});
// 3. 下载
dispatch_async(q, ^{
NSLog(@"下载 %@", [NSThread currentThread]);
});
NSLog(@"come here");
}
#3.其他用法
1. 延时执行
// 从现在开始,经过多少纳秒之后
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
2. 一次性执行
- (void)once {
NSLog(@"来了");
// 苹果提供了一个一次行执行的机制,不仅能够保证只被执行一次,而且是"线程安全"的
// 苹果推荐使用 dispatch_once_t 来做一次性执行!因为效率高!
// 不要使用互斥锁,互斥锁效率低!
static dispatch_once_t onceToken;
NSLog(@"%ld", onceToken);
dispatch_once(&onceToken, ^{
// 只会执行一次的代码
NSLog(@"执行了!%@", [NSThread currentThread]);
});
}
3. 调度组
// MARK: 调度组1
- (void)group1 {
// 1. 队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
// 2. 调度组
dispatch_group_t g = dispatch_group_create();
// 3. 添加任务,让队列调度,指定任务执行函数,最终通知群组
dispatch_group_async(g, q, ^{
NSLog(@"download A %@", [NSThread currentThread]);
});
dispatch_group_async(g, q, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"download B %@", [NSThread currentThread]);
});
dispatch_group_async(g, q, ^{
[NSThread sleepForTimeInterval:0.8];
NSLog(@"download C %@", [NSThread currentThread]);
});
// 4. 所有任务执行完毕后,获得通知
// 用一个调度组,可以监听全局队列调度的任务,执行完毕后,在主队列执行最终处理!
// dispatch_group_notify 本身是异步的
dispatch_group_notify(g, dispatch_get_main_queue(), ^{
// 更新UI,通知用户!
NSLog(@"OK %@", [NSThread currentThread]);
});
NSLog(@"come here");
}
4.注意点
-子线程运行循环默认不启动
子线程运行循环
10-串行队列异步执行示意图
5. 主队列 & 串行队列的区别
主队列 & 串行队列的区别
容易产生误解的地方:主队列也是一个一个安排任务
队列的特点:以先进先出的方式调度任务!就是一个接一个调度任务!
- 并发队列,可以调度很多任务
- 串行队列,必须等待一个任务执行完成,再调度另外一个任务
- 最多只能开启一条线程
- 主队列,以先进先出调度任务,如果主线程上有任务在执行,主队列不会调度任务
- 主要是负责在主线程上调度任务,达到线程间通讯的目的!
关于 GCD Block 嵌套的建议:要尽量减少层次!最多不要超过 3 层!
层次越多越复杂!
多线程开发的原则:简单!
// MARK: 主队列同步任务(增强版-不死锁-代码精简)
- (void)gcdDemo4 {
dispatch_async(dispatch_get_global_queue(0, 0), ^ {
NSLog(@"这里!!! %@", [NSThread currentThread]);
// 2. 同步执行任务 -》 死锁
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"能来吗? %@", [NSThread currentThread]);
});
NSLog(@"睡会");
[NSThread sleepForTimeInterval:1.0];
NSLog(@"come here");
});
}
三 NSOperation
1.简介
NSOperation 是苹果公司大力推荐的"并发"技术!程序员已经不在需要关心线程以及线程的生命周期!
NSOperation 的核心概念:将"操作"添加到"队列"
GCD 的核心概念:将"任务"添加到队列,指定任务的执行函数(方法)
---
GCD & NSOperation 的对比
GCD 在 iOS 4.0 推出的,主要针对多核处理器做了优化的并发技术,是 C 语言的
-将"任务[block]"添加到"队列[串行/并发/主队列/全局队列]",并且指定执行任务的函数[同步/异步]
-线程间通讯 dispatch_get_main_queue()
-提供了一些 NSOperation 不具备的功能
- 一次性执行
- 延迟执行
- 调度组(在op中也可以做到,就是有点麻烦)
NSOperation 在 iOS 2.0 推出的,苹果推出 GCD 之后,对 NSOperation 底层重写了一遍
-将操作[异步执行的任务]添加到队列[并发队列],就会立即异步执行
-mainQueue
-提供了一些 GCD 实现起来比较困难的功能!
- 最大并发操作数
- 队列的暂停/继续
- 取消所有操作
- 指定操作之间的依赖关系 (GCD 用同步实现)
---
NSOperation 类是一个抽象类
特点:
- 不能直接使用!
目的:
- 定义子类共有的属性和方法
子类
- NSInvocationOperation
- NSBlockOperation
- UIGestureRecognizer
- CAAnimation
- CAPropertyAnimation
- BASIC
- Keyframe
2.demo
// MARK: 线程间通讯
- (void)demo6 {
[self.opQueue addOperationWithBlock:^{
NSLog(@"耗时的操作 %@", [NSThread currentThread]);
// 主线程更新UI,主队列
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"UIUIUI --- %@", [NSThread currentThread]);
}];
}];
}
// MARK: 全局队列 - 只要是 NSOpeation 的子类,都可以添加到队列!
- (void)demo5 {
// 直接添加任务
for (int i = 0; i < 10; i++) {
[self.opQueue addOperationWithBlock:^{
NSLog(@"%@ --- %d", [NSThread currentThread], i);
}];
}
// block operation 同样可以添加到队列
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"BLOCK ---- %@ ---- %d ", [NSThread currentThread], 100);
}];
[self.opQueue addOperation:op1];
// invocation operation
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"invocation"];
[self.opQueue addOperation:op2];
}
// MARK: 更简单的
- (void)demo4 {
// 1. 队列 -> 队列如果每次分配会比较浪费!
// 在实际开发中,会使用全局的队列,统一调度所有异步执行的操作
NSOperationQueue *q = [[NSOperationQueue alloc] init];
// 2. 添加操作
for (int i = 0; i<10; i++) {
[q addOperationWithBlock:^{
NSLog(@"%@ --- %d", [NSThread currentThread], i);
}];
}
}
// MARK: NSBlockOperation 演练
// NSBlockOperation 所有的代码都写在一起,更加好维护
- (void)demo3 {
// 1. 队列
NSOperationQueue *q = [[NSOperationQueue alloc] init];
// 2. 操作
for (int i = 0; i<10; i++) {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@ ---- %d ", [NSThread currentThread], i);
}];
[q addOperation:op];
}
}
// MARK: NSInvocationOperation演练
/**
结果:开启多个线程,不会顺序执行 -> GCD 并发队列,异步执行任务
NSOperation 本质上是对 GCD 的面相对象的封装
- 队列:本质上就是 GCD 的并发队列
- 操作:异步执行的任务
*/
- (void)demo2 {
// 1. 队列
NSOperationQueue *q = [[NSOperationQueue alloc] init];
for (int i = 0; i < 10; i++) {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@(i)];
// 添加到队列
[q addOperation:op];
}
}
- (void)demo1 {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"invocation"];
// start 方法,会在当前线程执行调度方法
// [op start];
// 队列
NSOperationQueue *q = [[NSOperationQueue alloc] init];
// 将操作添加到队列 - 会自动异步执行调度方法
[q addOperation:op];
}
- (void)downloadImage:(id)obj {
NSLog(@"%@ %@", [NSThread currentThread], obj);
}
3.高级演示
- 最大并发数
设置同时最大并发操作数量
WIFI: 5~6
2G/3G/4G: 2~3
self.opQueue.maxConcurrentOperationCount = 2;
// 向队列添加任务
// 从 iOS 8.0 开始,无论使用 GCD 还是 NSOperation,都会开启很多线程
// 在 iOS 7.0 以前,GCD 通常只会开启 5~6 条线程!
// 目前的前程数多说明:
// 1. 底层的线程池更大了,能够拿到的线程资源更多了!
// 2. 对控制同时并发的线程数,要求就更高!
- 暂停与继续
- (IBAction)pause {
// 在设置队列的挂起属性时,并不会判断队列中是否有操作!
// 如果不希望用户产生困惑,可以提前做判断
// 判断队列中当前是否有操作
if (self.opQueue.operationCount == 0) {
NSLog(@"没有操作!");
return;
}
// 判断队列是否挂起
if (self.opQueue.isSuspended) {
// 在暂停的时候,队列中的操作数,是包含正在执行的操作的
NSLog(@"继续 %tu", self.opQueue.operationCount);
self.opQueue.suspended = NO;
} else {
// 再次继续的时候,如果之前执行的操作已经完成,队列中的操作数就只有没有调度的操作
NSLog(@"暂停 %tu", self.opQueue.operationCount);
self.opQueue.suspended = YES;
}
}
- 依赖关系
- (void)dependecy {
/**
例子:例如从网络上下载小说的压缩包、完成之后解压缩、通知用户
*/
// 1. 下载
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下载 - %@", [NSThread currentThread]);
}];
// 2. 解压缩
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"解压缩 - %@", [NSThread currentThread]);
}];
// 3. 通知用户
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"通知用户 - %@", [NSThread currentThread]);
}];
// NSOperation 提供了依赖关系
// NSOperation的所有操作都是异步执行的,但是为了建立任务之间的依赖,提供了dependency的功能
// GCD 中,可以直接通过同步任务来实现,也可以通过串行队列
[op2 addDependency:op1];
[op3 addDependency:op2];
// !!!: 注意,不要指定循环依赖,一旦指定了循环依赖,队列就不工作!
// 不会造成死锁!以前版本的 Xcode 会死锁!
// [op1 addDependency:op3];
// waitUntilFinished,是否等待所有操作完成
// 等待是阻塞式的,类似于 dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
[self.opQueue addOperations:@[op1, op2] waitUntilFinished:NO];
// 主线程通知用户
[[NSOperationQueue mainQueue] addOperation:op3];
NSLog(@"come here");
}
四 sdwebimage
1.位移枚举
- 利用位移枚举,可以通过按位或的方式,使用一个参数传递多个类型的组合!
- 在所有 C 语言中,如果看到位移枚举做参数,第一个选项不是0
暗示:传入 0 做参数,表示什么附加操作都不做!执行效率最高!
2.NSCache
NSCache 是苹果提供的一套缓存机制,使用起来和 NSMutabDictionary 非常像
特点
- 是线程安全的
- 当内存不足的时候,会自动释放
- 指定缓存的限额
- 缓存数量
- 缓存成本
---
属性
totalCostLimit; 总成本限额,默认为0
countLimit; 总数量限额,默认为0
什么是成本?成本我们可以自己指定
举个栗子!
图像:宽 * 高 = 总像素点,越大,像素越高,越清晰,内存占用越大
SDWebImage中,缓存的成本就是按照图像 宽 * 高 来指定的!
---
指定限额的好处,一旦超过限额,缓存就自动清理!
// *** 这个代理方法,仅供测试了解 NSCache 的工作机制的,平时不要实现,否则会严重影响缓存的执行性能!
- (void)cache:(NSCache *)cache willEvictObject:(id)obj {
[NSThread sleepForTimeInterval:0.3];
NSLog(@"---删除 %@", obj);
}
五 网络基础
1.通知中心
观察者模式-发生事件后,以广播的形式,通知所有的监听者
通知中心的本质到底是什么?就是监听一个字符串!
- defaultCenter 返回进程的默认通知中心!开发的时候,不能创建通知中心!
- 通知中心一旦获取到"字符串",就以广播的形式广播字符串,可以有多个观察者!
面试题:通知中心是同步的,还是异步的? - 同步的!
- 通知里面到底有什么?
- name 通知的名称字符串,通知中心本质上就是监听这个字符串
- object 发布通知的时候,希望传递给监听者的对象
- userInfo 发布通知的时候,希望传递给监听者的附加信息字典
- 通知和代理有什么区别?
- 代理是一对一的,设置 delegate 属性,在需要的时候,通过 delegate 调用 selector 方法!
- 通知是一对多的,只要发生事件,会以广播的形式,通知所有的监听者
- 通知中心/发布通知的对象不需要知道谁是监听者,发布对象和监听者之间的耦合度很低
- 监听者需要知道通知的名称字符串,如果发布者还传递 userInfo 字典,监听者同时需要知道字典的键名
- 技巧:在 OC 的框架中,如果定义通知,通常通知的名字包含"Notification"字样,同时 key 会和 通知定义在一起!
- 通知监听方法不能有返回值!
- 代理可以有返回值!
对于一些对性能消耗比较大的功能
例如:地图导航(GPS),利用加速计/陀螺仪,通知中心……
可以在界面出现的时候,开启功能,界面消失的时候,关闭功能,能够达到省电的目的!
如果是通知中心,利用这一技巧,可以避免和其他的界面中监听相同事件发生冲突!
2.通知队列
通知队列是一个缓冲,可以判断什么时候来发布通知!
利用发送通知的 Style,同样可以实现简单的异步!
提示:通知中心本身的工作机制没有发生任何变化,变化的是队列的缓冲效果!
NSPostWhenIdle = 1, Idle 发呆,空闲时发布
NSPostASAP = 2, ASAP as soon as possible尽快
NSPostNow = 3 立即发布
coalesce : 合并/聚合
NSNotificationNoCoalescing = 0, 不合并
NSNotificationCoalescingOnName = 1, 按照名称合并
NSNotificationCoalescingOnSender = 2 按照发布者合并
合并的用处:有的时候,可能会接收到多个通知,但是程序只想做出一次响应!
发布者可以利用通知队列,指定监听者工作几次!
*/
// 最后一个参数,指定发布通知的运行循环的模式数组,使用 nil,就是 默认的运行循环模式