NMT DAY03
1. GCD的高级功能
1.1 GCD阻塞(Barrier)
1.2 GCD延迟操作
1.3 GCD一次性执行once
1.4 GCD调度组group
1.5 GCD单例设计模式Singleton
1.5.1 两种方式创建单例
1.5.1 懒汉式单例(lazy initialization)
1.5.2 饿汉式单例(eager initialization)
2. NSOperation
2.1 NSInvocationOpearion
2.2 NSBlockOperation
2.3 NSOperation和GCD的对比
3. NSOperation的高级功能
3.1 队列的最大并发数
3.2 队列的暂停、继续、取消
3.2.1 队列暂停
3.2.2 队列继续
3.2.3 队列取消
3.3 操作间的依赖关系
3.4. 操作的优先级和监听操作执行完成的回调
4. 列表异步加载网络图片
4.1 SDWebImage加载网络图片
4.2 NSOperation加载网络图片
5. GitHub初体验
6. AFN请求json数据
7. SDWebImage
1. GCD的高级功能
- 阻塞 Barrier
- 一次性执行 Once
- 延迟操作 After
- 调度组 Group
1.1 GCD阻塞(Barrier)
注意:
- barrier使用的时候规定要使用自定义的并行队列
- 大部分情况下都是正确的,但是不能绝对信任。
1.2 GCD延迟操作
- 延迟操作 : ‘dispatch_after’这个函数默认是异步执行的
- 使用代码块
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"i am starting");
});
- 自己手写
//dispatch_time_t when :延迟多久开始执行,可以精确到纳秒
//dispatch_queue_t : 在那个队列执行任务
//参数三 :执行的任务代码块
dispatch_after(dispatch_time_t when, dispatch_queue_t _Nonnull queue, ^{
code
})
//dispatch_time_t when :延迟多久开始执行,可以精确到纳秒
//dispatch_queue_t : 在那个队列执行任务
//参数三 :执行的任务代码块
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 *NSEC_PER_SEC));
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_block_t block = ^{
NSLog(@"I'm late:%@",[NSThread currentThread]);
};
dispatch_after(when,queue,block);
NSLog(@"~~~~end~~~~~");
1.3 GCD一次性执行once
-
dispatch_once_t
内部有一把锁,是能够保证线程安全.而且是苹果公司推荐使用的.名词解释:线程安全
线程安全:这个任务在同一时刻有且只能被一个线程执行。
线程不安全:是说这个任务在同一时刻可以被多个线程执行。 -
在开发中,有些代码只想就只执行一次.典型的应用场景就是
设计单例模式
. dispatch_once_t
能够确保只执行一次的原理:
onceToken
有个初始值,当第一次执行时,判断是否是初始值,如果是初始值就执行函数内部的代码。- 执行结束之前,修改
onceToken
初始值。 - 第二次执行的时候,因为
onceToken
已经不再是初始值,所以就不会再次执行了。
static dispatch_once_t onceToken;
NSLog(@"%ld",onceToken);
dispatch_once(&onceToken, ^{
NSLog(@"hello");
});
1.4 GCD调度组group
- 调度组中的所有异步任务执行结束之后,会得到统一的通知.
- 使用场景:监听一组异步任务是否执行结束,如果执行结束就能够得到统一的通知.
// dispatch_group_t:调度组
// dispatch_queue_t:队列
// block:任务
dispatch_group_async(dispatch_group_t _Nonnull group, dispatch_queue_t _Nonnull queue, <#^(void)block#>)
enter
和leave
都是成对儿出现的。enter
如果比leave
多,那就永远不会被通知,监测实效。enter
如果比leave
少,程序直接crash。
//下载三首歌曲ABC,歌曲的下载顺序不做要求,但是要知道什么时候下载完成
- (void)downloadSongs{
// dispatch_group_t:调度组
// dispatch_queue_t:队列
// block:任务
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"A歌曲下载完毕 %@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"B歌曲下载完毕 %@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"C歌曲下载完毕 %@",[NSThread currentThread]);
dispatch_group_leave(group);
});
// 所有歌曲下载完之后,通知用户ABC都下载完毕了。
dispatch_notify(group, dispatch_get_main_queue(),^{
NSLog(@"Well Done. %@",[NSThread currentThread]);
});
}
1.5 GCD单例设计模式Singleton
单例设计模式的特点:
- 有一个全局访问点 (供全局实例化单例的类方法)
- 单例保存在静态存储区
- 在内存有且只有一份
- 生命周期跟APP一样长
场景:
- 例如QQ音乐播放器,只有一个播放器,播放所有的音频。
- 数据库大量的读写操作
- 工程里面常用的工具类:获取沙盒路径的类、处理网络数据的类、用户登录
系统提供的单例:
[NSUserDefaults standardUserDefaults];
perfermance设置[UIApplication sharedApplication];
Application[NSNotificationCenter defaultCenter];
通知中心[NSFileManager defaultManager];
文件管理器- 还有日历,等等
注意:单例不要滥用,因为单例的生命周期跟APP一样,也会占用内存。
潜规则系统一般定义,单例都是以default
,shared
开通。
命名规则:shared+类名称 +(instancetype)sharedNetworkTool
- 现在我们的目的是类无论创建多少次,都只分配一次存储空间,而分配存储空间的操作是在
alloc:
中执行,所以我们要重写类中的alloc:
类方法 - 在重写
alloc:
过程中,我们发现有个allocWithZone:
方法。alloc:
方法内部最终会去调用allocWithZone:
方法来分配存储空间,所以为了能更深层控制,我们直接重写allocWithZone:
方法。
+ (instancetype)alloc
+ (instancetype)allocWithZone:(struct _NSZone *)zone
- 为了让单例严谨一点,我们还要考虑copy这种情况,所以我们还要重写copy和mutableCopy。要重写copy和mutableCopy方法,必须先遵守协议。
@interface NetworkTool()<NSCopying, NSMutableCopying>
- (id)copyWithZone:(NSZone *)zone
{
// 直接返回变量即可,因为只有创建了对象,才能使用copy方法
return _instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
return _instance;
}
在学习多线程之前,写单例的方法:BUT,在多线程下是错误的,因为线程不安全。
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
static id _instance;
if (!_instance) {
_instance = [super allocWithZone:zone];
}
return _instance;
}
1.5.1 两种方式创建单例
多线程之后,有两种方法创建单例。
方式一:加线程锁
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
static id _instance;
@synchronized (self) {
if (!_instance) {
_instance = [super allocWithZone:zone];
}
}
return _instance;
}
方式二:不需要做判断了,因为dispatch_once
只能执行一次,并且线程安全。推荐使用。这个就是专门为单例设计的,高效+安全。
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
static id _instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
1.5.1 懒汉式单例(lazy initialization)
- 在使用时才会创建。
1.5.2 饿汉式单例(eager initialization)
- 尽早的创建单例,可以利用
initialize
方法创建单例。 initialize
会在类第一次被使用时调用initialize
调用是线程安全的。
+(void)initialize
{
_instance = [[self alloc]init];
}
//饿汉式单例
+(instancetype)sharedNetworkManager
{
return _instance;
}
拓展:
饿汉模式、懒汉模式的优缺点
- 饿汉式在类创建的同时,已经创建好对象,以后不再改变,所以天生就是线程安全的
- 懒汉模式,如果需要线程安全还需要借助线程锁或者dispatch_once。无论是哪个方法,对性能都有降低,而实际99%的情况下都不需要同步。
2. NSOperation
- 是OC语言中基于GCD的面向对象的封装.
- 使用起来比GCD更加简单(面向对象)
- 提供了一些用GCD不好实现的功能.
-
苹果推荐使用,使用NSOperation不用关心线程以及线程的生命周期.
-
NSOperation是个抽象类,无法直接使用.因为方法只有声明没有实现。
作为父类使用的.约束子类共有的属性和方法.
-
子类 :
2.1.NSInvocationOperation
//系统提供的
2.2.NSBlockOperation
//系统提供的
2.3. 自定义NSOperation操作默认是异步的.
-
队列 :
NSOperationQueue
队列默认是并发的.
-
核心 :
4.1. GCD的核心 : 将任务添加到队列中
4.2. OP的核心 : 将operation添加到队列中
-
使用步骤:
- 先将需要执行的operation封装到一个NSOperation对象中.创建NSOperation对象.(其实是封装到NSOperation的子类中去。)
- 将NSOperation对象添加到NSOperationQueue中.
- NSOperationQueue会自动将NSOperation取出来.
- 将取出的NSOperation封装的操作自动放到一条对应的新线程中执行.
2.1 NSInvocationOpearion
-
核心:将
操作
添加到队列
-
使用
start
的方法,会在当前线程执行@selector方法
。不会开启新线程。 -
正确开启新线程的姿势
- (void)invocationOperation{
// 将op封装成NSInvocationOperation
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationMethod) object:nil];
// start也会执行,但是不会开启新线程
// [invocationOperation start];
// 创建NSOperationQueue
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//将op添加入队列
[queue addOperation:invocationOperation];
}
2.2 NSBlockOperation
NSOperationQueue
只有一种类型.就是并发队列.- 在实际开发时,如果要使用到
NSOperationQueue,
可以直接定义成全局的队列 - 使用
start
的方法,会在当前线程执行@selector方法
。不会开启新线程。
2.3 NSOperation和GCD的对比
名称 | GCD | NSOperation |
---|---|---|
核心概念 | 将任务添加到队列中 | 把操作添加到队列中 |
语言类型 | C | OC |
推出版本 | iOS4.0 | iOS2.0,在推出GCD之后又对底层进行了重写 |
封装地点 | 任务封装在block中 | 任务凤凰在Operation对象中 |
是否支持停止 | 可以,但是很麻烦 | 可以取消任务,但是正在执行的任务除外 |
优先级 | 只能设置队列的优先级 | 可以设置队列中每一个操作的优先级 |
依赖关系 | 建立任务间依赖关系比较复杂 | 可以跨腿咧设置操作的依赖关系 |
高级功能 | barrier\once\after\group | 最大操作并发数、继续/暂停/取消任务、跨队列设置操作的依赖关系 |
3. NSOperation的高级功能
- 最大操作并发数
- 继续/暂停/取消全部
- 操作的优先级和监听操作执行完成的回调
- 操作间依赖关系
3.1 队列的最大并发数
- 是队列的一个属性.
@property NSInteger maxConcurrentOperationCount;
-
限制同时执行的任务数.
比如,最大并发数设置成3,队列就会保证只同时执行3个任务.从而间接的控制了线程的数量.
-
线程可以复用.而且在线程回收的间隙可以及时的准备线程保证并发性.
- 注意:队列最大并发数不是线程数!!!
- 准备队列:也可以采用
成员变量
的方式定义
3.2 队列的暂停、继续、取消
-
- (BOOL)isSuspended;
暂停和继续队列的属性.YES代表暂停队列,NO代表恢复队列.
-
cancelAllOperations
: 取消队列中的全部操作. -
cancel
: 取消队列中的单个操作. -
队列处于暂停状态时,是没有办法修改
operationCount
数值。
3.2.1 队列暂停
- 将队列挂起之后,队列中的操作就不会被调度,但是正在执行的操作不受影响
operationCount
: 操作计数,没有执行和没有执行完的操作,都会计算在操作计数之内
[self.queue setSuspend:YES];
- 正在执行的操作,没有办法暂停。
3.2.2 队列继续
[self.queue setSuspend:NO];
3.2.3 队列取消
- 一旦调用
cancelAllOperations
方法,队列中的操作,都会被移除,正在执行的操作除外. - 正在执行的操作取消不了,如果要取消,需要自定义
NSOperation
. - 队列取消全部操作时,会有一定的时间延迟
3.3 操作间的依赖关系
- 不能循环建立操作间依赖关系.否则,队列不调度操作执行
- 操作间可以跨队列建立依赖关系
- 要将操作间的依赖建立好了之后,再添加到队列中
- 先建立操作依赖关系
- 再把操作添加到队列
//登录-》付费-》下载
- (void)downloadSongs{
// 创建OP
NSBlockOperation *login = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"login");
}];
NSBlockOperation *pay = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"payment");
}];
NSBlockOperation *download = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download");
}];
// 添加依赖关系
[download addDependency:pay];
[pay addDependency:login];
// 即便op在不同的队列里面,依赖关系依然存在
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperations:@[login,download] waitUntilFinished:NO];
[[NSOperationQueue mainQueue] addOperation:pay];
NSLog(@"Well Done");
}
3.4. 操作的优先级和监听操作执行完成的回调
NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 10; i ++) {
NSLog(@"op %d",i);
}
}];
//监听操作执行完成
[op setCompletionBlock:^{
NSLog(@"------CompletionBlock-------");
}];
4. 列表异步加载网络图片
4.1 SDWebImage加载网络图片
4.2 NSOperation加载网络图片
- 注意:异步下载完图片之后,刷新UI一定要回到主线程
5. GitHub初体验
6. AFN请求json数据
- 导入AFN框架和头文件 #import “AFNetworking.h
- 定义获取json文件的主方法
参数一:URL地址
参数二:不需要
参数三:下载进度
参数四:成功的block
参数五:失败的block
- 使用block字典转模型
数据加载之后,需要刷新页面