ios-GCD 核心

GCD,全称是 Grand Central Dispatch,纯 C 语言,提供了非常多强大的函数. 是苹果公司为多核的并行运算提出的解决方案, 会自动利用更多的CPU内核(比如双核、四核), 会自动管理线程的生命周期(创建线程、调度任务、销毁线程), 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码.

核心概念

1. 将任务添加到队列,并且指定执行任务的函数

2. 任务使用block封装

        任务的block没有参数也没有返回值

3. 执行任务的函数

       异步dispatch_async

           不用等待当前语句执行完毕,就可以执行下一条语句

           会开启线程执行block的任务

           异步是多线程的代名词

       同步dispatch_sync

             必须等待当前语句执行完毕,才会执行下一条语句

             不会开启线程

            在当前执行block的任务

4. 队列- 系统以先进先出的方式调度队列中的任务执行

        串行队列

              一次只能"调度"一个任务 

             dispatch_queue_create("cn.itcast.queue", NULL);

       并发队列

              一次可以"调度"多个任务

             dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_CONCURRENT);

       主队列

            专门用来在主线程上调度任务的队列

            不会开启线程

              在主线程空闲时才会调度队列中的任务在主线程执行

              dispatch_get_main_queue();

      全局队列

            为了方便程序员的使用,苹果提供了全局队列

dispatch_get_global_queue(0, 0)

            全局队列是一个并发队列

            在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使           用全局队列

小结:

开不开线程由执行任务的函数决定

         异步开,异步是多线程的代名词

         同步不开

开几条线程由队列决定

        串行队列开一条线程

        并发队列开多条线程,具体能开的线程数量由底层线程池决定

                iOS 8.0 之后,GCD 能够开启非常多的线程

                iOS 7.0 以及之前,GCD 通常只会开启 5~6 条线程

- 队列的选择

多线程的目的:将耗时的操作放在后台执行!

串行队列,只开一条线程,所有任务顺序执行

         如果任务有先后执行顺序的要求

         效率低 -> 执行慢 -> "省电"

        有的时候,用户其实不希望太快!例如使用 3G 流量,"省钱"

并发队列,会开启多条线程,所有任务不按照顺序执行

       如果任务没有先后执行顺序的要求

       效率高 -> 执行快 -> "费电"

       WIFI,包月

实际开发中,线程数量如何决定?

       WIFI 线程数6条

       3G / 4G 移动开发的时候,2~3条,再多会费电费钱!


全局队列 和 主队列

全局队列

为了方便程序员的使用,苹果提供了全局队列dispatch_get_global_queue(0, 0)

全局队列是一个并发队列,马上会讲

在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列

主队列

为了方便线程间通讯,异步执行完网络任务,在主线程更新 UI

苹果提供了主队列dispatch_get_main_queue()

主队列专门用于在主线程上调度任务执行

异步执行任务

- (void)gcdDemo1 {

// 1. 全局队列

dispatch_queue_tqueue = dispatch_get_global_queue(0,0);

// 2. 任务

dispatch_block_t task = ^ {

NSLog(@"hello gcd %@", [NSThreadcurrentThread]);

    };

// 3. 将任务添加到队列,并且指定异步执行

dispatch_async(queue, task);

}

注意:如果等待时间长一些,会发现线程的number发生变化,由此可以推断gcd 底层线程池的工作

精简代码

模拟循环添加 10 个异步下载图像的人物

// 精简代码

- (void)gcdDemo2 {

for(NSInteger i =0; i <10; i++) {

        [NSThread sleepForTimeInterval:0.5];

dispatch_async(dispatch_get_global_queue(0,0), ^{

NSLog(@"下载图像 %zd %@", i, [NSThreadcurrentThread]);

        });

    }

}

NSThread 实现等价代码对比

#pragma mark - NSThread 代码

- (void)threadDemo {

for(NSInteger i =0; i <10; i++) {

        [NSThread sleepForTimeInterval:0.5];

        [NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:@(i)];

    }

}

- (void)downloadImage:(id)obj {

NSLog(@"下载图像 %@ %@", obj, [NSThreadcurrentThread]);

}

与NSThread的对比

1.所有的代码写在一起的,让代码更加简单,易于阅读和维护

         NSThread通过@selector指定要执行的方法,代码分散

         GCD通过block指定要执行的代码,代码集中

2.使用GCD不需要管理线程的创建/销毁/复用的过程!程序员不用关心线程的生命周期

3.如果要开多个线程NSThread必须实例化多个线程对象

4.NSThread靠NSObject的分类方法实现的线程间通讯,GCD靠block

线程间通讯

#pragma mark - 线程间通讯

