- 全称是Grand Central Dispatch,可译为“牛逼的中枢调度器”
- 纯C语言,提供了非常多强大的函数
GCD的优势 :
- GCD是苹果公司为多核的并行运算提出的解决方案
- GCD会自动利用更多的CPU内核(比如双核、四核)
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
GCD的使用就两个步骤 :
- 定制任务(确定想做的事情)
- 将任务添加到队列中GCD会自动将队列中的任务取出,放到对应的线程中执行,任务的取出遵循队列的FIFO(First in First out)原则:先进先出,后进后出
GCD有两个核心概念 :
概念一 : 任务 (执行什么操作)
GCD中有两个用来执行任务的函数
// 用同步的方式执行任务 :只能在当前线程中执行任务,不具备开启新线程的能力
// sync: 同步 queue:队列 block:任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
// 用异步的方式执行任务 : 可以在新的线程中执行任务,具备开启新线程的能力
// async: 异步 queue:队列 block:任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
概念二 : 队列 :(用来存放任务)
GCD中的队列可以分为两大类型
1: 并发队列(Concurrent Dispatch Queue):
- 可以让多个任务并发同时执行(自动开启多个线程同时执行任务)
- 并发功能只有在异步(dispatch_async)函数下才有效
2: 串行队列(Serial Dispatch Queue)
- 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
同步、异步、并发、串行的作用:
- 同步和异步主要影响:能不能开启新的线程
- 同步:在当前线程中执行任务,不具备开启新线程的能力
- 异步:在新的线程中执行任务,具备开启新线程的能力
- 并发和串行主要影响:任务的执行方式
- 并发:多个任务并发(同时)执行
- 串行:一个任务执行完毕后,再执行下一个任务
并发队列与串行队列
并发队列
GCD的默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建.
- 获得全局并发队列
// 用这个获得全局并发队列{dispatch_get_global_queue}
// 用这个获得优先级{DISPATCH_QUEUE_PRIORITY_DEFAULT}
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 // 后台
并发队列 + 同步函数 : 不会开启线程
并发队列 + 异步函数 : 会开启线程
串行队列
GCD中获得串行有2种途径
- 1 - 使用dispatch_queue_create函数创建串行队列
dispatch_queue_t queue = dispatch_queue_create("c.w.z", NULL);
串行队列 + 异步函数 : 会开启线程
串行队列 + 同步函数 : 不会开启线程
- 2 - 使用主队列(跟主线程相关联的队列)
主队列是GCD自带的一种特殊的串行队列
只要是放在主队列中的任务,不管你是同步还是异步函数全都放在主线程中执行
dispatch_queue_t queue = dispatch_get_main_queue();
主要是主队列:不会开启线程
各种队列的执行效果
线程间的通讯
从子线程回到主线程
执行耗时的异步操作...
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 回到主线程,执行UI刷新操作
dispatch_async(dispatch_get_main_queue(), ^{
});
});
下载图片的操作
延时执行
iOS常见的延时执行有2种方式
// 调用NSObject的方法
// 2秒后再调用self的run方法
self performSelector:@selector(run) withObject:nil afterDelay:2.0;
// 使用GCD函数
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)),`
dispatch_get_main_queue(), ^{
// 2秒后异步执行这里的代码...
});
// 使用NSTimer
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];
只执行一次的代码
// 运行时只执行一次
// 使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});
队列组
Warning:有这么1种需求:
首先:分别异步执行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(), ^{
// 等前面的异步操作都执行完毕后,回到主线程...
});
代码详细实现:
需求:在子线程中下载2张图片.分别显示在屏幕的左右.
分析:由于下载图片属于耗时操作,所以要在子线程中操作.等2张图片都下载好后,在回到主线程加载.
// 点击屏幕后调用
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self group];
}
// 队列组
- (void)group
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 创建一个队列组
dispatch_group_t group = dispatch_group_create();
// 1.下载图片1
dispatch_group_async(group, queue, ^{
// 图片的网络路径
NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
// 加载图片
NSData *data = [NSData dataWithContentsOfURL:url];
// 生成图片
self.image1 = [UIImage imageWithData:data];
});
// 2.下载图片2
dispatch_group_async(group, queue, ^{
// 图片的网络路径
NSURL *url = [NSURL URLWithString:@"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];
// 加载图片
NSData *data = [NSData dataWithContentsOfURL:url];
// 生成图片
self.image2 = [UIImage imageWithData:data];
});
// 3.将图片1、图片2合成一张新的图片
dispatch_group_notify(group, queue, ^{
// 开启新的图形上下文
UIGraphicsBeginImageContext(CGSizeMake(100, 100));
// 绘制图片
[self.image1 drawInRect:CGRectMake(0, 0, 50, 100)];
[self.image2 drawInRect:CGRectMake(50, 0, 50, 100)];
// 取得上下文中的图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 结束上下文
UIGraphicsEndImageContext();
// 回到主线程显示图片
dispatch_async(dispatch_get_main_queue(), ^{
// 4.将新图片显示出来
self.imageView.image = image;
});
});
}
显示结果
barrier
GCD中还有个用来执行任务的函数barrier(栅栏/障碍物)
dispatch_barrier_async(dispatch_queue_t queue>, ^(void)block)
- 在前面的任务执行结束后它才执行,而且它后面的的任务等它执行完之后才会执行
- 这个queue不能是全局的并发队列
他会在执行完 barrier上面的1和2后在执行barrier后面的3和4
NSOperation:
是在GCD的基础上进行的一层面向对象的包装.
核心概念:
任务和队列.和GCD基本上是一样的,只不过更加的面向对象.用起来比较爽.
NSOperation的作用
- 配合使用NSOperation和NSOperationQueue也能实现多线程编程
- NSOperation:就是任务
- NSOperationQueue:就是队列
NSOperation的子类
- NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类
使用NSOperation子类的方式有3种:
- NSInvocationOperation,
- NSBlockOperation,
- 自定义子类继承NSOperation,实现内部相应的方法;
NSOperation和NSOperationQueue实现多线程的具体步骤:
- 先将需要执行的操作封装到一个NSOperation对象中,
- 然后将NSOperation对象添加到NSOperationQueue中,
- 系统会自动将NSOperationQueue中的NSOperation取出来,
- 将取出的NSOperation封装的操作放到一条新线程中执行;
1 - NSInvocationOperation(比较少用)
// 1.创建NSInvocationOperation对象
- (id)initWithTarget:(id)target selector:(SEL)sel object:
- (id)arg;
// 2. 调用start方法开始执行操作
- (void)start;
一旦执行操作,就会调用target的sel方法
- 默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作
-
只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作
2 - NSBlockOperation
// 1. 创建NSBlockOperation对象
+ (id)blockOperationWithBlock:(void (^)(void))block;
// 2. 通过addExecutionBlock:方法添加更多的操作
- (void)addExecutionBlock:(void (^)(void))block;
-
只要NSBlockOperation封装的操作数 > 1,就会异步执行操作
3 -自定义NSOperation
- 首先,在控制器中,将操作添加到队列中
- 然后会调用自定义类的
- main
方法,执行- main
方法中的任务
1.首先新建一个类,继承自NSOperation
2.在.m文件中重写- main
方法 (不是main函数而是- main
方法)
3.将任务写入- main
方法中
NSOperationQueue
- NSOperationQueue的作用
- NSOperation可以调用
start方法
来执行任务,但默认是同步执行
的 - 如果将NSOperation添加到
NSOperationQueue(操作队列)中
,系统会自动异步执行NSOperation中的操作
// 添加操作到NSOperationQueue中
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;
NSOperationQueue的队列类型
主队列:
凡是放到了主队列的任务(NSOperation),都会放到主线程中执行
[NSOperationQueue mainQueue];
其他队列(串行/并发):
同时包含:串行 / 并发功能
添加这种队列中的任务,就会自动放到子线程中执行
[NSOperationQueue alloc] init];
最大并发数
同时执行的任务数:比如同时开3个线程执行3个任务,并发数就是3
// 最大并发数的相关方法
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
队列的取消、暂停、恢复
// 取消队列的所有操作
- (void)cancelAllOperations;
__提示:
也可以调用NSOperation的- (void)cancel方法取消单个操作
// 暂停和恢复队列(YES代表暂停队列,NO代表恢复队列)
- (void)setSuspended:(BOOL)b;
- (BOOL)isSuspended;
经常通过`- (BOOL)isCancelled`方法检测操作是否被取消,对取消做出响应
suspended 暂定/挂起
__warning:#cancel
官方建议:执行完一段耗时操作的后面最好加上是否点击了取消的判断.
人为控制取消操作.以免,全部执行完,还没有取消掉.
操作优先级
设置NSOperation在queue中的优先级,可以改变操作的执行优先级
- (NSOperationQueuePriority)queuePriority;
- (void)setQueuePriority:(NSOperationQueuePriority)p;
##优先级的取值
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
操作依赖
- NSOperation之间可以设置依赖来保证执行顺序
比如一定要让操作A执行完后,才能执行操作B,可以这么写
// 操作B依赖于操作A
operationB addDependency:operationA;
-
可以在不同queue的NSOperation之间创建依赖关系
操作的监听
// 可以监听一个操作的执行完毕
- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;
自定义NSOperation的步骤很简单
重写`-(void)main`方法,在里面实现想执行的任
重写- (void)main
方法的注意点:
- 自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
- 经常通过
-(BOOL)isCancelled
方法检测操作是否被取消,对取消做出响应
GCD和NSOperation的区别
- GCD是基于C的底层API
- NSOperation属于object-c类。
- iOS首先引入的是NSOperation,IOS4之后引入了GCD和NSOperationQueue并且其内部是用GCD实现的。
NSOperation:
1 - NSOperation拥有更多的函数可用,具体查看API。
2 - 在NSOperationQueue中,可以建立各个NSOperation之间的依赖关系。
3 - 有KVO,可以监测operation是否
--------------------------正在执行isExecuted
、
--------------------------是否结束isFinished
、
--------------------------是否取消isCanceld
;
4 - NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。
NSOperationQueue的队列类型
- 主队列
- [NSOperationQueue mainQueue]
- 凡是添加到主队列中的任务(NSOperation),都会放到主线程中执行
- 非主队列(其他队列)
- [[NSOperationQueue alloc] init]
- 同时包含了:串行、并发功能
- 添加到这种队列中的任务(NSOperation),就会自动放到子线程中执行
GCD:
1 - 主要与block结合使用。代码简洁高效。
2 - GCD也可以实现复杂的多线程应用,主要是建立个个线程时间的依赖关系这类的情况,但是需要自己实现相比NSOperation要复杂。具体使用哪个,依需求而定。
GCD的队列类型
- 并发队列
- 全局
- 自己创建的
- 串行队列
- 主队列
- 自己创建的
从个人使用的感觉来看,比较合适的用法是:除了依赖关系尽量使用GCD,因为苹果专门为GCD做了性能上面的优化。
GCD实现单例模式
单例模式 :
其实就是一种设计模式
适用场合
单例模式一般会在整个应用程序中,共享一份资源(这份资源只需要创建初始化1次)的时候使用.
比如:
- 一个APP中有一个界面是提供公司的信息的,或者创建账号的界面
- 那么,无论是点击添加好友或者登录,都会跳转到(访问)创建账号这个界面
- 那么就可以把这个界面写成单例模式,保证属性以及各种信息都一样
- 这样不仅方便地控制了实例个数,并节约系统资源
比如:
Person *p1 = [Person alloc] init];
Person *p2 = [Person alloc] init];
Person *p3 = [Person alloc] init];
Person *p4 = [Person alloc] init];
从上图我们可以看到:
- 打印出来的p1,p2,p3,p4的内存地址是不一样的
- 因为每次调用alloc会分配新的内存空间.
- 但是如果实现了单例模式,就能可以保证在程序运行过程中,
- 无论alloc了多少次,Person这个类都是同一个对象.
那么我们怎么样来实现单例模式呢?
第一种方法:重写+ allocWithZone
- 之所以我们要实现单例模式,是希望无论
alloc
了多少次,都只产生一份内存
- 那么我们就要从
alloc
这里下手 - 最容易想到的办法就是在内部直接重写
alloc
- 而且alloc内部会调用
+ allocWithZone
这个方法 - 无论别人在外界调用
alloc
还是调用+ allocWithZone
都会来到+ allocWithZone
这个方法 - 所以要重写
+ allocWithZone
- 上图可以看出我们打印出来的内存地址的结果都是一样的.
接下来我们来通过打印属性值来验证下:
第二种方法:[UIApplication sharedApplication]
第三种方法:copy
那么,我们应该这样做
- 看到这里可能有一个疑问,这里直接返回_Person的话值不会为空么?
- 答案是肯定不会为空的.
- 因为调用copy方法,肯定是有对象在调用的
- 所以对象都有值了,那么这个方法里面肯定也是回有值的
将以上的结合起来才属于一套完整的单例模式
那么以后一个工程中可能会有很多单例,总不能一个一个改吧?
那么我们可以将单例弄成宏的形式,以后遇到了直接拖入工程就可以了
那么 我们来看下效果:
.h文件
.m文件
实现的效果
单例模式在ARC\MRC环境下的写法有所不同
需要编写2套不同的代码
可以用宏判断是否为ARC环境
#if __has_feature(objc_arc)
// ARC
#else
// MRC
#endif
ARC中,单例模式的实现
1. 在.m中保留一个全局的`static`的实例
static id _instance;
// 重写allocWithZone:方法,在这里创建唯一的实例(注意线程安全)
+(id)allocWithZone:(struct _NSZone *)zone
{
@synchronized(self) {
if (!_instance) {
_instance = [super allocWithZone:zone];
}
}
return _instance;
}
2. 提供1个类方法让外界访问唯一的实例
+ (instancetype)sharedSoundTool{
@synchronized(self) {
if (!_instance) {
_instance = [[self alloc] init];
}
}
return _instance;
}
3. 实现copyWithZone:方法
- (id)copyWithZone:(struct _NSZone *)zone
{
return _instance;
}
MRC中,单例模式的实现(比ARC多了几个步骤)
// 实现内存管理方法
- (id)retain {
return self;
}
- (NSUInteger)retainCount{
return 1;
}
- (oneway void)release {
}
- (id)autorelease {
return self;
}