玩转iOS开发:实战开发中的GCD Tips小技巧 (二)

文章分享至个人我的技术博客: 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_enterdispatch_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_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_contextdispatch_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


最后

码字很费脑, 看官赏点饭钱可好

微信

支付宝
相关文章
相关标签/搜索