美文网首页iOS学习笔记IOSiOS Developer
iOS篇-线程篇-0基础到熟练应用多线程( 二 )

iOS篇-线程篇-0基础到熟练应用多线程( 二 )

作者: TianTianBaby223 | 来源:发表于2017-07-10 15:33 被阅读286次

    //:如有需要代码demo 在评论留下邮箱 即刻回复

    一 : 科普一分钟

    上一期简单普及了一下有多线程的知识.
    如何创建子线程,对于线程的控制,如何对UI 线程的操作.
    在实际开发应用中,我们用到子线程,进行耗时任务操作,并发任务之间是如何依赖的,这一期将详细讲解.

    我们在买火车票的时候为什么不会购买重复吗,对于线程安全也会有详细讲解.

    二 : 详细讲解iOS中多线程方案

    1. pthread
    • 简单了解即可,在开发应用中非常少. 首先导入库 #import <pthread.h>
    • 线程对象的创建
    
     //1.pthread 创建线程对象
        pthread_t thread;
        
         pthread_t thread1 = NULL;
    
    • 创建线程
     /**
    
    
         
         第一个参数:线程对象传递地址
         第二个参数:线程的属性 NULL
         第三个参数:指向函数的指针
         第四个参数:函数需要接受的参数
         
         */
        
        pthread_create(&thread, NULL,TZtest, NULL);
        
    
    void *TZtest (void *parm){
        
      NSLog(@"--%@",[NSThread mainThread]);
        return NULL;
        
    }
    
    
    • 判断两个条线程是否相等
    pthread_equal(thread, thread1);
    
    • 应用

    记得之前在 viewDidLoad 写过一个耗时操作

    //耗时操作
        for (int i = 0; i < 100000; i++) {
            NSLog(@" i =  %d ---currentThread = %@",i,[NSThread mainThread]);
            
        }
        
    
    
    

    现在我们把这个方法放入我们的创建的线程方法里

    - (void)viewDidLoad {
        [super viewDidLoad];
    
       pthread_t thread;
       pthread_create(&thread, NULL,test, NULL);
    
    }
    
    void *test (void *parm){
        
        for (int i = 0; i < 100000; i++) {
            NSLog(@" i =  %d ---currentThread = %@",i,[NSThread mainThread]);
            
        }
        
      NSLog(@"--%@",[NSThread mainThread]);
        return NULL;
        
    }
    
    2. NSThread
    • 创建线程的的方式
      1.第一种方式 手动启动线程
         参数1 : 目标对象
         参数2 : 方法选择器
         参数3 : 前面调用方法需要传递的参数 没有时传nil
     NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(test:) object:@"TZ"];
    
     //启动线程
        [thread start];
    
    
    -(void)test:(NSString *)str{
        
        NSLog(@"--%@",[NSThread mainThread]);
        
    
    }
    

    2.第二种方式 类方法 自动启动

    //创建线程的第二种方法 自动启动线程
        [NSThread detachNewThreadSelector:@selector(test:) toTarget:self withObject:@"TZ"];
    

    3.第三种方法

     //开启一条后台线程
        [self performSelectorInBackground:@selector(test:) withObject:@"TZ"];
    
    • 属性 属性在启动线程之前调用
      name
    
     NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(test:) object:@"TZ"];
        
        //设置属性在启动线程之前设置
        thread.name = @"A";
    
    

    threadPriority

     TZThread *thread = [[TZThread alloc]initWithTarget:self selector:@selector(test:) object:@"TZ"];
        
        //设置属性在启动线程之前设置
       //线程的优先级  取值范围 0.0 - 1.0之间  默认优先级 0.5
        thread.threadPriority = 1.0 ;
    
    • 线程的周期
      生命周期,当任务执行完毕后被释放掉

    • 线程的状态
      当线程创建好后 执行start 方法后 --->线程进入 就绪状态-runable--> 当CPU 调度线程的时候 线程进入--->运行状态-Running<------当CPU 调度其他线程时候 状态变为 就绪状态-runable----->当调用sleep方法等待同步锁---->线程进入阻塞状态------>当线程任务执行完毕/异常退出/强制退出(exit)---->线程进入死亡状态-Dead

    • !注意:

    当线程执行start后 线程对象放入 可调度线程池
    当线程进入阻塞状态时候 线程对象还在内存中 只不过暂时从可调度线程池移除
    当线程进入死亡状态线程对象内存中移除

    从可调度线程池移除.png 进入可调度线程池.png
    • 阻塞方法
       //阻塞线程
        [NSThread sleepForTimeInterval:2];
    
    //3秒后
        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3.0]];
    
    • 强制退出方法
      [NSThread exit];//退出当前线程 强制退出
    
    • 线程之间的通信
      1.我们去面试或者交流的时候总会被人们问到一个问题,一个线程读取完数据怎么再次操作另一个线程呢...等等
      其实这就是线程之间的通信

      2.线程通信体现在

    1.一个线程传递数据给另一个线程
    2.在一个线程中执行完特定任务后,转到另一个线程继续执行任务.

    3.线程通信的常用方法

    回到主线程,第三个参数的意思 就是 是否等待执行,如果选择`YES` 则知道回到主线程 参数的方法执行完成后 继续执行,
    

    否则的话 直接执行 该方法下面的函数

    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
    

    回到某个线程

    - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
    

    4.代码实现
    我们写一个下载图片的代码 完成线程间通信

     - (void)viewDidLoad {
     [super viewDidLoad];
      [NSThread detachNewThreadSelector:@selector(downLoad) toTarget:self withObject:nil];
     
    }
    
    
    -(void)downLoad{
    // http://imgsrc.baidu.com/baike/pic/item/3c6d55fbb2fb4316fb41d3f525a4462308f7d3e7.jpg
    
        
    
        NSURL *URL  = [NSURL URLWithString:@"http://imgsrc.baidu.com/baike/pic/item/3c6d55fbb2fb4316fb41d3f525a4462308f7d3e7.jpg"];
        
        //根据URL 下载图片  二进制数据 到本地
        
        NSData *imageData = [NSData dataWithContentsOfURL:URL];
        
        //3.转换图片格式
        UIImage *image = [UIImage imageWithData:imageData];
        
        //回到主线程显示UI
        
    //    [self performSelectorOnMainThread:@selector(TZshowUI:) withObject:image waitUntilDone:YES];
        
    //    [self performSelector:@selector(TZshowUI:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
        
        [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
        
         
    }
    
    -(void)TZshowUI:(UIImage*)image{
    self.imageView.image = image;
    
    }
    
    3. GCD
    1. 概括 : Grand Central DisPatch 中枢调度器
      纯C语言,提供了非常多强大的函数

    2. GCD 的优势 : GCD 是苹果为多核的并行运算提出的解决方案
      GCD会自动利用更多的CPU 内核
      GCD会自动管理线程的生命周期(创建线程,调度任务,销毁线程)
      程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理的代码

    3. CCD 最大的两个特性任务队列
      任务: 执行什么操作
      队列:用来存放任务
      GCD使用的两个步骤
      a : 定制任务
      b : 将任务添加到队列中

      GCD 会自动将队列中的任务取出,放到对应的栈中执行
      任务的取出时遵循队列的FIFO 原则,先进先出,后进后

    4. 常用函数
      a : 同步函数 参数 : 1.队列 2 block 想做的事(任务)

       dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
      

      b : 异步函数 参数 : 1.队列 2 block 想做的事(任务)

       dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
      
    5. 同步函数与异步函数的区别

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

    6. 队列的类型

      并发队列 : 可以让多个任务并发(同时)执行 ,自动开启多个线程同时执行任务,并发功能只有在异步函数dispatch_asyn下才有效

      串行队列 : 让任务一个接一个地执行,一个任务执行完毕后,再执行下一个任务

    7. 基础代码

    a : 创建异步函数+并发队列 : 会开启多条线程,队列中的任务是异步执行

    -(void)TZasyncConCurrent{
        
    //1.创建队列
        
        /**
         参数1 : C语言的字符串,标签
         参数2 : 队列的类型
         DISPATCH_QUEUE_CONCURRENT 并发
         DISPATCH_QUEUE_SERIAL 串行
         
         */
    //    dispatch_queue_t queue = dispatch_queue_create("TZzzz", DISPATCH_QUEUE_CONCURRENT);
        
        
        
        //获取全局并发队列 从系统中获得
        /**
         参数一 : 优先级
         参数二 : 未来使用
         */
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
       //2 1->封装任务 2->添加任务到队列中
        
        /**
         
         参数1 : 队列
         参数2 : 要执行的任务
         
         */
    
        
        dispatch_async(queue, ^{
            
            NSLog(@"-TZ1--%@",[NSThread mainThread]);
            
        });
        
        dispatch_async(queue, ^{
            
            NSLog(@"-TZ2--%@",[NSThread mainThread]);
            
        });
        
        dispatch_async(queue, ^{
            
            NSLog(@"-TZ3--%@",[NSThread mainThread]);
            
        });
        
    }
    

    b : 创建异步函数+串行队列 : 会开线程,开一条线程,队列中的任务是串行执行的

    //异步函数+串行队列 : 会开线程,开一条线程,队列中的任务是串行执行的
    -(void)TZasyncSerial{
    
        //1.创建队列
      dispatch_queue_t queue = dispatch_queue_create("TZzzzz", DISPATCH_QUEUE_SERIAL);
        
    
        //2.封装操作
        dispatch_async(queue, ^{
            
            NSLog(@"-TZ1--%@",[NSThread mainThread]);
            
        });
        
        dispatch_async(queue, ^{
            
            NSLog(@"-TZ2--%@",[NSThread mainThread]);
            
        });
        dispatch_async(queue, ^{
            
            NSLog(@"-TZ3--%@",[NSThread mainThread]);
            
        });
    }
    

    c : 创建同步函数+并发队列 : 不会开线程,任务是串行执行的

    -(void)TZsyncConCurrent{
    
        //创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("TZzzz", DISPATCH_QUEUE_CONCURRENT);
        
        //封装任务
        //2.封装操作
        dispatch_sync(queue, ^{
            
            NSLog(@"-TZ1--%@",[NSThread mainThread]);
            
        });
        
        dispatch_sync(queue, ^{
            
            NSLog(@"-TZ2--%@",[NSThread mainThread]);
            
        });
        dispatch_sync(queue, ^{
            
            NSLog(@"-TZ3--%@",[NSThread mainThread]);
            
        });
    }
    

    d : 创建同步函数+串行队列 : 不会开线程,任务是串行执行的

    -(void)TZsyncSerial{
        
        //创建串行队列
        dispatch_queue_t queue = dispatch_queue_create("TZzzz", DISPATCH_QUEUE_SERIAL);
        
        //封装任务
        //2.封装操作
        dispatch_sync(queue, ^{
            
            NSLog(@"-TZ1--%@",[NSThread mainThread]);
            
        });
        
        dispatch_sync(queue, ^{
            
            NSLog(@"-TZ2--%@",[NSThread mainThread]);
            
        });
        dispatch_sync(queue, ^{
            
            NSLog(@"-TZ3--%@",[NSThread mainThread]);
            
        });
    }
    
    

    8.GCD 线程间的通信
    我们还是以下载图片为例子

     //创建子线程 下载图片
       
       
       dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
       
       dispatch_async(queue, ^{
           
           //1.1
            NSURL *URL  = [NSURL URLWithString:@"http://imgsrc.baidu.com/baike/pic/item/3c6d55fbb2fb4316fb41d3f525a4462308f7d3e7.jpg"];
           //1.2 下载二进制数据到本地
           NSData *imageData = [NSData dataWithContentsOfURL:URL];
           UIImage *image = [UIImage imageWithData:imageData];
           
           //更新UI
           dispatch_async(dispatch_get_main_queue(), ^{
               self.imageView.image = image;
    
           });
           
           
       });
    

    我们开启了子线程,然后进行下载 .回到主线程做事情(刷新UI)

    9.GCD 常用函数
    a : 延迟
    我们平时为了在某个地方停留一会用的比较多的就是延迟函数

    ```
    //1. 延迟执行的第一种方法
    NSLog(@"--start");
    [self performSelector:@selector(tztask) withObject:nil afterDelay:2];
    
    
    //2.延迟执行的第二种方法
    
    [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(tztask) userInfo:nil repeats:NO];
    
    //3.延迟执行的第三种方法
    /**
     参数1 : DISPATCH_TIME_NOW 从现在开始计算时间
     参数2 : 延迟的时间 2.0 GCD 时间单位为纳秒
     参数3 : 队列
     */
    
    //传入全局并发队列打印操作在子线程执行
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_queue_t queue = dispatch_get_main_queue;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), (queue), ^{
        
        NSLog(@"--GCD----%@",[NSThread currentThread]);
        
    });
    ```
    

    延迟不仅可以在主线程(UI)线程中进行,也可以在子线程进行,当队列为并发队列时候再子线程执行 ,主队列时候再主线程执行. 使用时请大家注意

    b : 一次性代码-大多数情况用在单利中

    //整个应用程序只调用一次
       static dispatch_once_t onceToken;
       dispatch_once(&onceToken, ^{
           NSLog(@"===onece");
           
       });
    

    注意:一次性代码不用放在懒加载里面,因为一次性代码 整个app 启动后只会运行一次.

    10.GCD 栅栏函数
    他是干什么的呢, 在多个异步任务,我们怎么要规范顺序呢 这就是栅栏函数的作用,像围栏一样控制你想要的顺序

     //栅栏函数不能使用全局并发队列
      dispatch_queue_t queue = dispatch_queue_create("TZdown", DISPATCH_QUEUE_CONCURRENT);
      
      
      
      dispatch_async(queue, ^{
          
          NSLog(@"-TZ1--%@",[NSThread mainThread]);
          
      });
      
      dispatch_async(queue, ^{
          
          NSLog(@"-TZ2--%@",[NSThread mainThread]);
          
      });
      
      //栅栏函数
      
      dispatch_barrier_async(queue, ^{
          
          NSLog(@"+++++++++围栏++++++++++");
          
      });
      dispatch_async(queue, ^{
          
          NSLog(@"-TZ3--%@",[NSThread mainThread]);
          
      });
    

    运行结果 : TZ1 TZ2 打印顺序随意,因为是异步的 ,栅栏在其后,最后执行的是TZ3任务

    栅栏函数注意点:控制并发队列任务执行顺序,不能使用全局并发队列

    11.GCD 快速迭代
    什么是迭代呢,简单来说就是循环 ,为什么要使用快速迭代呢,因为内部能开子线程,执行速度快

    /**
     参数 1 : 要遍历的次数
     参数 2 : 队列(只能传并发队列)
     参数 3 :index 索引
     
     */
    dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
        
        NSLog(@"-%zd---%@",index,[NSThread mainThread]);
    
    });
        
    

    12.GCD队列组
    队列组的作用是会监听假如队列组中任务的执行情况

    队列组的两种方式
    a :

    ///1.创建队列
      dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
      //2.创建队列组
      dispatch_group_t group = dispatch_group_create();
      
      //3.异步函数
      /**
       封装任务
       把任务添加到队列中
       会监听任务的执行情况,通知group
       */
      dispatch_group_async(group, queue, ^{
          
          NSLog(@"1---%@",[NSThread currentThread]);
      });
      
      dispatch_group_async(group, queue, ^{
          
          NSLog(@"2---%@",[NSThread currentThread]);
      });
      
      dispatch_group_async(group, queue, ^{
          
          NSLog(@"3---%@",[NSThread currentThread]);
      });
    
      //拦截任务,当队列组中所有的任务都执行完毕的时候会进入下面的方法
      dispatch_group_notify(group, queue, ^{
          NSLog(@"---完成所有任务---");
          
      });
    
    

    b :

    ///1.创建队列
      dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
      
      //2.创建队列组
      dispatch_group_t group = dispatch_group_create();
      
      //3.在该方法后面的异步任务会被纳入队列组的监听范围,进入群组
      //dispatch_group_enter|dispatch_group_leave 必须要配对使用
      dispatch_group_enter(group);
      dispatch_async(queue, ^{
          NSLog(@"1---%@",[NSThread currentThread]);
          
          //离开群组
          dispatch_group_leave(group);
      });
      
      dispatch_group_enter(group);
      dispatch_async(queue, ^{
          NSLog(@"2---%@",[NSThread currentThread]);
          
          //离开群组
          dispatch_group_leave(group);
      });
    
      //拦截通知 内部本身是异步的 ,不会堵塞
    //    dispatch_group_notify(group, queue, ^{
    //        NSLog(@"----完成任务----");
    //    });
      
      //死等 直到队列组中所有任务都执行完毕之后才能执行
      //本身是阻塞的,这一行代码没有执行完毕后面代码永远不会被执行
      dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
      NSLog(@"---over");
    

    13:GCD 队列组实例 ,下载两张图片,合并图片

    /**
      1.下载图片1 开子线程
      2.下载图片2 开子线程
      3.合成图片并显示图片 开子线程
      
      */
     
     
     //获取一个队列组
     
     dispatch_group_t group = dispatch_group_create();
     
     //获得并发队列
     dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
     
     //1.下载图片 1 开启子线程
     dispatch_group_async(group, queue,^{
         //确定url
         NSURL *url = [NSURL URLWithString:@""];
         //下载二进制数据
         NSData *imageData = [NSData dataWithContentsOfURL:url];
     
         //转换图片
         self.image1 = [UIImage imageWithData:imageData];
         
         
     });
     
     
     
     
     //2.下载图片2 开启子线程
      dispatch_group_async(group, queue,^{
         //确定url
         NSURL *url = [NSURL URLWithString:@""];
         //下载二进制数据
         NSData *imageData = [NSData dataWithContentsOfURL:url];
         
         //转换图片
        self.image2 = [UIImage imageWithData:imageData];
         
     });
     
    
     
     
     //3.合并图片
     
      dispatch_group_notify(group, queue, ^{
          //1.1 创建图形上下文
          UIGraphicsBeginImageContext(CGSizeMake(200, 200));
         
          //1.2 画图 图片1
          [self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
          self.image1 = nil;
          
          //1.3 画图 图片 2
          [self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
          self.image2 = nil;
          
          
          //1.4根据上下文得到一张图片
     UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
          
          
          //1.5关闭上下文
          UIGraphicsEndImageContext();
          
          
          //1.6更新UI
          
          dispatch_async(dispatch_get_main_queue(), ^{
              self.imageView.image = image;
              
          });
          
      });
    
    
    4.NSOperation

    NSOperation 主要元素就是 操作和队列,其实操作也就是任务.
    NSOperation 是一个抽象类,没有封装,所以我们使用的时候要使用其自子类,接下来详细讲解一下 .

    1. NSInvocationOperation

    不会开线程,在主线程执行

    1.创建操作封装任务
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(TZrun) object:nil];
    2 启动执行操作
      [op1 start];
    

    2.NSBlockOperation

    -(void)blockOperation{
    
       NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"1----%@",[NSThread mainThread]);
        
    }];
        
    
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            
            NSLog(@"2----%@",[NSThread mainThread]);
            
        }];
        
        NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            
            NSLog(@"3----%@",[NSThread mainThread]);
            
        }];
        
        
        //追加任务,如果一个操作中的任务数量大于1,那么会开子线程并发执行任务
        [op3 addExecutionBlock:^{
            
            NSLog(@"4----%@",[NSThread mainThread]);
    
        }];
        
        [op3 addExecutionBlock:^{
            
            NSLog(@"5----%@",[NSThread mainThread]);
    
        }];
        
        [op3 addExecutionBlock:^{
            
            NSLog(@"6----%@",[NSThread mainThread]);
    
        }];
        
        
        //启动任务
        [op1 start];
        [op2 start];
        [op3 start];
        
        
    }
    

    3.NSOperationQueue 如何使用呢,我们要实现在子线程里面做事情.
    首先我们先讲一下GCD 中的队列 和 NSOperation 中队列的区别
    GCD 中的队列
    串行队列 :
    a : 创建串行队列
    b : 主队列

    并行队列 :
    a : 创建并行队列
    b : 全局并发队列

    NSOperation中的队列

    主队列 : [NSOperationQueue mainQueue] 和 GCD 主队列一样,串行队列

    非主队列 : [[NSOperationQueue alloc]init];(同时具备串行和并发的功能) 默认状态下非主队列是并发队列

    NSOperationQueue使用示例

    -(void)invocationOperationWithQueue{
    
    
        //1.创建操作封装任务
        NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(TZrun) object:nil];
         NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(TZrun) object:nil];
         NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(TZrun) object:nil];
        
        
      //2.创建队列
      NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        
        //3.添加操作到队列
        [queue addOperation:op1];
        [queue addOperation:op2];   // 内部已经调用了 start 方法
        [queue addOperation:op3];
        
        
    }
    
    -(void)blockOperationWithQueue{
        NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            
            NSLog(@"1----%@",[NSThread mainThread]);
            
        }];
        
        
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            
            NSLog(@"2----%@",[NSThread mainThread]);
            
        }];
        
        NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            
            NSLog(@"3----%@",[NSThread mainThread]);
            
        }];
        
        [op3 addExecutionBlock:^{
            
            NSLog(@"4----%@",[NSThread mainThread]);
            
        }];
        
        [op3 addExecutionBlock:^{
            
            NSLog(@"5----%@",[NSThread mainThread]);
            
        }];
        
        [op3 addExecutionBlock:^{
            
            NSLog(@"6----%@",[NSThread mainThread]);
            
        }];
        
         //2.创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        
        //3.添加操作到队列
        [queue addOperation:op1];
        [queue addOperation:op2];
        [queue addOperation:op3];
        
        
        //简便方法
        //内部 : 1创建操作 2.添加操作到队列中
        
        [queue addOperationWithBlock:^{
            
            NSLog(@"7----%@",[NSThread mainThread]);
    
        }];
        
    
    }
    

    4.自定义 NSOperation 子类

    系统为我们提供了子类 封装操作,为什么我还要定义子类呢.
    因为就像是一个自定义View 一样 我们不喜欢分散的写在Controller 一样,假如我们操作代码十分庞大,方便我复用等等,我们需要自定义.

    通过重写 main 方法 来实现我们需要操作的事情

    @interface TZOperation : NSOperation
    
    #import "TZOperation.h"
    
    @implementation TZOperation
    //告知的是执行什么任务
    //有利于代码隐蔽
    //复用性好
    
    
    -(void)main{
        NSLog(@"---封装的操作");
        
    
    }
    
    @end
    
    -(void)TZOperationWithQueue{
        //1.封装操作
        TZOperation *op1 = [[TZOperation alloc]init];
        TZOperation *op2 = [[TZOperation alloc]init];
        
        //2.创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        
        //3.添加操作队列
        [queue addOperation:op1];
        [queue addOperation:op2];
        
    }
    

    5.NSOperation 的依赖和监听

    有时候我们同时执行多个操作,但是我们要在某一个操作之后完成后再执行另一个操作,这就用到了 依赖.

    如何知道我们的某一操作已经做完了-监听

    注意点 : 我们所添加的依赖不能循环,那样的话,两个操作都不会执行.

    依赖可以跨队列依赖

     //1.创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        NSOperationQueue *queue2 = [[NSOperationQueue alloc]init];
        
        
        //2.封装操作
        NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            
            NSLog(@"1----%@",[NSThread currentThread]);
            
        }];
        
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            
            NSLog(@"2----%@",[NSThread currentThread]);
            
        }];
        
        NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            
            NSLog(@"3----%@",[NSThread currentThread]);
            
        }];
        NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
            
            NSLog(@"4----%@",[NSThread currentThread]);
            
        }];
    
        
        
        //操作监听
        
        op3.completionBlock = ^{
        
            NSLog(@"+++操作三完成了 ^_^ ");
            
        };
        
        
        
        //添加操作依赖
        
        //注意点 : 不能循环依赖
        
        //可以跨队列依赖
        [op1 addDependency:op4];
        [op4 addDependency:op3];
        [op3 addDependency:op2];
        
        //任务执行顺序
        // op2->op3->op4->op1
        
        
        //3添加操作
        [queue addOperation:op1];
        [queue addOperation:op2];
        [queue addOperation:op3];
        [queue2 addOperation:op4];
    
    

    6.NSOperation 线程间的通信
    和GCD 还有NSThread 一样,我们做完子线程操再回到主线程操作.
    我们做两个例子,一个是下载图片 和 合成图片,来看看NSOperation 如何实现线程间的通信

    a : 下载图片

    //下载图片
    -(void)TZdown{
       
       //1.开子线程下载图片 非主队列
       NSOperationQueue *queue = [[NSOperationQueue alloc]init];
       
       //2.封装操作
       NSBlockOperation *download = [NSBlockOperation blockOperationWithBlock:^{
           
           
           NSURL *URL  = [NSURL URLWithString:@"http://imgsrc.baidu.com/baike/pic/item/3c6d55fbb2fb4316fb41d3f525a4462308f7d3e7.jpg"];
           
           NSData *imageData = [NSData dataWithContentsOfURL:URL];
           UIImage *imge = [UIImage imageWithData:imageData];
           
           
           //更新UI
           [[NSOperationQueue mainQueue] addOperationWithBlock:^{
               
               self.imageView.image = imge;
               
           }];
           
           
       }];
       
       //添加操作到队列中
       [queue addOperation:download];
    
    }
    
    

    b : 合成图片

      //1.开子线程下载图片 非主队列
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        
        
        __block UIImage *image1;
        __block UIImage *image2;
    
        //2.封装操作 下载图片 1
        NSBlockOperation *download = [NSBlockOperation blockOperationWithBlock:^{
            
            
            NSURL *URL  = [NSURL URLWithString:@"http://imgsrc.baidu.com/baike/pic/item/3c6d55fbb2fb4316fb41d3f525a4462308f7d3e7.jpg"];
            
            NSData *imageData = [NSData dataWithContentsOfURL:URL];
            image1 = [UIImage imageWithData:imageData];
            
            
            
        }];
        
        //3.封装操作,下载图片2
        NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{
            
            
            NSURL *URL  = [NSURL URLWithString:@"http://imgsrc.baidu.com/baike/pic/item/3c6d55fbb2fb4316fb41d3f525a4462308f7d3e7.jpg"];
            
            NSData *imageData = [NSData dataWithContentsOfURL:URL];
            image2 = [UIImage imageWithData:imageData];
            
            
        }];
        
        
        //4合并图片的操纵
        
        //4.1
        NSBlockOperation *com = [NSBlockOperation blockOperationWithBlock:^{
            
           //开启上下文
            UIGraphicsBeginImageContext(CGSizeMake(200, 200));
            
           //画图1
            [image1 drawInRect:CGRectMake(0, 0, 100, 200)];
            
           //画图2
            [image2 drawInRect:CGRectMake(100, 0, 100, 200)];
            
           //根据上下文得到图片
            UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
            
            
           //关闭上下文
            UIGraphicsEndImageContext();
            
            //7.更新UI
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                
                self.imageView.image = image;
                
            }];
            
        }];
        
        
        //5.设置依赖关系
        [com addDependency:download];
        [com addDependency:download2];
        
        //6.添加操作到队列里面去
        
        [queue addOperation:download];
        [queue addOperation:download2];
        [queue addOperation:com];
    
    

    合成图片 用到了 依赖 ,合成操作依赖于 两个下载操作已经完成

    三 : 线程安全性

    • 原因
    1. 一块资源可能被多个线程共享,也就是多个线程可能会访问同一块内存资源.

    2. 比如多个线程访问同一个对象,同一个变量,同一个文件

    3. 当多个线程访问同一块资源时,很容易引发数据错乱,和数据安全问题.

    苹果官方访问统一资源造成了数据错乱示意图


    未加锁.png
    • 解决办法

      1. 互斥锁:
        必须是全局唯一的,锁定一份代码只能用一把锁,用多把锁是无效的
    加互斥锁.png

    上图为加了互斥所 后 线程A 和线程 B 访问同一块资源,线程A 在读取 17 后被锁上 进行 +1操作 变为18 直到解锁写入 ,线程B 才能方位这个资源 否则不能访问,当 线程A 解锁后,线程B 访问的结果是18 再 对其进行 +1操作 最后写入结果为19 然后线程B 解锁

    • 代码

    添加互斥所 只需要一行代码 @synchronized就可以啦 以下代码做使用示范

    A,B,C 是三个手机 共同在网上卖电影票

    
        self.TZCount = 1000;
        self.theadA = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
        
    
         self.threadB = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
        
         self.threadC = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
        self.theadA.name = @"A";
        self.threadB.name = @"B";
        self.threadC.name = @"C";
        
        //启动线程
        [self.theadA start];
        [self.threadB start];
        [self.threadC start];
    
    
    
    -(void)saleTicket{
        
        while (1) {
           
            @synchronized (self) {
                NSInteger TZCount = self.TZCount;
                if (TZCount > 0) {
                    [self lastTimeAction];
                    self.TZCount = TZCount;
                  
                }else{
                    
                    NSLog(@"出售光了");
                    break;
                }
            }
           
        }
           
    
    }
    
    

    模拟耗时操作

    -(void)lastTimeAction{
    
        for (int i = 0 ; i < 100000; i++) {
            
        }
    }
    

    @synchronized (self) 因为全局唯一 我们通常使用self

    • 注意事项

      1. 注意加锁的位置
      2. 注意加锁的条件:多线程共享同一块资源
      3. 注意加锁是耗费性能的
    • 互斥锁的优点
      能有效防止因多线程抢夺资源造成的数据安全问题

    • 互斥所的缺点
      需要消耗大量CPU 资源

    加互斥所又叫线程同步,多条线程在同一条线上按顺序执行

    • 原子性和非原子属性
    1. atomic : 原子属性为setter方法加锁(默认就atomic)
    2. nonatomic : 非原子属性,不会为setter方法加锁
    • 原子和非原子的对比
      atomic : 线程安全,需要消耗大量的资源
      nonatomic : 非线程安全,适合内存较小的移动设备

    一般在开发中我们都是用 nonatomic 属性
    尽量避免多线程抢夺同一块资源.

    四: 总结

    对于像GCD ,NSOperation 常用控制线程的 API 应该熟练掌握
    在实际开发应用中我们大多用到的就是开设子线程

    创建队列
    创建任务
    任务放进队列里
    根据需求 设置好同步异步任务,和串行并行队列
    刷新主线程UI 等等操作. 希望大家读完这一期有所收获,下期再见 ^ _ ^

    //----------希望我们永远年轻-----永远热泪盈眶-----

    相关文章

      网友评论

        本文标题:iOS篇-线程篇-0基础到熟练应用多线程( 二 )

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