zoukankan      html  css  js  c++  java
  • 多线程的之GCD的介绍

    iOS GCD

    字数4293 阅读2718 评论18 

    一、简介

    1、什么是GCD?

    • 全称是Grand CentralDispatch,可译为“伟大的中枢调度器”
    • 纯C语言,提供了非常多强大的函数

    2、GCD的优势

    • GCD是苹果公司为多核的并行运算提出的解决方案
    • GCD会自动利用更多的CPU内核(比如双核、四核)
    • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程),相比NSThread需要手动管理线程声明周期
    • 只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

    3、GCD中有2个核心概念:任务、队列

    • 任务:执行什么操作
    • 队列:用来存放任务
      • 队列的类型:并发队列 与 串行队列
    • 将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行,任务的取出遵循队列的 FIFO原则:先进先出,后进后出

    使用.png

    二、执行任务:同步、异步、栅栏

    同步 dispatch_sync : 只能在当前线程中执行任务,不具备开启新线程的能力

    dispatch_sync(dispatch_queue_tqueue, dispatch_block_tblock);
    • 注意:
      • 使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列

    异步 dispatch_async:可以在新的线程中执行任务,具备开启新线程的能力

    dispatch_`async`(dispatch_queue_tqueue, dispatch_block_tblock);

    另外一个执行任务的方法:dispatch_barrier_async 栅栏

    • 场景:在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
    • 注意:这个queue不能是全局的并发队列

      dispatch_barrier_async(dispatch_queue_tqueue, dispatch_block_tblock);
    • 下面有详细说明

    三、队列:串行 与 并行

    • 使用dispatch_queue_create函数创建队列
    dispatch_queue_t dispatch_queue_create(
       const char *label, //队列名称 
       dispatch_queue_attr_t  attr //队列的类型
    );

    1、并发队列:(ConcurrentDispatch Queue)

    • 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
      • 注意:并发功能只有在异步(dispatch_async)函数下才有效,因为异步函数才具备开启新线程的能力,而同步函数只能在当前线程中执行不具备开启线程的能力。
    • 并发队列获取

      • 手动创建并发队列 dispatch_queue_create

        dispatch_queue_t queue = dispatch_queue_create("队列名称", DISPATCH_QUEUE_CONCURRENT);
      • GCD默认已经提供了全局的并发队列,供整个应用使用,可以无需手动创建,使用dispatch_get_global_queue函数获得全局的并发队列

      // 获得全局并发队列
      dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    2、串行队列:(SerialDispatch Queue)

    • 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
    • 注意:
      • 使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列
    • GCD中获得串行有2种途径

    • 串行队列创建

      • 使用dispatch_queue_create函数

        //创建串行队列(队列类型传递NULL或者DISPATCH_QUEUE_SERIAL)
        dispatch_queue_tqueue = dispatch_queue_create("队列名称",NULL);
      • 主队列(是GCD自带的一种特殊的串行队列)

        • 放在主队列中的任务,都会放到主线程中执行
        dispatch_queue_tqueue = dispatch_get_main_queue();

    3、同步 / 异步 与 并发 / 串行 如何区分

    • 同步和异步主要影响:能不能开启新的线程

      • 同步:只是在当前线程中执行任务,不具备开启新线程的能力
      • 异步:可以在新的线程中执行任务,具备开启新线程的能力
    • 并发和串行主要影响:任务的执行方式

      • 并发:允许多个任务并发(同时)执行
      • 串行:一个任务执行完毕后,再执行下一个任务

    四、GCD基本使用

    #import "ViewController.h"
    
    
    @implementation ViewController
    
    /*
     如果是在子线程中调用 同步函数 + 主队列, 那么没有任何问题
     */
    - (void)syncMain2
    {
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_async(queue, ^{
            // block会在子线程中执行
            //        NSLog(@"%@", [NSThread currentThread]);
    
            dispatch_queue_t queue = dispatch_get_main_queue();
            dispatch_sync(queue, ^{
                // block一定会在主线程执行
                NSLog(@"%@", [NSThread currentThread]);
            });
        });
        NSLog(@"------------");
    }
    /*
     如果是在主线程中调用同步函数 + 主队列, 那么会导致死锁
     导致死锁的原因:
     sync函数是在主线程中执行的, 并且会等待block执行完毕. 先调用
     block是添加到主队列的, 也需要在主线程中执行. 后调用
     */
    - (void)syncMain
    {
        NSLog(@"%@", [NSThread currentThread]);
        // 主队列:
        dispatch_queue_t queue = dispatch_get_main_queue();
    
        //  如果是调用 同步函数, 那么会等同步函数中的任务执行完毕, 才会执行后面的代码
        // 注意: 如果dispatch_sync方法是在主线程中调用的, 并且传入的队列是主队列, 那么会导致死锁
        dispatch_sync(queue, ^{
            NSLog(@"----------");
            NSLog(@"%@", [NSThread currentThread]);
        });
        NSLog(@"----------");
    }
    /*
     异步 + 主队列 : 不会创建新的线程, 并且任务是在主线程中执行
     */
    - (void)asyncMain
    {
        // 主队列:
        // 特点: 只要将任务添加到主队列中, 那么任务"一定"会在主线程中执行 
        无论你是调用同步函数还是异步函数
        dispatch_queue_t queue = dispatch_get_main_queue();
    
        dispatch_async(queue, ^{
            NSLog(@"%@", [NSThread currentThread]);
        });
    }
    /*
     同步 + 并发 : 不会开启新的线程
     妻管严
     */
    - (void)syncConCurrent
    {
        // 1.创建一个并发队列
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
        // 2.将任务添加到队列中
        dispatch_sync(queue, ^{
            NSLog(@"任务1  == %@", [NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"任务2  == %@", [NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"任务3  == %@", [NSThread currentThread]);
        });
    
         NSLog(@"---------");
    }
    /*
     同步 + 串行: 不会开启新的线程
     注意点: 如果是调用 同步函数, 那么会等同步函数中的任务执行完毕, 才会执行后面的代码
     */
    - (void)syncSerial
    {
        // 1.创建一个串行队列
        // #define DISPATCH_QUEUE_SERIAL NULL
        // 所以可以直接传NULL
        dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", NULL);
    
        // 2.将任务添加到队列中
        dispatch_sync(queue, ^{
            NSLog(@"任务1  == %@", [NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"任务2  == %@", [NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"任务3  == %@", [NSThread currentThread]);
        });
    
        NSLog(@"---------");
    }
    /*
     异步 + 串行:会开启新的线程
    但是只会开启一个新的线程
     注意: 如果调用 异步函数, 那么不用等到函数中的任务执行完毕, 就会执行后面的代码
     */
    - (void)asynSerial
    {
        // 1.创建串行队列
        dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", DISPATCH_QUEUE_SERIAL);
        /*
         能够创建新线程的原因:
         我们是使用"异步"函数调用
         只创建1个子线程的原因:
         我们的队列是串行队列
         */
        // 2.将任务添加到队列中
        dispatch_async(queue, ^{
            NSLog(@"任务1  == %@", [NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"任务2  == %@", [NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"任务3  == %@", [NSThread currentThread]);
        });
    
        NSLog(@"--------");
    }
    
    /*
     异步 + 并发 : 会开启新的线程
     如果任务比较多, 那么就会开启多个线程
     */
    - (void)asynConcurrent
    {
        /*
         执行任务
         dispatch_async
         dispatch_sync
         */
    
        /*
         第一个参数: 队列的名称
         第二个参数: 告诉系统需要创建一个并发队列还是串行队列
         DISPATCH_QUEUE_SERIAL :串行
         DISPATCH_QUEUE_CONCURRENT 并发
         */
        //    dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", DISPATCH_QUEUE_CONCURRENT);
    
        // 系统内部已经给我们提供好了一个现成的并发队列
        /*
         第一个参数: iOS8以前是优先级, iOS8以后是服务质量
         iOS8以前
         *  - DISPATCH_QUEUE_PRIORITY_HIGH          高优先级 2
         *  - DISPATCH_QUEUE_PRIORITY_DEFAULT:      默认的优先级 0
         *  - DISPATCH_QUEUE_PRIORITY_LOW:          低优先级 -2
         *  - DISPATCH_QUEUE_PRIORITY_BACKGROUND:
    
         iOS8以后
         *  - QOS_CLASS_USER_INTERACTIVE  0x21 用户交互(用户迫切想执行任务)
         *  - QOS_CLASS_USER_INITIATED    0x19 用户需要
         *  - QOS_CLASS_DEFAULT           0x15 默认
         *  - QOS_CLASS_UTILITY           0x11 工具(低优先级, 苹果推荐将耗时操作放到这种类型的队列中)
         *  - QOS_CLASS_BACKGROUND        0x09 后台
         *  - QOS_CLASS_UNSPECIFIED       0x00 没有设置
    
         第二个参数: 废物
         */
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
        /*
         第一个参数: 用于存放任务的队列
         第二个参数: 任务(block)
    
         GCD从队列中取出任务, 遵循FIFO原则 , 先进先出
         输出的结果和苹果所说的原则不符合的原因: CPU可能会先调度其它的线程
    
         能够创建新线程的原因:
         我们是使用"异步"函数调用
         能够创建多个子线程的原因:
         我们的队列是并发队列
         */
        dispatch_async(queue, ^{
            NSLog(@"任务1  == %@", [NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"任务2  == %@", [NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"任务3  == %@", [NSThread currentThread]);
        });
    }
    @end

    线程间通信

    #import "ViewController.h"
    
    @interface ViewController ()
    @property (weak, nonatomic) IBOutlet UIImageView *imageView;
    
    @end
    
    @implementation ViewController
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        NSLog(@"--------");
        // 1.除主队列以外, 随便搞一个队列
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
        // 2.调用异步函数
        dispatch_async(queue, ^{
            // 1.下载图片
            NSURL *url = [NSURL URLWithString:@"http://pic.4j4j.cn/upload/pic/20130531/07ed5ea485.jpg"];
            NSData *data = [NSData dataWithContentsOfURL:url];
            // 2.将二进制转换为图片
            UIImage *image = [UIImage imageWithData:data];
    
            // 3.回到主线程更新UI
    //        self.imageView.image = image;
            /*
             技巧:
             如果想等UI更新完毕再执行后面的代码, 那么使用同步函数
             如果不想等UI更新完毕就需要执行后面的代码, 那么使用异步函数
             */
            dispatch_sync(dispatch_get_main_queue(), ^{ // 会等block代码执行完毕后,执行后面最后一句的打印代码
                self.imageView.image = image;
            });
            NSLog(@"设置图片完毕 %@", image);
        });
    }
    @end

    五、线程间通信

    • 从子线程回到主线程
    dispatch_async(
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
    
      // 1. 执行耗时的异步操作 
        .......
    
     //2. 回到主线程,执行UI刷新操作
      dispatch_async(dispatch_get_main_queue(),^{
    
     //这里使用的是dispatch_async:不用等block执行完,就会调用下面的打印代码,如果替换为使用dispatch_sync同步函数:那么会等block执行更新UI完毕,才会执行最后一句打印代码
          .......
      });
    });
    
    NSLog(@”更新UI界面结束”);
    • 实例程序:子线程下载图片,回到主线程刷新UI界面
    #import "ViewController.h"
    
    @interface ViewController ()
    @property (weak, nonatomic) IBOutlet UIImageView *imageView;
    
    @end
    
    @implementation ViewController
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        NSLog(@"--------");
        // 1.除主队列以外, 随便搞一个队列
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
        // 2.调用异步函数
        dispatch_async(queue, ^{
            // 1.下载图片
            NSURL *url = [NSURL URLWithString:@"https://www.baidu.com/img/bd_logo1.png"];
            NSData *data = [NSData dataWithContentsOfURL:url];
            // 2.将二进制转换为图片
            UIImage *image = [UIImage imageWithData:data];
    
            // 3.回到主线程更新UI
    //        self.imageView.image = image;
            /*
             技巧:
             如果想等UI更新完毕再执行后面的代码, 那么使用同步函数
             如果不想等UI更新完毕就需要执行后面的代码, 那么使用异步函数
             */
            dispatch_sync(dispatch_get_main_queue(), ^{ // 会等block代码执行完毕后,执行后面最后一句的打印代码
                self.imageView.image = image;
            });
            NSLog(@"设置图片完毕 %@", image);
        });
    }
    @end

    六、延时执行

    • 1、iOS常见的延时执行,调用NSObject的方法
      • performSelector 一旦定制好延时任务,不会卡住当前线程
        //2秒后再调用self的run方法
        [self performSelector:@selector(run) withObject:nil afterDelay:2.0];
    • 2、使用GCD函数

        // 该方法中, 会根据传入的队列来决定回掉block在哪个线程中执行
        // 如果传入的是主队列, 那么block会在主线程调用
        // 如果传入的是全局队列, 那么block会在子线程中调用
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{ // 这里传入全局队列会在子线程中执行block,如果传入主队列就会在主线程中执行block
            NSLog(@"3秒之后执行  %@", [NSThread currentThread]);
        });
    • 3、使用NSTimer

    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:NO];
    
    - (void)test{
        NSLog(@"---- begin-----");
        [NSThread sleepForTimeInterval:3]; // 它会卡住线程
        NSLog(@"---- end-----");
    }
    • 4 [NSThread sleepForTimeInterval:3];

      • 延时执行不要用 sleepForTimeInterval(它会卡住线程) ( 不使用 )
      - (void)dely1{
        // 1、延时执行不要用 sleepForTimeInterval(它会卡住线程)
        NSLog(@"---- begin-----");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"---- end-----");
      }

    7、一次性代码

    • 使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次,只执行1次的代码(这里面默认是线程安全的)
    • 注意: 千万不能把一次性代码当作懒加载来使用

         // 注意: 千万不能把一次性代码当作懒加载来使用
         static dispatch_once_t onceToken;
         dispatch_once(&onceToken, ^{
            NSLog(@" 一次性代码");
         });

    8、快速迭代

    • 使用dispatch_apply函数能进行快速迭代遍历,但是它和for循环又有些不同

      第一个参数: 需要遍历几次
      第二个参数: 决定第三个参数的block在哪个线程中执行
      第三个参数: 回掉
      dispatch_apply(10, 队列queue, ^(size_tindex){
        //执行10次代码,index顺序不确定
      });

    实例:拷贝文件到目标目录中 : 快速迭代实现

    // 1.定义变量记录原始文件夹和目标文件夹的路径
        NSString *sourcePath = @"/Users/cjp/Desktop/原始文件";
        NSString *destPath = @"/Users/cjp/Desktop/目标文件";
        // 2.取出原始文件夹中所有的文件
        NSFileManager *manager = [NSFileManager defaultManager];
        NSArray *files = [manager subpathsAtPath:sourcePath];
        //    NSLog(@"%@", files);
        // 3.开始拷贝文件
        /*
         for (NSString *fileName in files) {
         // 3.1生产原始文件的绝对路径
         NSString *sourceFilePath = [sourcePath stringByAppendingPathComponent:fileName];
         // 3.2生产目标文件的绝对路径
         NSString *destFilePath = [destPath stringByAppendingPathComponent:fileName];
         //        NSLog(@"%@", sourceFilePath);
         //        NSLog(@"%@", destFilePath);
         // 3.3利用NSFileManager拷贝文件
         [manager moveItemAtPath:sourceFilePath toPath:destFilePath error:nil];
         }
         */
        dispatch_apply(files.count, dispatch_get_global_queue(0, 0), ^(size_t index) {
            NSString *fileName = files[index];
            // 3.1生产原始文件的绝对路径
            NSString *sourceFilePath = [sourcePath stringByAppendingPathComponent:fileName];
            // 3.2生产目标文件的绝对路径
            NSString *destFilePath = [destPath stringByAppendingPathComponent:fileName];
            //        NSLog(@"%@", sourceFilePath);
            //        NSLog(@"%@", destFilePath);
            // 3.3利用NSFileManager拷贝文件
            [manager moveItemAtPath:sourceFilePath toPath:destFilePath error:nil];
        });

    9、栅栏 dispatch_barrier_async

    1、功能:

    • 1.拦截前面的任务, 只有先添加到队列中的任务=执行完毕, 才会执行栅栏添加的任务
    • 2.如果栅栏后面还有其它的任务, 那么必须等栅栏执行完毕才会执行后面的其它任务

    2、注意点:

    • 1.如果想要使用栅栏, 那么就不能使用全局的并发队列
    • 2.如果想使用栅栏, 那么所有的任务都必须添加到同一个队列中

    3、实例:合并两种图片,异步函数并发队列多线程下载图片,多个线程同时下载图片,当两种图片下载完毕 -> 开启栅栏功能子线程中进行合并图片,合成图片结束后,再会到主线程更新UI,

    • 使用,栅栏可以实现该需求,使用下面介绍的队列组也可以实现该功能
    - (void)barrier
    {
        dispatch_queue_t queue = dispatch_queue_create("cjp", DISPATCH_QUEUE_CONCURRENT);
        //    dispatch_queue_t queue2 = dispatch_queue_create("cjp", DISPATCH_QUEUE_CONCURRENT);
        //    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
        __block UIImage *image1 = nil;
        __block UIImage *image2 = nil;
        // 1.开启一个新的线程下载第一张图片
        dispatch_async(queue, ^{
            NSURL *url = [NSURL URLWithString:@"https://img.alicdn.com/tps/i1/TB1AE.sFVXXXXaCXFXXwu0bFXXX.png"];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];
            image1 = image;
            NSLog(@"图片1下载完毕");
        });
        // 2.开启一个新的线程下载第二张图片
        dispatch_async(queue, ^{
            NSURL *url = [NSURL URLWithString:@"https://www.baidu.com/img/bd_logo1.png"];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];
            image2 = image;
            NSLog(@"图片2下载完毕");
        });
    
        // 3.开启一个新的线程, 合成图片
        // 栅栏
        dispatch_barrier_async(queue, ^{
           // 图片下载完毕
            NSLog(@"%@ %@", image1, image2);
    
            // 1.开启图片上下文
            UIGraphicsBeginImageContext(CGSizeMake(200, 200));
            // 2.将第一张图片画上去
            [image1 drawInRect:CGRectMake(0, 0, 100, 200)];
            // 3.将第二张图片画上去
            [image2 drawInRect:CGRectMake(100, 0, 100, 200)];
            // 4.从上下文中获取绘制好的图片
            UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
            // 5.关闭上下文
            UIGraphicsEndImageContext();
    
            // 4.回到主线程更新UI
            dispatch_async(dispatch_get_main_queue(), ^{
                self.imageView.image = newImage;
    // 将合并后的图片写到本地桌面:
                [UIImagePNGRepresentation(newImage) writeToFile:@"/Users/cjp/Desktop/123.png" atomically:YES];
            });
    
            NSLog(@"栅栏执行完毕了");
        });
    
        dispatch_async(queue, ^{
            NSLog(@"---------");
        });
        dispatch_async(queue, ^{
            NSLog(@"---------");
        });
        dispatch_async(queue, ^{
            NSLog(@"---------");
        });
    }

    10、队列组 dispatch_group_t

    • 应用场景:分别异步执行2个耗时的操作,要等2个异步操作都执行完毕后,再回到主线程执行操作
    • 注意:该场景,使用上面的栅栏也可解决
    dispatch_group_tgroup =  dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
        //执行1个耗时的异步操作
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
        //执行1个耗时的异步操作
    });
    dispatch_group_notify(group, dispatch_get_main_queue(),^{
        //等前面的异步操作都执行完毕后,回到主线程...
    });

    实例:异步线程中,下载两种图片,在dispatch_group_notify通知中说明图片下载完-> 合并图片,合并图片结束后 ->再回到主线程中更新UI

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
         NSLog(@"%s", __func__);
    
        dispatch_queue_t queue = dispatch_queue_create("cjp", DISPATCH_QUEUE_CONCURRENT);
    
        __block UIImage *image1 = nil;
        __block UIImage *image2 = nil;
    
        dispatch_group_t group = dispatch_group_create();
    
        // 1.开启一个新的线程下载第一张图片
        dispatch_group_async(group, queue, ^{
            NSURL *url = [NSURL URLWithString:@"https://img.alicdn.com/tps/i1/TB1AE.sFVXXXXaCXFXXwu0bFXXX.png"];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];
            image1 = image;
            NSLog(@"图片1下载完毕");
        });
    
        // 2.开启一个新的线程下载第二张图片
        dispatch_group_async(group, queue, ^{
            NSURL *url = [NSURL URLWithString:@"https://www.baidu.com/img/bd_logo1.png"];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];
            image2 = image;
            NSLog(@"图片2下载完毕");
        });
    
        // 3.开启一个新的线程, 合成图片
        // 只要将队列放到group中, 队列中的任务执行完毕, group就会发出一个通知
    
        dispatch_group_notify(group, queue, ^{
            NSLog(@"%@ %@", image1, image2);
            // 1.开启图片上下文
            UIGraphicsBeginImageContext(CGSizeMake(200, 200));
            // 2.将第一张图片画上去
            [image1 drawInRect:CGRectMake(0, 0, 100, 200)];
            // 3.将第二张图片画上去
            [image2 drawInRect:CGRectMake(100, 0, 100, 200)];
            // 4.从上下文中获取绘制好的图片
            UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
            // 5.关闭上下文
            UIGraphicsEndImageContext();
    
            // 4.回到主线程更新UI
            dispatch_async(dispatch_get_main_queue(), ^{
                self.imageView.image = newImage;
                [UIImagePNGRepresentation(newImage) writeToFile:@"/Users/cjp/Desktop/123.png" atomically:YES];
            });
        });
    }
  • 相关阅读:
    Master公式计算递归时间复杂度
    对数器的使用
    HTML翻转菜单练习
    剑指offer题目解答合集(C++版)
    HTML---仿网易新闻登录页
    两个有序数组中的中位数以及求第k个最小数的值
    算法之重建二叉树
    AFNetWorking 上传功能使用及源码分析
    图片Alpha预乘的作用[转]
    C#/.NET 学习之路——从入门到放弃
  • 原文地址:https://www.cnblogs.com/zhufeng1994/p/5600746.html
Copyright © 2011-2022 走看看