iOS - GCD编程

今天温故了一下有关 GCD 编程的相关知识,做了个小Demo,梳理一下。其中也是用到在github上下载的别的大牛封装好的一个GCD源代码,使用很方便,另外里面也用到了SDWebImage这个第三方库,设置网络图片

Demo的源代码放在 Coding.net 上,有需要可以下载查看。

主要内容

  • GCD串行队列与并发队列
  • GCD延时执行
  • GCD线程组
  • GCD定时器
  • GCD信号量
  • GCD综合使用示例

GCD串行队列与并发队列

  • 串行队列:串行队列一次只执行一个线程,按照添加到队列的顺序依次执行
  • 并发队列: 一次可以执行多个线程,线程之间没有先后顺序
  • UI界面所在线程是串行队列

首先创建串行队列

// 创建串行队列
- (void)serailQueue {
    // 创建队列
    GCDQueue *queue = [[GCDQueue alloc] initSerial];
    //执行队列中的线程1
    [queue execute:^{
        NSLog(@"线程1");
    }];

    //执行队列中的线程2
    [queue execute:^{
        NSLog(@"线程2");
    }];

    //执行队列中的线程3
    [queue execute:^{
        NSLog(@"线程3");
    }];

    //执行队列中的线程4
    [queue execute:^{
        NSLog(@"线程4");
    }];

    //执行队列中的线程5
    [queue execute:^{
        NSLog(@"线程5");
    }];
}

viewDidLoad里调用该方法

    // 执行串行队列
    [self serailQueue];

输出结果能看出,线程执行是按顺序依次执行的


输出结果

再看并发队列

// 创建并发队列
- (void)initConcurrent {
    // 创建队列
    GCDQueue *queue = [[GCDQueue alloc] initConcurrent];
    //执行队列中的线程1
    [queue execute:^{
        NSLog(@"线程1");
    }];

    //执行队列中的线程2
    [queue execute:^{
        NSLog(@"线程2");
    }];

    //执行队列中的线程3
    [queue execute:^{
        NSLog(@"线程3");
    }];

    //执行队列中的线程4
    [queue execute:^{
        NSLog(@"线程4");
    }];

    //执行队列中的线程5
    [queue execute:^{
        NSLog(@"线程5");
    }];
}

viewDidLoad里调用该方法

      // 执行并发队列
    [self initConcurrent];

此时的输出结果就不再遵循有序执行了,如下图:


输出结果

UI界面所在的线程是串行队列

这个知识点,也通过写一段代码来加载一张网络图片来演示
首先,声明两个属性

@interface ViewController ()
// GCD 执行队列与UI界面所在线程队列
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIImage     *image;

@end

然后写一个 UI界面所在的线程是串行队列的方法

- (void)UIQueue {
    self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
    self.imageView.center = self.view.center;
    [self.view addSubview:self.imageView];

    [GCDQueue executeInGlobalQueue:^{
        // 处理业务逻辑
        // 网络测试图片url
        NSURL *imgUrl  = [NSURL URLWithString:@"http://o7mwf03sy.bkt.clouddn.com/1460334144112.jpg"];
        // 处理义务逻辑
        NSLog(@"处理业务逻辑");

        [GCDQueue executeInMainQueue:^{
            // 获取图片更新UI
            [self.imageView sd_setImageWithURL:imgUrl];
            // 更新UI
            NSLog(@"更新UI");  
        }];
    }];
}

控制台输出和模拟器效果如下:


结果

从结果我们也不难看出,UI所在的线程队列是串行队列。 所以有个注意的地方就是,在处理业务逻辑的地方,如果耗费的资源和时间较多,就会阻塞主线程的执行。

GCD延时执行

在测试 GCD延时执行 执行前面,我们先来看一下 普通的 NSThread 延时执行 效果:
先写一个方法:

- (void)setNSThread {
    NSLog(@"启动");
   [self performSelector:@selector(threadEvent:)
              withObject:self
              afterDelay:2.f]; // 延时2秒执行
}
// NSThread 延时执行的事件
- (void)threadEvent:(id)sender {
    NSLog(@"NSThread 延时执行事件");
}

NSThread延时执行不仅要写方法,还要写一个执行事件的方法,调用方法运行结果如下:

结果

正好延时了2秒, 再来看 GCD延时执行 方法,代码相对来说比较简单