- (void)gcdDemo3 {

dispatch_async(dispatch_get_global_queue(0,0), ^{

// 异步下载图像

NSLog(@"下载图像 %@", [NSThread currentThread]);

// 主线程更新

UIdispatch_async(dispatch_get_main_queue(), ^{

NSLog(@"主线程更新 UI %@", [NSThread currentThread]);

        });

    });

}

以上代码是GCD最常用代码组合!

网络下载图片

- (void)viewDidLoad {

    [super viewDidLoad];

// 1. 准备

URLNSString*urlString =@"http://image.tianjimedia.com/uploadImages/2011/286/8X76S7XD89VU.jpg";

NSURL*url = [NSURLURLWithString:urlString];

// 2. 下载图像

dispatch_async(dispatch_get_global_queue(0,0), ^{

// 1> 所有从网络返回的都是二进制数据

NSData*data = [NSData dataWithContentsOfURL:url];

// 2> 将二进制数据转换成 image

UIImage*image = [UIImage imageWithData:data];

// 3> 主线程更新UI

dispatch_async(dispatch_get_main_queue(), ^{

// 1> 设置图像

_imageView.image= image;

// 2> 调整大小

[_imageView sizeToFit];

// 3> 设置 contentSize

_scrollView.contentSize= image.size;

        });

    });

}

串行队列

特点

以先进先出的方式,顺序调度队列中的任务执行

无论队列中所指定的执行任务函数是同步还是异步,都会等待前一个任务执行完成后,再调度后面的任务

队列创建

dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue",NULL);

串行队列 同步执行

/// 串行队列 - 同步执行

/// 提问:是否开线程?是否顺序执行?come here 的位置?

/// 回答:不会开线程/顺序执行/最后

- (void)gcdDemo1 {

// 1. 串行队列

dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_SERIAL);

// 2. 添加同步执行的任务

for(NSInteger i =0; i <10; i++) {

dispatch_sync(queue, ^{

            [NSThread sleepForTimeInterval:0.5];

NSLog(@"%@ %zd", [NSThread currentThread], i);

        });

    }

NSLog(@"come here");

}

串行队列 异步执行

/// 串行队列 - 异步执行

/// 提问:是否开线程?是否顺序执行?come here 的位置?

/// 回答:会开一条线程/顺序执行/不确定

- (void)gcdDemo2 {

// 1. 串行队列

dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_SERIAL);

// 2. 添加异步执行的任务

for(NSInteger i =0; i <10; i++) {

dispatch_async(queue, ^{

NSLog(@"%@ %zd", [NSThread currentThread], i);

        });

    }

NSLog(@"come here");

}

并发队列

特点

以先进先出的方式,并发调度队列中的任务执行

如果当前调度的任务是同步执行的,会等待任务执行完成后,再调度后续的任务

如果当前调度的任务是异步执行的,同时底层线程池有可用的线程资源,会再新的线程调度后续任务的执行

队列创建

dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);

并发队列 异步执行

/// 并发队列 - 异步执行

/// 提问:是否开线程?是否顺序执行?come here 的位置?

/// 回答:会开线程(取决于底层线程池可用资源)/不是顺序执行/不确定

- (void)gcdDemo2 {

// 1. 并发队列

dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_CONCURRENT);

// 2. 添加异步执行的任务

for(NSInteger i =0; i <10; i++) {

dispatch_async(queue, ^{

NSLog(@"%@ %zd", [NSThread currentThread], i);

        });

    }

NSLog(@"come here");

}

并发队列 同步执行

/// 并发队列 - 同步执行

/// 提问:是否开线程?是否顺序执行?come here 的位置?

/// 回答:不开线程/顺序执行/最后

- (void)gcdDemo1 {

// 1. 并发队列

dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_CONCURRENT);

// 2. 添加同步执行的任务

for(NSInteger i =0; i <10; i++) {

dispatch_sync(queue, ^{

            [NSThread sleepForTimeInterval:0.5];

NSLog(@"%@ %zd", [NSThread currentThread], i); 

      });

    }

NSLog(@"come here");

}

主队列

特点

专门用来在主线程上调度任务的队列

不会开启线程

以先进先出的方式,在主线程空闲时才会调度队列中的任务在主线程执行

如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度

队列获取

主队列是负责在主线程调度任务的

会随着程序启动一起创建

主队列只需要获取不用创建

dispatch_queue_t queue = dispatch_get_main_queue();

主队列,异步执行

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

    [self gcdDemo1];

    [NSThread sleepForTimeInterval:1.0];

NSLog(@"over");}

/// 主队列异步

/// 提问:是否开线程?是否顺序执行?come here 的位置?

/// 回答:不开/顺序/最前面

