GCD 使用总结(一) 介绍了 GCD 大概情况,现在看下在项目应用中,哪一些地方用到了 GCD 的能力.
1.延后加载 >>> dispatch_after
举例:在某些时候,我们加载一个 view, 希望用户注意到我们想要突出的部分,但是 view 上的东西太多,一起加载出现,用户很有可能会错过我们想要提醒的部分.
使用 dispatch_after 可以实现这个功能. dispatch_after能够延后执行代码. 将需要的 view 先加载出来, 在加载其他 view. 这样用户就能注意到我们想要提醒的部分了.
UIView *view1 = [[UIView alloc]initWithFrame:CGRectMake(60, 60, 40, 40)];
UIView *view2 = [[UIView alloc]initWithFrame:CGRectMake(60, 260, 40, 40)];
UIView *view3 = [[UIView alloc]initWithFrame:CGRectMake(160, 60, 40, 40)];
UIView *view4 = [[UIView alloc]initWithFrame:CGRectMake(160, 260, 40, 40)];
view1.backgroundColor = [UIColor greenColor];
view2.backgroundColor = [UIColor greenColor];
view3.backgroundColor = [UIColor greenColor];
view4.backgroundColor = [UIColor greenColor];
UIView *centerView = [[UIView alloc]initWithFrame:CGRectMake(110, 160, 40, 40)];
centerView.backgroundColor = [UIColor redColor];
double delayInSeconds = 2.0;
// dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); // 1
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ // 2
NSLog(@"当前线程:%@",[NSThread currentThread]);
[self addSubview:view1];
[self addSubview:view2];
[self addSubview:view3];
[self addSubview:view4];
});
NSLog(@"主线程:%@",[NSThread currentThread]);
[self addSubview:centerView];
界面比较粗燥,但是不重要. 红色的方块先出现,然后在加载四个绿色的方块.
2.异步执行
异步执行可以让 CPU 执行更多的事情,让 APP 跑起来更流畅, 本质上就是开辟子线程,让 CPU 合理的干更多的活
举例: 网络请求
绝大多数网络请求都是耗时操作,尤其下载音视频,图片等. 如果用主线程做这些事情就比较蠢了.
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dispatch_async(globalQueue, ^{
// 一个异步的任务,如网络请求,耗时的文件操作等等
// ...
NSLog(@"当前线程:%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
// UI刷新 或其他主线程操作
// ...
});
});
绝大多数网络请求都是耗时操作,尤其下载音视频,图片等. 如果用主线程做这些事情就比较蠢了.
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dispatch_async(globalQueue, ^{
// 一个异步的任务,如网络请求,耗时的文件操作等等
// ...
NSLog(@"当前线程:%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
// UI刷新 或其他主线程操作
// ...
});
});
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dispatch_async(globalQueue, ^{
// 一个异步的任务,如网络请求,耗时的文件操作等等
// ...
NSLog(@"当前线程:%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
// UI刷新 或其他主线程操作
// ...
});
});
3.单例安全
不知道为什么,单例在 ios 的开发中多的可怕.
单例的特点之一就是 在程序运行的过程中一直存在,且唯一.
简单的举例就是在第一次调用创建单例的时候 [alloc init]的时候 还没进行,第二次调用就触发又会走进 [alloc init]的方法.这样就会创建两个单例. 单例这就不唯一了;
如下代码:
这就是多线程 临界区 的问题. 像这样某一段代码被两个线程执行,就会产生不可预知的事情
GCD可以解决这个问题.
单例的特点之一就是 在程序运行的过程中一直存在,且唯一.
+ (instancetype)sharedManager
{
static PhotoManager *sharedPhotoManager = nil;
if (!sharedPhotoManager) {
sharedPhotoManager = [[PhotoManager alloc] init];
}
return sharedPhotoManager;
}
像这种通过断定 存不存在来决定是否创建,看起来没毛病. 但是在单例在多线程调用的时候就会有问题.
简单的举例就是在第一次调用创建单例的时候 [alloc init]的时候 还没进行,第二次调用就触发又会走进 [alloc init]的方法.这样就会创建两个单例. 单例这就不唯一了;
如下代码:
+ (instancetype)sharedManager
{
static PhotoManager *sharedPhotoManager = nil;
if (!sharedPhotoManager) {
[NSThread sleepForTimeInterval:2];
sharedPhotoManager = [[PhotoManager alloc] init];
NSLog(@"Singleton has memory address at: %@", sharedPhotoManager);
[NSThread sleepForTimeInterval:2];
sharedPhotoManager->_photosArray = [NSMutableArray array];
}
return sharedPhotoManager;
}
// 外部调用单例的代码
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[PhotoManager sharedManager];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[PhotoManager sharedManager];
});
如果外部此时同事调用两次单例的创建方法,就会创建两个单例. 手敲代码 打印一遍两个单例的地址 就可以发现地址不同.
这就是多线程 临界区 的问题. 像这样某一段代码被两个线程执行,就会产生不可预知的事情
GCD可以解决这个问题.
+ (instancetype)sharedManager
{
static PhotoManager *sharedPhotoManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedPhotoManager = [[PhotoManager alloc] init];
});
return sharedPhotoManager;
}
dispatch_once_t //一个整形数据 ,
dispatch_once(&onceToken, ^{// dispatch_once在运行的时候,取onceToken的值,0就执行 block ,非零就跳过. 所以这个也重新创建单例提供了可能,
// 必要的时候 清零 onceToken 重新创建 单例
});
4.处理读者 和 写者的问题
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
类似对于一个可变内存,多个线程读取没什么问题,但是当读取的时候 另一个线程进行修改时,就很危险了. 而且目前多数单例的可变属性,都没有做到保护.
通过 GCD 的栅栏(dispatch barriers
), 可以有效防止这类读取可能被其他线程修改的数据时,导致的不可思议的问题.
- (void)addPerson:(Person *)person
{
if (person) { // 1
dispatch_barrier_async(self.concurrentPersonQueue, ^{ // 2
[_personArr addObject:person]; // 3
dispatch_async(dispatch_get_main_queue(), ^{ // 4
});
});
}
}
self.concurrentPersonQueue // 初始化为异步的并发队列
在这个并发队列中, barrier 持有的 block 永远都只会独自执行.