- (void)setGCDEvent {
     NSLog(@"启动");

    [GCDQueue executeInMainQueue:^{
        NSLog(@"GCD 延时执行事件");
    } afterDelaySecs:2.f];
}

不需要额外的声明方法,调用方法后运行的结果是这样的:


结果

也能达到我们要的延迟效果

这里之前有个误区, 简单看打印时间觉得 NSTimer 延时执行任务是比较精准的,但最近查了一些资料,其实 NSTimer 是不精准的, 因为它不是一个实时系统, 如果此时程序是多线程的, 而 NSTimer 又处在其中一个 runloop 中的某一特定 Mode 中,由于多线程都是分时执行的, 所以当 NSTimer 需要执行任务的时候,很有可能当前线程还在执行任务,此时 NSTimer 则会等待其执行完毕才会执行延时任务,所以说,NSTimer 是不精准的.

除此之外, GCD延迟执行 还可以取消延时事件的执行,只需在执行延时执行前加一行代码

[NSObject cancelPreviousPerformRequestsWithTarget:self];

NSTimer 没有这个方法。

GCD线程组

在开发中,我们可能会有这样的要求:就是在执行完毕线程1和线程2之后,才去执行线程3。有一种方法是设置一个标志,线程1执行完了标志为1,线程2执行完了再+1变成2,线程3执行的时候先判断当前标志的值是多少,再来决定是否执行,貌似这种方法是可行的,但总感觉是不科学的,我们完全可以用GCD线程组来完成这个需求;
首先声明方法

- (void)setGCDGroup {
    // 初始化线程组
    GCDGroup *group = [[GCDGroup alloc] init];

    // 创建一个线程队列
    GCDQueue *queue = [[GCDQueue alloc] initConcurrent];

    // 让线程在group中执行 线程1
    [queue execute:^{
        sleep(1);// 休眠
        NSLog(@"线程1执行完毕");
    } inGroup:group];

    // 让线程在group中执行 线程2
    [queue execute:^{
        sleep(3);
        NSLog(@"线程2执行完毕");
    } inGroup:group];

    // 监听线程组是否执行完毕,然后执行线程3
    [queue notify:^{
        NSLog(@"线程3执行完毕");
    } inGroup:group];
}

为了展示效果,这里让线程1休眠1秒执行,线程2休眠3秒执行(也就是在线程1之后2秒执行),看看是否是我们想要的结果。


结果

可见,GCD 线程组 实现了我们想要的结果.线程组的作用一般就是实现监听响应的线程执行完毕后才执行。

GCDTimer 定时器

同样,为了展示效果,我们还是先来看看NSTimer 的定时执行效果
首先,声明两个私有属性

@property (nonatomic, strong) GCDTimer *gcdTimer;
@property (nonatomic, strong) NSTimer  *normalTimer;

接着,创建 NSTimer方法

- (void)runNSTimer {
    // 初始化并激活NSTimer
    self.normalTimer = [NSTimer scheduledTimerWithTimeInterval:1
                                                        target:self
                                                      selector:@selector(timerEvent)
                                                      userInfo:nil
                                                       repeats:YES];
}
// NSTimer 执行时间
- (void)timerEvent {
    NSLog(@"运行NSTimer");
}

调用该方法执行结果为


结果

再来看GCDTimer
声明方法:

- (void)runGCDTimer {
    // 初始化GCDTimer
    self.gcdTimer = [[GCDTimer alloc] initInQueue:[GCDQueue mainQueue]];

    // 指定间隔时间
    [self.gcdTimer event:^{
        NSLog(@"运行GCDTimer");
    } timeInterval:NSEC_PER_SEC];// NSEC_PER_SEC 宏 代表1 秒

    // 运行GCDTimer
    [self.gcdTimer start];
}

调用该方法执行结果为


结果

可以看到,GCDTimer 虽然在打印时间上有那么一点点误差, 但其实 GCD 的延时操作才是最为精准的,因为NSTimer是运行在Runloop里面的,而 RunLoop 是在 GCD 基础上实现的,所以说 GCD 可算是更加精准. 并且如果把NSTimer运行在一些例如UITableView里面,可能会出现一些奇怪的问题,而GCDTimer不存在这个问题。

GCDSemaphore (GCD信号量)

