文章分享至我的个人技术博客: https://cainluo.github.io/15062229631553.html
上一篇, 我们简单的讲了一些使用GCD
的小技巧, 如果没有看的朋友, 可以去玩转iOS开发:实战开发中的GCD Tips小技巧 (一)看.
这次, 我们继续讲解小技巧.
转载声明:如需要转载该文章, 请联系作者, 并且注明出处, 以及不能擅自修改本文.
队列组的灵活使用
通常我们使用队列组执行任务的时候是酱紫的:
- (void)queueGroup {
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"执行任务, 当前线程为:%@", [NSThread currentThread]);
});
}
打印的结果:
2017-09-24 11:34:44.766052+0800 GCD-Tips[59653:3481972] 开始执行
2017-09-24 11:34:44.766606+0800 GCD-Tips[59653:3482075] 执行任务, 当前线程为:<NSThread: 0x604000464980>{number = 3, name = (null)}
但有时候, 我们会遇到一种情况, 就是没有办法直接使用队列组变量, 这个时候, 还有另外一种方式, 就是dispatch_group_enter
和dispatch_group_leave
, 注意, 这两个方法是同时出现的:
- (void)gourpEnterAndLeave {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self urlRequestSuccess:^{
NSLog(@"网络请求成功");
dispatch_group_leave(group);
} failure:^{
NSLog(@"网络请求失败");
dispatch_group_leave(group);
}];
}
- (void)urlRequestSuccess:(void(^)())success
failure:(void(^)())failure {
success();
// failure();
}
打印的结果:
2017-09-24 11:46:16.054410+0800 GCD-Tips[60002:3501228] 开始执行
2017-09-24 11:46:16.054721+0800 GCD-Tips[60002:3501228] 网络请求成功
这样子, 我们就可以把这个网络请求给打包起来, 但这里要注意一下, 不能同时调用两个dispatch_group_leave
, 不然就会挂了.
如果我们要添加结束任务的话, 可以有两种方式:
- dispatch_group_notify
- 当前的队列组任务执行完毕之后, 就会调用
dispatch_group_notify
来通知, 任务已经结束.
- 当前的队列组任务执行完毕之后, 就会调用
- dispatch_group_wait
- 和
dispatch_group_notify
类似, 只不过是在可以添加延迟结束的时间, 但这里需要注意一点,dispatch_group_wait
会阻塞当前线程, 所以不要在主线程
中调用, 不然会阻塞主线程.
- 和
dispatch_barrier_(a)sync使用的注意
我们都知道dispatch_barrier_(a)sync
其实是一个栅栏方法, 它的作用就是在向某个队列插入一个block
, 等到该block
执行完之后, 才会继续执行其他队列, 有点老大的味道.
- (void)queueBarrier {
dispatch_queue_t queue = dispatch_queue_create("queueBarrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"执行一, 当前线程:%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"大佬来了, 当前线程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"执行二, 当前线程:%@", [NSThread currentThread]);
});
}
打印的结果:
2017-09-24 12:44:36.151126+0800 GCD-Tips[61121:3585236] 开始执行
2017-09-24 12:44:36.151579+0800 GCD-Tips[61121:3585334] 执行一, 当前线程:<NSThread: 0x600000462e40>{number = 3, name = (null)}
2017-09-24 12:44:36.152335+0800 GCD-Tips[61121:3585334] 大佬来了, 当前线程:<NSThread: 0x600000462e40>{number = 3, name = (null)}
2017-09-24 12:44:36.154241+0800 GCD-Tips[61121:3585334] 执行二, 当前线程:<NSThread: 0x600000462e40>{number = 3, name = (null)}
PS: dispatch_barrier_(a)sync
只在自己创建的并发队列的才会有效, 如果是在全局并发队列, 串行队列, dispatch_(a)sync
效果是一样的, 这样子的话, 就会容易造成线程死锁, 所以这里要注意.
dispatch_set_context与dispatch_set_finalizer_f
这里要补充两个东西, dispatch_set_context
和dispatch_set_finalizer_f
.
- dispatch_set_context: 可以为队列添加上下文数据
- dispatch_set_finalizer_f: 转移内存管理权限
这里要注意一点dispatch_set_context
接受的context
参数是为C
语言参数, 所以这里写的时候, 要注意一下:
typedef struct _Info {
int age;
} Info;
void cleanStaff(void *context) {
NSLog(@"In clean, context age: %d", ((Info *)context)->age);
//释放,如果是new出来的对象,就要用delete
free(context);
}
- (void)setContext {
dispatch_queue_t queue = dispatch_queue_create("contextQueue", DISPATCH_QUEUE_SERIAL);
// 初始化Data对象, 并且设置初始化值
Info *myData = malloc(sizeof(Info));
myData->age = 100;
// 绑定Context
dispatch_set_context(queue, myData);
// 设置finalizer函数,用于在队列执行完成后释放对应context内存
dispatch_set_finalizer_f(queue, cleanStaff);
dispatch_async(queue, ^{
//获取队列的context数据
Info *data = dispatch_get_context(queue);
//打印
NSLog(@"1: context age: %d", data->age);
//修改context保存的数据
data->age = 20;
});
}
打印一下结果:
2017-09-24 14:24:10.394129+0800 GCD-Tips[61881:3652088] 开始执行
2017-09-24 14:24:10.394547+0800 GCD-Tips[61881:3652274] 1: context age: 100
2017-09-24 14:24:10.394738+0800 GCD-Tips[61881:3652274] In clean, context age: 20
PS: 我们设置了dispatch_set_context
记得一定要释放掉, 不然就会造成内存泄漏.
除了这个之外, 我们还可以对Core Foundation
进行操作, 那么该怎么做呢?
NSObject与dispatch_set_context
这里我们要创建一个Model
类, 继承与NSObject
:
@interface GCDModel : NSObject
@property (nonatomic, assign) NSInteger age;
@end
@implementation GCDModel
- (void)dealloc {
NSLog(@"%@ 释放了", NSStringFromClass([self class]));
}
@end
在这个类里面, 我们就简单操作, 只有一个属性和一个dealloc
方法, 具体操作:
void cleanObjectStaff(void *context) {
GCDModel *model = (__bridge GCDModel *)context;
NSLog(@"In clean, context age: %ld", model.age);
// 释放内存
CFRelease(context);
}
- (void)objectAndContext {
dispatch_queue_t queue = dispatch_queue_create("objectQueue", DISPATCH_QUEUE_SERIAL);
// 初始化Data对象, 并且设置初始化值
GCDModel *model = [[GCDModel alloc] init];
model.age = 20;
// 绑定Context, 这里使用__bridge关键
dispatch_set_context(queue, (__bridge_retained void *)(model));
// 设置finalizer函数,用于在队列执行完成后释放对应context内存
dispatch_set_finalizer_f(queue, cleanObjectStaff);
dispatch_async(queue, ^{
//获取队列的context数据
GCDModel *model = (__bridge GCDModel *)(dispatch_get_context(queue));
//打印
NSLog(@"1: context age: %ld", model.age);
//修改context保存的数据
model.age = 120;
});
}
打印一下结果:
2017-09-24 14:40:34.024509+0800 GCD-Tips[62448:3676807] 开始执行
2017-09-24 14:40:34.024915+0800 GCD-Tips[62448:3676887] 1: context age: 20
2017-09-24 14:40:34.025236+0800 GCD-Tips[62448:3676887] In clean, context age: 120
2017-09-24 14:40:34.025706+0800 GCD-Tips[62448:3676887] GCDModel 释放了
这里我们要解释一下__bridge
关键字:
- __bridge: 只做类型转换, 不做内存管理权限修改.
- __bridge_retained: 内存转换
(CFBridgingRetain)
, 并且把内存管理权限从ARC
里拿到自己手里, 最后释放时要用CFRelease
来释放对象. - __bridge_transfer: 将
Core Foundation
转换成Objective-C
对象(CFBridgingRelease)
, 并且将内存管理的权限交给ARC
.
看到这里应该会有人问, 为什么要把内存管理拿到自己的手里, 而不是交给ARC
?
其实道理很简单, 如果是ARC
管理的话, 一旦它检测到作用于完了之后, 你的对象就会释放了.
那么你就无法将这个Context
添加到队列当中, 一旦添加就会给你报一个野指针
错误, 所以我们为了确保不会被ARC
给释放掉, 我们就需要自己去操作了.
而上面那段代码的解释也很简单:
- 在
dispatch_set_context
时候用__bridge_retained
进行转换, 将Context
的内存管理权限拿到我们自己手上进行管理. - 在队列任务的中, 我们用
dispatch_get_context
来获取context
的时候用关键字__bridge
进行转换, 这样子可以维持context
的内存管理权不变, 防止出了作用域Context
就会被释放掉. - 最后用
CFRelease
来释放掉context
, 这样子就可以保证内存得到释放, 不会造成内存泄漏的问题.
总结
好了, 额外补充的GCD
小技巧到这里就差不多了, 如果以后还有更多的小技巧也会继续更新, 欢迎各位小伙伴们和我分享其他的使用技巧~~
这里推荐几篇文章:
Grand Central Dispatch (GCD) Reference Concurrency Programming Guide Toll-Free Bridged Types
工程地址
项目地址: https://github.com/CainRun/iOS-Project-Example/tree/master/GCD-Tips/GCD-Tips-Two