技术提升iOS开发之GCD

iOS Objective-C GCD 应用篇

2020-10-28  本文已影响0人  just东东

iOS Objective-C GCD 应用篇

本文主要对GCD中的dispatch_semaphore信号量dispatch_barrier栅栏函数dispatch_group调度组dispatch_source调度资源dispatch_once的使用进行举例,当然在某些地方使用相应的方法不一定是最好的,但也就是为了解决一些问题而提出的某种解决方案,仅供参考,也是为了提升自我记忆。

1. 信号量(dispatch_semaphore)

dispatch_semaphore.jpg

1.1 控制任务同时执行的个数

这里通过控制信号量创建时传入的个数来控制任务同时可执行的个数,代码如下:

-(void)semaphoreDemo {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 信号量 -- gcd控制并发数
    // 同步
    //总结:由于设定的信号值为3,先执行三个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过3
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
    
    //任务1
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务1%@", [NSThread currentThread]);
        sleep(1);
        NSLog(@"任务1完成%@", [NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);
    });
    
    //任务2
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务2%@", [NSThread currentThread]);
        sleep(1);
        NSLog(@"任务2完成%@", [NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);
    });
    
    //任务3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务3%@", [NSThread currentThread]);
        sleep(1);
        NSLog(@"任务3完成%@", [NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);
    });
}

其实很好理解,在创建信号量的时候给定的值是几,就有几个任务可以同时执行,如果传1就可以起到同步阻塞的作用。其实还有一种阻塞就是传0,直接就阻塞了,等到signal或者超时的时候就不在阻塞了。

1.2 控制变量在异步累加时符合规则

下面是通过使用信号量创建时传入的值为1来起到阻塞作用。

dispatch_semaphore_t sem = dispatch_semaphore_create(1);
    
__block int a = 0;
    
while (a<5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
        a++;
        dispatch_semaphore_signal(sem);
    });
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}

NSLog(@"外面   a = %d",a);

打印结果:

打印结果.jpg

如果不进行控制,则打印结果必定是一个大于等于5的数值,与我们的期待不是一样的结果。

2. 栅栏函数(dispatch_barrier)

dispatch_barrier.jpg

2.1 token 实例

这里面我们通过常用的网络请求步骤来演示栅栏函数的作用,在常用的网络请求中我们一般都有请求一个token,有了token才能用其进行校验进一步请求其他的网络请求。

请求代码:

/**
 token请求

 @param successBlock 请求回来的token保存 通常还有时效性
 */
- (void)requestToken:(void(^)(id value))successBlock{
    NSLog(@"开始请求token");
    [NSThread sleepForTimeInterval:2];
    successBlock(@"b2a8f8523ab41f8b4b9b2a79ff47c3f1");
}

/**
 头部数据的请求

 @param token token
 @param successBlock 成功数据回调
 */
- (void)requestHeadDataWithToken:(NSString *)token handle:(void(^)(id value))successBlock{
    if (token.length == 0) {
        NSLog(@"没有token,因为安全性无法请求数据");
        return;
    }
    [NSThread sleepForTimeInterval:1];
    successBlock(@"我是头,都听我的");
}
/**
 列表数据的请求
 
 @param token token
 @param successBlock 成功数据回调 --> 刷新列表
 */
- (void)requestListDataWithToken:(NSString *)token handle:(void(^)(id value))successBlock{
    if (token.length == 0) {
        NSLog(@"没有token,因为安全性无法请求数据");
        return;
    }
    [NSThread sleepForTimeInterval:1];
    successBlock(@"我是列表数据");
}

普通方式

-(void)demo1 {
    __weak typeof(self) weakSelf = self;
    
    [self requestToken:^(id value) {
        weakSelf.token = value;

        [weakSelf requestHeadDataWithToken:value handle:^(id value) {
            NSLog(@"%@",value);
            weakSelf.headData = value;
        }];

        [weakSelf requestListDataWithToken:value handle:^(id value) {
            NSLog(@"%@",value);
            weakSelf.listData = value;
        }];
    }];
}

使用dispatch_block_t包装

-(void)demo2 {

    __weak typeof(self) weakSelf = self;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_block_t task = ^{

        dispatch_sync(queue, ^{
            [self requestToken:^(id value) {
                weakSelf.token = value;
            }];
        });

        dispatch_async(queue, ^{
            [weakSelf requestHeadDataWithToken:self.token handle:^(id value) {
                NSLog(@"%@",value);
                weakSelf.headData = value;
            }];
        });

        dispatch_async(queue, ^{
            [weakSelf requestListDataWithToken:self.token handle:^(id value) {
                NSLog(@"%@",value);
                weakSelf.listData = value;
            }];
        });
    };

    dispatch_async(queue, task);
}