先来看看我们正常调用一个异步线程方法的输出结果

- (void)setGCDSemaphore {
    // 异步线程 1
    [GCDQueue executeInGlobalQueue:^{
        NSLog(@"异步线程 1");
    }];

    // 异步线程 2
    [GCDQueue executeInGlobalQueue:^{  
        NSLog(@"异步线程 2");
    }];
}

在异步线程执行里,我们是无法确定是哪个线程先执行完毕的,从输出结果就能看出,第一次是这样的:


结果

第二次是这样的:


结果

第三次是这样的:


结果

、、、、、
以此可见,它的执行顺序是不固定的,这个时候,如果使用GCDSemaphore,就能按着我们的意愿顺序执行线程了。
创建GCDSemaphore并在响应位置加入消息指令:

- (void)setGCDSemaphore {
    // 创建GCDSemaphore 信号量
    GCDSemaphore *semaphore = [[GCDSemaphore alloc] init];

    // 异步线程 1
    [GCDQueue executeInGlobalQueue:^{
        NSLog(@"异步线程 1");

        // 发送执行完毕信号
        [semaphore signal];
    }];

    // 异步线程 2
    [GCDQueue executeInGlobalQueue:^{
        // 等待接收执行完毕信号才开始执行进程
        [semaphore wait];

        NSLog(@"异步线程 2");
    }];
    // 作用: 必须线程1先执行完毕,然后在执行线程2
    // 将异步线程转化成同步线程。
}

这样,每次输出顺序就是一样的

结果

这样,当我们有这种按顺序执行的特殊需求时,GCDSemaphore就可以发挥很好的作用了。

GCD 综合使用介绍示例

这里我们通过并发下载三张网络图片的例子来延时 GCD 的强大作用。首先生命三个私有属性

@property (nonatomic, strong) UIImageView *view1;
@property (nonatomic, strong) UIImageView *view2;
@property (nonatomic, strong) UIImageView *view3;

为了等下代码更加方便操作,简化代码,我们声明一个设置view的方法,这里同样用到SDWebImage获取图片。

// 创建 imageview ,需要frame 和 图片网络地址 的参数
- (UIImageView *)createImageViewWithFrame:(CGRect)frame imageUreStr:(NSString *)string {
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
    imageView.alpha = 0.f; // 透明度设置为0
    [imageView sd_setImageWithURL:[NSURL URLWithString:string]];
    [self.view addSubview:imageView];

    return imageView;
}

接着我们创建一个方法,就叫 setGCD 吧。

- (void)setGCD {

    NSString *img1 = @"http://o7mwf03sy.bkt.clouddn.com/1460334156212.jpg";
    NSString *img2 = @"http://o7mwf03sy.bkt.clouddn.com/1460334134611.jpg";
    NSString *img3 = @"http://o7mwf03sy.bkt.clouddn.com/1460334144112.jpg";

    self.view1 = [self createImageViewWithFrame:CGRectMake(50, 50, 200, 100) imageUreStr:img1];
    self.view2 = [self createImageViewWithFrame:CGRectMake(50, 200, 200, 100) imageUreStr:img2];
    self.view3 = [self createImageViewWithFrame:CGRectMake(50, 350, 200, 100) imageUreStr:img3];

    // 在子线程中完成下载  图片1
    [GCDQueue executeInGlobalQueue:^{
        // 在主线程中更新UI
        [GCDQueue executeInMainQueue:^{
          NSLog(@"线程 1 开始执行");
            // 2秒动画显示图片
            [UIView animateWithDuration:2.f animations:^{
                self.view1.alpha = 1.f;
            } completion:^(BOOL finished) {
              NSLog(@"线程 1 执行完毕");
            }];
        }];
    }];

    // 在子线程中完成下载  图片2
    [GCDQueue executeInGlobalQueue:^{
      NSLog(@"线程 2 开始执行");
        // 在主线程中更新UI
        [GCDQueue executeInMainQueue:^{
            // 2秒动画显示图片
            [UIView animateWithDuration:2.f animations:^{
                self.view2.alpha = 1.f;
            } completion:^(BOOL finished) {
              NSLog(@"线程 2 执行完毕");
            }];
        }];
    }];

    // 在子线程中完成下载  图片3
    [GCDQueue executeInGlobalQueue:^{
      NSLog(@"线程 3 开始执行");
        // 在主线程中更新UI
        [GCDQueue executeInMainQueue:^{
            // 2秒动画显示图片
            [UIView animateWithDuration:2.f animations:^{
                self.view3.alpha = 1.f;
            } completion:^(BOOL finished) {
                NSLog(@"线程 3 执行完毕");
            }];
        }];
    }];
}

