ios-GCD 核心

作者: Clark_new | 来源:发表于2017-07-21 10:36 被阅读0次

    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 核心

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