- (void)gcdDemo1 {

// 1. 主队列

dispatch_queue_t queue = dispatch_get_main_queue();

// 2. 异步任务

for(NSInteger i =0; i <10; i++) {

dispatch_async(queue, ^{

NSLog(@"%@", [NSThread currentThread]);

        });

    }

NSLog(@"come here");

}

在主线程空闲时才会调度主队列中的任务在主线程执行

主队列,同步执行

/// 主队列同步

- (void)gcdDemo2 {

NSLog(@"begin");

// 1. 主队列

dispatch_sync(dispatch_get_main_queue(), ^{

NSLog(@"hello"); 

  });

NSLog(@"end");

}

主队列和主线程相互等待会造成死锁

全局队列

是系统为了方便程序员开发提供的,其工作表现与并发队列一致

全局队列 & 并发队列的区别

全局队列

没有名称

无论 MRC & ARC 都不需要考虑释放

日常开发中,建议使用"全局队列"

并发队列

有名字,和NSThread的name属性作用类似

如果在 MRC 开发时,需要使用dispatch_release(q);释放相应的对象

dispatch_barrier必须使用自定义的并发队列

开发第三方框架时,建议使用并发队列

全局队列 异步任务

/**

提问:是否开线程?是否顺序执行?come here 的位置?

*/

- (void)gcdDemo8 {

// 1. 队列

dispatch_queue_t q = dispatch_get_global_queue(0,0);

// 2. 执行任务

for(int i =0; i <10; ++i) {

dispatch_async(q, ^{

NSLog(@"%@ - %d", [NSThread currentThread], i);

        });

    }

NSLog(@"come here");

}

运行效果与并发队列相同

参数

服务质量(队列对任务调度的优先级)/iOS 7.0 之前,是优先级

iOS 8.0(新增,暂时不能用,今年年底)

QOS_CLASS_USER_INTERACTIVE0x21, 用户交互(希望最快完成-不能用太耗时的操作)

QOS_CLASS_USER_INITIATED0x19, 用户期望(希望快,也不能太耗时)

QOS_CLASS_DEFAULT0x15, 默认(用来底层重置队列使用的,不是给程序员用的)

QOS_CLASS_UTILITY0x11, 实用工具(专门用来处理耗时操作!)

QOS_CLASS_BACKGROUND0x09, 后台

QOS_CLASS_UNSPECIFIED0x00, 未指定,可以和iOS 7.0 适配

iOS 7.0

DISPATCH_QUEUE_PRIORITY_HIGH2 高优先级

DISPATCH_QUEUE_PRIORITY_DEFAULT0 默认优先级

DISPATCH_QUEUE_PRIORITY_LOW(-2) 低优先级

DISPATCH_QUEUE_PRIORITY_BACKGROUNDINT16_MIN 后台优先级

为未来保留使用的,应该永远传入0

结论:如果要适配 iOS 7.0 & 8.0,使用以下代码:dispatch_get_global_queue(0, 0);

单例

有的时候,在程序开发中,有些代码只想从程序启动就只执行一次,典型的应用场景就是“单例”

单例的特点

在内存中只有一个实例

提供一个全局的访问点

提示:单例的使用在 iOS 中非常普遍,以下代码在很多公司的面试中,都要求能够手写出来

- (void)once {

static dispatch_once_t onceToken;

NSLog(@"%zd", onceToken);

// onceToken == 0 的时候执行 block 中的代码

// block 中的代码是同步执行的

dispatch_once(&onceToken, ^{

NSLog(@"执行了");

    });

NSLog(@"come here");

}

dispatch 内部也有一把锁,是能够保证"线程安全"的!而且是苹果公司推荐使用的

以下代码用于测试多线程的一次性执行

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

for(NSInteger i =0; i <10; i++) {

dispatch_async(dispatch_get_global_queue(0,0), ^{

            [self once];

        }); 

  }

}

单例的特点

在内存中只有一个实例

提供一个全局的访问点

懒汉式单例实现

所谓懒汉式单例,表示在使用时才会创建

@implementation NetworkTools

+ (instancetype)sharedTools {

static id instance;

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

        instance = [[self alloc] init];

    });

return instance;

}

@end

面试时只要实现上面sharedTools方法即可

饿汉式单例实现

所谓饿汉式单例,表示尽早地创建单例实例,可以利用initialize方法建立单例

static id instance;

/// initialize 会在类第一次被使用时调用

/// initialize 方法的调用是线程安全的

+ (void)initialize {

NSLog(@"创建单例");

    instance = [[self alloc] init];

}

+ (instancetype)sharedTools {

return instance;

}

你可能感兴趣的:(ios-GCD 核心)