使用栅栏函数

-(void)demo3 {
    __weak typeof(self) weakSelf = self;
    
    dispatch_queue_t queue = dispatch_queue_create("aa", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_barrier_async(queue, ^{
        [self requestToken:^(id value) {
            weakSelf.token = value;
        }];
    });

    dispatch_async(queue, ^{
        [weakSelf requestHeadDataWithToken:self.token handle:^(id value) {
            NSLog(@"%@",value);
            weakSelf.headData = value;
        }];
    });

    dispatch_async(queue, ^{
        [weakSelf requestListDataWithToken:self.token handle:^(id value) {
            NSLog(@"%@",value);
            weakSelf.listData = value;
        }];
    });
}

以上列出三种方式:

2.2 图片水印

这里我们通过给图片添加水印在举个例子,比如说我们要给一张图片添加一个水印,这个水印也是个图片,并且需要下载。还有可能在加水印的基础上添加个签章,这个签章也是图片,并且需要下载,这里必须两张图片都下载完毕才能对图片进行处理,实现代码如下:

- (void)demo1{
    // 栅栏函数
    dispatch_queue_t concurrentQueue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(concurrentQueue, ^{
        NSString *logoStr = @"https://image.0.jpg";
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
        UIImage *image = [UIImage imageWithData:data];
        [self.mArray addObject:image];
    });
    
    dispatch_async(concurrentQueue, ^{
        NSString *logoStr = @"https://image.1.jpg";
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
        UIImage *image = [UIImage imageWithData:data];
        [self.mArray addObject:image];
    });
    
    //加载完毕了 栅栏函数上
    __block UIImage *newImage = [UIImage imageNamed:@"xxx"];
    dispatch_barrier_async(concurrentQueue, ^{
        
        for (int i = 0; i<self.mArray.count; i++) {
            UIImage *waterImage = self.mArray[i];
            newImage =[ImageTool WaterImageWithWaterImage:waterImage backImage:newImage waterImageRect:CGRectMake(20, 100*(i+1), 100, 40)];
        }
    });
    
    
    dispatch_async(concurrentQueue, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = newImage;
        });
    });
}

2.3 可变数组

我们知道在iOS中可变的数组和集合是线程不安全的,如果在多线程中操作可变数组就可能出现崩溃,但是我们肯定会出现在多线程中操作数组的需求,并且还希望数组是有序的,那么这里也可以使用栅栏函数解决这个问题,代码如下:

- (void)demo{
    // 线程安全
    dispatch_queue_t concurrentQueue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);

    // 通过循环模拟多线程
    for (int i = 0; i<2000; i++) {
        dispatch_async(concurrentQueue, ^{
            NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
            NSURL *url = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];

            dispatch_barrier_async(concurrentQueue, ^{
                [self.mArray addObject:image];
                NSLog(@"添加了一张图片%d-----%@",i,[NSThread currentThread]);
            });
        });
    }
}

3. 调度组(dispatch_group)

dispatch_group.jpg

这里我们依旧使用水印图片的例子,实现代码如下:

- (void)groupDemo{
    //创建调度组
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_queue_t queue1 = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);

    // SIGNAL
    dispatch_group_async(group, queue, ^{
        NSString *logoStr = @"http:/xxx.jpg";
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
        UIImage *image = [UIImage imageWithData:data];
        [self.mArray addObject:image];
    });
    
    dispatch_group_async(group, queue1, ^{
        NSString *logoStr = @"https://www.baidu.com/img/baidu_resultlogo@2.png";
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
        UIImage *image = [UIImage imageWithData:data];
        [self.mArray addObject:image];
    });

    __block UIImage *newImage = nil;
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"数组个数:%ld",self.mArray.count);
        for (int i = 0; i<self.mArray.count; i++) {
            UIImage *waterImage = self.mArray[i];
            newImage = [ImageTool WaterImageWithWaterImage:waterImage backImage:newImage waterImageRect:CGRectMake(20, 100*(i+1), 100, 40)];
        }
        self.imageView.image = newImage;
    });
}

其实调度组同样也可以实现在所有图片都返回后再进行添加水印的操作,我们一个一个入组,并且通过dispatch_group_notify函数接收组内任务执行完毕的通知然后进行处理。如果我们不使用dispatch_group_async函数也可以使用如下代码:

