文章分享至个人我的技术博客: https://cainluo.github.io/15062229631553.htmlhtml
上一篇, 咱们简单的讲了一些使用GCD
的小技巧, 若是没有看的朋友, 能够去玩转iOS开发:实战开发中的GCD Tips小技巧 (一)看.git
此次, 咱们继续讲解小技巧.github
转载声明:如须要转载该文章, 请联系做者, 而且注明出处, 以及不能擅自修改本文.vim
一般咱们使用队列组执行任务的时候是酱紫的:bash
- (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
, 否则就会挂了.app
若是咱们要添加结束任务的话, 能够有两种方式:async
dispatch_group_notify
来通知, 任务已经结束.dispatch_group_notify
相似, 只不过是在能够添加延迟结束的时间, 但这里须要注意一点, dispatch_group_wait
会阻塞当前线程, 因此不要在主线程
中调用, 否则会阻塞主线程.咱们都知道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
接受的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
进行操做, 那么该怎么作呢?
这里咱们要建立一个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
关键字:
(CFBridgingRetain)
, 而且把内存管理权限从ARC
里拿到本身手里, 最后释放时要用CFRelease
来释放对象.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