此时的输出结果是:

输出结果

可见三张图片几乎是同时开始执行, 同时执行完毕。 但是假如我们有一种需求,要三张图片按顺序一张一张的出现,该怎么办呢?
对了,前面介绍过,使用GCDSemaphore信号量!创建 GCDSemaphore 并在响应位置插入信号代码

...
self.view3 = [self createImageViewWithFrame:CGRectMake(50, 350, 200, 100) imageUreStr:img3];

   GCDSemaphore *semaphore = [[GCDSemaphore alloc] init];

   // 在子线程中完成下载  图片1
   [GCDQueue executeInGlobalQueue:^{
       // 在主线程中更新UI
       [GCDQueue executeInMainQueue:^{
           NSLog(@"线程 1 开始执行");
           // 2秒动画显示图片
           [UIView animateWithDuration:2.f animations:^{
               self.view1.alpha = 1.f;
           } completion:^(BOOL finished) {
               NSLog(@"线程 1 执行完毕");
               // 发送执行完毕信号
               [semaphore signal];
           }];
       }];
   }];

   // 在子线程中完成下载  图片2
   [GCDQueue executeInGlobalQueue:^{
       // 阻塞执行,等待消息
       [semaphore wait];
       NSLog(@"线程 2 开始执行");
       // 在主线程中更新UI
       [GCDQueue executeInMainQueue:^{
           // 2秒动画显示图片
           [UIView animateWithDuration:2.f animations:^{
               self.view2.alpha = 1.f;
           } completion:^(BOOL finished) {
               NSLog(@"线程 2 执行完毕");
               // 发送执行完毕信号
               [semaphore signal];
           }];
       }];
   }];

   // 在子线程中完成下载  图片3
   [GCDQueue executeInGlobalQueue:^{
       // 阻塞执行,等待消息
       [semaphore wait];
       NSLog(@"线程 3 开始执行");
       // 在主线程中更新UI
       [GCDQueue executeInMainQueue:^{
           // 2秒动画显示图片
           [UIView animateWithDuration:2.f animations:^{
               self.view3.alpha = 1.f;
           } completion:^(BOOL finished) {
               NSLog(@"线程 3 执行完毕");
           }];
       }];
   }];
}

这样,在线程1执行完毕后发送信号,线程2接收线程1发送的信号再开始执行,完毕之后再给线程3发送信号,以此类推,就能实现按顺序执行线程。输出结果如下:


结果

三个线程的开始执行和结束执行的时间有了明显差别,实现了我们想要的效果。

总结

到这里,我们基本能学到一下几点有关GCD的实用知识点

  • GCD串行队列与并发队列
  • GCD延时执行操作使用
  • GCD线程组的操作使用
  • GCD定时器的使用
  • GCD信号量将异步操作转变成同步操作的使用
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • 从哪说起呢? 单纯讲多线程编程真的不知道从哪下嘴。。 不如我直接引用一个最简单的问题,以这个作为切入点好了 在ma...
    Mr_Baymax阅读 2,714评论 1 17
  • 本文用来介绍 iOS 多线程中 GCD 的相关知识以及使用方法。这大概是史上最详细、清晰的关于 GCD 的详细讲...
    花花世界的孤独行者阅读 491评论 0 1
  • 本文转载自:行走的少年郎的简书:iOS多线程:『GCD』详尽总结 本文用来介绍 iOS 多线程中 GCD 的相关知...
    遠遊旳遊子阅读 1,106评论 0 10
  • 01/12/2018 长达30个小时没有吃饭的我在吃到一碗排骨紫菜汤面的时候,感觉世界都是温暖的。还有清蒸鸡翅等着...
    陌路遇见阅读 132评论 0 0
  • 一、这周的目标达成情况 运动方面: 倒立5分40秒 未达成,客观原因,这周只倒立一次,目前5分25秒。 长跑49分...
    黄道凯阅读 345评论 0 1