dispatch_group_enter(group);
dispatch_async(queue1, ^{
    NSString *logoStr = @"https://www.baidu.com/img/baidu_resultlogo@2.png";
    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
    UIImage *image = [UIImage imageWithData:data];
    [self.mArray addObject:image];
    dispatch_group_leave(group);
});

另外还有个dispatch_group_wait函数,两个参数,第一个就是传一个调度组了,第二个就是传一个dispatch_time_t,用于标识指定的等待时间。这里的等待表示,一旦调用dispatch_group_wait函数,该函数就处理调用的状态而不返回值,只有如下情况才返回值:

当指定timeoutDISPATCH_TIME_FOREVER时就意味着永久等待;当指定timeoutDISPATCH_TIME_NOW时就意味不用任何等待即可判定属于Dispatch Group的处理是否全部执行结束;

如果dispatch_group_wait函数返回值不为0,就意味着虽然经过了指定的时间,但Dispatch Group中的操作并未全部执行完毕。

4. 调度资源(dispatch_source)

dispatch_source1.jpg dispatch_source2.jpg

在日常开发中我很少会用到dispatch_source,在dispatch_source中主要用到上图所示的几个函数。下面我们通过一个加载进度条的例子来演示一下dispatch_source的简单应用,代码如下:

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
@property (nonatomic, strong) dispatch_source_t source;
@property (nonatomic, strong) dispatch_queue_t queue;

@property (nonatomic, assign) NSUInteger totalComplete;
@property (nonatomic) BOOL isRunning;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.totalComplete = 0;
    
    self.queue = dispatch_queue_create("queue", 0);

    self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
    
    
    // 保存代码块 ---> 异步 dispatch_source_set_event_handler()
    // 设置取消回调 dispatch_source_set_cancel_handler(dispatch_source_t source,dispatch_block_t _Nullable handler)
    // 封装我们需要回调的触发函数 -- 响应
    dispatch_source_set_event_handler(self.source, ^{
        NSUInteger value = dispatch_source_get_data(self.source); // 取回来值 1 响应式
        self.totalComplete += value;
        NSLog(@"进度:%.2f", self.totalComplete/100.0);
        self.progressView.progress = self.totalComplete/100.0;
    });
    
    self.isRunning     = YES;
    dispatch_resume(self.source);
}

- (IBAction)didClickStartOrPauseAction:(id)sender {
    if (self.isRunning) {// 正在跑就暂停
        dispatch_suspend(self.source);
        dispatch_suspend(self.queue);// mainqueue 挂起
        self.isRunning = NO;
        [sender setTitle:@"暂停中..." forState:UIControlStateNormal];
    }else{
        dispatch_resume(self.source);
        dispatch_resume(self.queue);
        self.isRunning = YES;
        [sender setTitle:@"加载中..." forState:UIControlStateNormal];
    }
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    NSLog(@"点击开始加载");
    for (NSUInteger index = 0; index < 100; index++) {
        dispatch_async(self.queue, ^{
            if (!self.isRunning) {
                NSLog(@"暂停下载");
                return ;
            }
            sleep(1);

            dispatch_source_merge_data(self.source, 1); // source 值响应
        });
    }
}
@end

这里详细说一下dispatch_source_create函数的几个参数,其函数定义如下:

dispatch_source_t
dispatch_source_create(dispatch_source_type_t type,
    uintptr_t handle,
    uintptr_t mask,
    dispatch_queue_t _Nullable queue);
  1. 第一个参数:dispatch_source_type_t type为设置GCD源方法的类型。
  2. 第二个参数:uintptr_t handle Apple的API介绍说,暂时没有使用,传0即可。
  3. 第三个参数:unsigned long mask Apple的API介绍说,使用DISPATCH_TIMER_STRICT,会引起电量消耗加剧,毕竟要求精确时间,所以一般传0即可,视业务情况而定。
  4. 第四个参数:dispatch_queue_t _Nullable queue 队列,将定时器事件处理的Block提交到哪个队列之上。可以传Null,默认为全局队列。注意:当提交到全局队列的时候,时间处理的回调内,需要异步获取主线程,更新UI。

我们可以使用dispatch_source来实现定时器,这个很准确,因为在Runloop中运行循环的时候也是使用的dispatch_source来实现时间的取值的; 所以我们可以去GitHub上找找相关的代码来学习学习。这里就不班门弄斧了。关于dispatch_sourcetype有很多,这里就不一一介绍了,感兴趣的可以自己去看看。

5. dispatch_once

对于dispatch_once我们常用的地方就是单例,我们通过dispatch_once来使我们block中的代码只执行一次。

+(instancetype)shareManager{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}
上一篇下一篇

猜你喜欢

热点阅读