GCD

作者: 箫声_筱昇 | 来源:发表于2016-07-02 15:08 被阅读28次
    • 什么是 GCD

    首先 GCD (Grand Central Dispatch)是apple 开发的一个多核编程的解决方法.GCD 和 block 的配合使用,可以方便的进行多线程编程.

    • 应用举例
      例如在 iPhone 上做一个下载网页的功能,该功能非常简单,就是 viewcontroller 上防止一个按钮,点击该按钮时,显示一个转动的圆圈,表示正在进行下载,当下载完成后,将内容加载到界面上的一个文本控件中.
    • 不使用 GCD 前

    功能虽简单,但是我们必须将下载的过程放到后台的线程中,否则的话会阻塞 UI线程
    .所以如果不用 GCD, 我们需要些3个方法

    • someClick 方法是点击按钮后的代码,可以看到我们用 NSInvocationOperation 建了一个后台线程,并且放到 NSOperationQueue 中。后台线程执行 download 方法
    • download 方法处理下载网页的逻辑。下载完成后用 performSelectorOnMainThread 执行 download_completed 方法。
    • download_completed 进行 clear up 的工作,并把下载的内容显示到文本控件中。
    static NSOperationQueue * queue;
    //点击按钮
    - (IBAction)someClick:(id)sender { 
    self.indicator.hidden = NO; 
    [self.indicator startAnimating]; 
    queue = [[NSOperationQueue alloc] init]; 
    NSInvocationOperation * op = [[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil] autorelease]; 
    [queue addOperation:op];
    }
    //下载任务
    - (void)download { 
    NSURL * url = [NSURL URLWithString:@"http://www.youdao.com"]; NSError * error; 
    NSString * data = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
     if (data != nil) { 
    [self performSelectorOnMainThread:@selector(download_completed:) withObject:data waitUntilDone:NO]; 
    } else { 
    NSLog(@"error when download:%@", error); 
    [queue release]; 
    }
    }
    //显示文本
    - (void) download_completed:(NSString *) data { 
    NSLog(@"call back"); 
    [self.indicator stopAnimating]; 
    self.indicator.hidden = YES; 
    self.content.text = data; [queue release];
    }
    
    • 使用 GCD

    如果使用 GCD, 以上的三个方法就可以放到一起

    //原始代码片段一
    self.indicator.hidden = NO;
    [self.indicator startAnimating];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     // 原始代码片段二
    NSURL * url = [NSURL URLWithString:@"http://www.youdao.com"];
    NSErroe *error;
    NSString *data = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
    if (data != nil) {
    //原始代码片段三
    dispatch_async(dispatch_get_main_queue(), ^{ //回主线程
    [self.indicator stopAnimating]; 
    self.indicator.hidden = YES; 
    self.content.text = data;
     });
    } else{
    NSLog(@"error when download:%@", error);
    }
    });
    

    代码块变短,这是因为少了原来3个方法的定义,也少了相互之间需要传递的变量的封装.
    此外呢,代码变清楚了.虽然是异步的代码.但是他们被 GCD 合理的整合在一起.逻辑非常清晰.如果应用上 MVC 模式.我们也可以将 ViewController 层的回调函数用 GCD 的方式传递给 Model 层,这相比之前用@ selector 的方式,代码的逻辑关系会更加清楚.

    block 的定义

    block 的定义有点像函数指针,差别是 block 用^替代了函数指针的*号.

    //申明变量
    - (void)(^loggerBlock)(void){
    //定义
    
    loggerBlock = ^{
    NSLog("hello world");
    };
    //调用
    loggerBlock();
    }
    

    但是在大多数时候,我们通常使用内联的方式来定义 Block, 即将它的程序块写在调用的函数里面,例如这样:

    dispatch_async(dispatch_get_global_queue(0,0),^{
    //something;
    });
    

    所以可以看出, block 有如下特点:
    1.程序块可以在代码总以内联的方式来定义
    2.程序块可以访问在创建它的范围内的可用的变量.

    系统提供的 dispatch 的方法

    在我们使用的时候,为了更加方便的使用 GCD, 苹果提供了一些方法方便我们将 block 放在主线程或后台线程执行,货值延后执行.

    //后台执行
    dispatch_async(dispatch_get_global_queue(0,0),^{
    //something
    });
    //主线程执行
    dispatch_async(dispatch_get_main_queue(),^{
    //something;
    });
    //一次执行;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken,^{
    //code to be executed once
    });
    
    //延迟2秒执行;
    double delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW ,delayInSeconds *NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 
    // code to be executed on the main queue after delay
    });
    

    另外dispatch_queue_t也可以自己定义,如果要自定义 queue, 可以用dispatch_queue_create方法来进行定义.

    dispatch_queue_t urls_queue = dispatch_queue_create("blog.devtang.com", NULL);
    dispatch_async(urls_queue, ^{
    //your code
    });
    dispatch_release(urls_queue);
    

    另外, GCD 还有一些高级用法,例如让后台2个线程并行执行,然后等2个线程都结束后,在汇总执行结果.这个可以用dispatch_group\dispatch_group_async 和 dispatch_group_notify来实现.

    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{ 
    // 并行执行的线程一
    });
    dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{ 
    // 并行执行的线程二
    });
    dispatch_group_notify(group, dispatch_get_global_queue(0,0), ^{ 
    // 汇总结果
    });
    
    • 加入要修改 block 之外的变量
      一般在默认情况下,在程序块访问的外部变量是复制过去的,即写操作不对原变量生效,但是你可以加上__block来使得其写操作生效.
    __block int a =  0;
    void(^foo)(void) = ^{
    a =1;
    }
    foo();
    //在这里,a 的值被修改为1;
    

    -此外使用 block 的另一个用处是可以让程序在后台较长就的运行.在以前,当 app被按 home 键推出后, app仅有最多5秒钟的时候做一些保存或者清理资源的工作.但是应用可以调用 UIApplication 的beginBackgroundTaskWithExpirationHandler方法,让APP 最多有10分钟的时间在后台长久运行,这个时间可以用来做清理本地缓存,发送统计数据等工作.

    让程序在后台长久运行的实例代码如下;

    // AppDelegate.h 文件
    @property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundUpdateTask;
    // AppDelegate.m 文件
    - (void)applicationDidEnterBackground:(UIApplication *)application
    { 
    [self beingBackgroundUpdateTask]; 
    // 在这里加上你需要长久运行的代码 [self endBackgroundUpdateTask];
    }
    
    - (void)beingBackgroundUpdateTask
    { 
    self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ 
    [self endBackgroundUpdateTask]; 
    }];
    }
    
    - (void)endBackgroundUpdateTask{ 
    [[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
     self.backgroundUpdateTask = UIBackgroundTaskInvalid;
    }
    

    例如代码;

    #pragma mark-----|GCD的串行队列,有序的执行一组任务.
    - (void)serialQueue{
        //创建一个串行队列,执行任务的时候,在一个子线程里面.
        /*第一个参数:当前队列的标签(也就是名字).第二个参数是队列类型*/
        dispatch_queue_t serialQueue = dispatch_queue_create("串行", DISPATCH_QUEUE_SERIAL);
        //创建任务
         /*第一个参数:当前任务所在的队列.第二个参数是任务的 block 回调,,逻辑代码就在 block 中*/
        dispatch_async(serialQueue, ^{
            NSLog(@"当前的线程%@",[NSThread currentThread]);
            NSLog(@"小蝌蚪");
        });
        dispatch_async(serialQueue, ^{
            NSLog(@"找妈妈");
            NSLog(@"当前的线程%@",[NSThread currentThread]);
        });
        dispatch_async(serialQueue, ^{
            NSLog(@"十个月的变态发育");
            NSLog(@"当前的线程%@",[NSThread currentThread]);
        });
        dispatch_async(serialQueue, ^{
            NSLog(@"出生");
            NSLog(@"当前的线程%@",[NSThread currentThread]);
        });
        dispatch_async(serialQueue, ^{
            NSLog(@"挂了");
            NSLog(@"当前的线程%@",[NSThread currentThread]);
        });
    }
    #pragma mark---并行队列
    - (void)concurrentQueue{
        dispatch_queue_t concurrentQueue = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);
        /*通过回调函数的方式来实现任务*/
        dispatch_async_f(concurrentQueue, @"天堂",  function);
        
        dispatch_async(concurrentQueue, ^{
            NSLog(@"当前的线程%@",[NSThread currentThread]);
            NSLog(@"睡觉");
        });
        dispatch_async(concurrentQueue, ^{
            NSLog(@"当前的线程%@",[NSThread currentThread]);
            NSLog(@"抽烟");
        });
        dispatch_async(concurrentQueue, ^{
            NSLog(@"当前的线程%@",[NSThread currentThread]);
            NSLog(@"泰国");
        });
        
    }
    #pragma mark---回调函数
    //声明一个函数
    void function(void *contenct){
        NSLog(@"%@",contenct);
        
    }
    #pragma mark-----/*在实际使用中,我们常用的 GCD 方式*/
    -(void)usefulMethod{
        //系统提供的全局队列
        //全局队列优先级
        //预留参数:当前并没有什么用.
        dispatch_queue_t oneQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(oneQueue, ^{
            //在此处执行耗时的操作
            //当操作完成之后,我们需要回到主线程刷新 UI.GCD回主线程方式
            dispatch_async(dispatch_get_main_queue(), ^{
                //已经回到主线程,在此处刷新 UI.
            });
        });
        //系统提供的
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            dispatch_async(dispatch_get_main_queue(), ^{
                
            });
        });
        
    }
    #pragma mark------GCD延时调用
    - (void)afterDelay{
        //DISPATCH_TIME_NOW:从什么时候开始计时
        //第二个参数:从计时开始.多长时间之后执行.
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"那安安");
        });
        
        //声明一个 oncetoken 对象
        //使用oncetoken
        //
        
    //    static dispatch_once_t onceToken;
    //    dispatch_once(&onceToken, ^{
    //        <#code to be executed once#>
    //    });
    }
    

    相关文章

      网友评论

          本文标题:GCD

          本文链接:https://www.haomeiwen.com/subject/hteadttx.html