zoukankan      html  css  js  c++  java
  • 4.4 多线程进阶篇<下>(NSOperation)

    本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末

    如果觉得本文内容过长,请前往本人"简书"

    本文源码 Demo 详见 Github
    https://github.com/shorfng/iOS-4.0-multithreading.git


    1.0 NSOperation 的作用

    使用 NSOperation 的目的就是为了让开发人员不再关心线程

    • 配合使用 NSOperation(任务) 和 NSOperationQueue(队列) 也能实现多线程编程

    NSOperation 和 NSOperationQueue 实现多线程的具体步骤:

    (1)先将需要执行的操作封装到一个NSOperation对象中

    (2)然后将NSOperation对象添加到NSOperationQueue中

    (3)系统会自动将NSOperationQueue中的NSOperation取出来

    (4)将取出的NSOperation封装的操作放到一条新线程中执行

    使用NSOperation子类的方式有3种:

    NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类

    1. NSInvocationOperation
    • NSBlockOperation
    • 自定义子类继承NSOperation,实现内部相应的方法

    2.0 NSInvocationOperation

    //创建NSInvocationOperation对象
    - (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
    
    //调用start方法开始执行操作,一旦执行操作,就会调用target的sel方法
    - (void)start;
    

    注意:

    • 默认情况下,操作对象在主线程中执行
    • 调用了start方法后并不会开一条新线程去执行操作,只有添加到队列中才会开启新的线程
    • 即默认情况下,如果操作没有放到队列中queue中,都是同步执行。
    • 只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作

    代码示例:

    #import "ViewController.h"
    
    @interface ViewController ()
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
      [super viewDidLoad];
      // Do any additional setup after loading the view, typically from a nib.
    }
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
      //创建操作对象,封装要执行的任务
      NSInvocationOperation *op =
          [[NSInvocationOperation alloc] initWithTarget:self
                                               selector:@selector(run)
                                                 object:nil];
      //执行操作
      [op start];
    }
    
    - (void)run {
      NSLog(@"------%@", [NSThread currentThread]);
    }
    
    - (void)didReceiveMemoryWarning {
      [super didReceiveMemoryWarning];
      // Dispose of any resources that can be recreated.
    }
    @end
    

    打印结果:

    NSInvocationOperation[862:29437] ------<NSThread: 0x7f9cea507920>{number = 1, name = main}
    

    3.0 NSBlockOperation

    //创建 NSBlockOperation 操作对象
    + (id)blockOperationWithBlock:(void (^)(void))block;
    
    // 添加操作
    - (void)addExecutionBlock:(void (^)(void))block;
    

    注意:只要NSBlockOperation封装的操作数 > 1,就会异步执行操作


    代码示例:

    #import "ViewController.h"
    
    @interface ViewController ()
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
      [super viewDidLoad];
      // Do any additional setup after loading the view, typically from a nib.
    }
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
      // 1.创建 NSBlockOperation 操作对象
      NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        // 在主线程
        NSLog(@"下载1------%@", [NSThread currentThread]);
      }];
    
      // 2.添加操作(额外的任务)(在子线程执行)
      [op addExecutionBlock:^{
        NSLog(@"下载2------%@", [NSThread currentThread]);
      }];
    
      [op addExecutionBlock:^{
        
      [op addExecutionBlock:^{
        NSLog(@"下载2------%@", [NSThread currentThread]);
      }];
      [op addExecutionBlock:^{
        NSLog(@"下载3------%@", [NSThread currentThread]);
      }];
      [op addExecutionBlock:^{
        NSLog(@"下载4------%@", [NSThread currentThread]);
      }];
      // 3.开启执行操作
      [op start];
    }
    - (void)run {
      NSLog(@"------%@", [NSThread currentThread]);
    }
    - (void)didReceiveMemoryWarning {
      [super didReceiveMemoryWarning];
      // Dispose of any resources that can be recreated.
    }
    @end
    

    打印结果:

    NSBlockOperation[1013:37922] 下载1------<NSThread: 0x7feea1c05460>{number = 1, name = main}
    NSBlockOperation[1013:37952] 下载2------<NSThread: 0x7feea1f0b790>{number = 2, name = (null)}
    NSBlockOperation[1013:37955] 下载3------<NSThread: 0x7feea1c0f8a0>{number = 3, name = (null)}
    NSBlockOperation[1013:37951] 下载4------<NSThread: 0x7feea1e0b520>{number = 4, name = (null)}
    

    4.0 NSOperationQueue

    NSOperationQueue的作用:添加操作到NSOperationQueue中,自动执行操作,自动开启线程

    • NSOperation 可以调用 start 方法来执行任务,但默认是同步执行的
    • 如果将 NSOperation 添加到 NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作

    添加操作到 NSOperationQueue 中:2种方式

    
    - (void)addOperation:(NSOperation *)op;
    
    - (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);
    

    代码示例:

    #import "ViewController.h"
    
    @interface ViewController ()
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
      [super viewDidLoad];
      // Do any additional setup after loading the view, typically from a nib.
    }
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
      [self operationQueue2];
    }
    
    #pragma mark - 把操作添加到队列中,方式1:addOperation
    - (void)operationQueue1 {
      // 1.创建队列
      NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
      // 2.1 方式1:创建操作(任务)NSInvocationOperation ,封装操作
      NSInvocationOperation *op1 =
          [[NSInvocationOperation alloc] initWithTarget:self
                                               selector:@selector(download1)
                                                 object:nil];
    
      // 2.2 方式2:创建NSBlockOperation ,封装操作
      NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download2 --- %@", [NSThread currentThread]);
      }];
    
      // 添加操作
      [op2 addExecutionBlock:^{
        NSLog(@"download3 --- %@", [NSThread currentThread]);
      }];
    
      // 3.把操作(任务)添加到队列中,并自动调用 start 方法
      [queue addOperation:op1];
      [queue addOperation:op2];
    }
    
    - (void)download1 {
      NSLog(@"download1 --- %@", [NSThread currentThread]);
    }
    
    #pragma mark - 把操作添加到队列中,方式2:addOperationWithBlock
    - (void)operationQueue2 {
      // 1.创建队列
      NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
      // 2.添加操作到队列中
      [queue addOperationWithBlock:^{
        NSLog(@"download1 --- %@", [NSThread currentThread]);
      }];
      [queue addOperationWithBlock:^{
        NSLog(@"download2 --- %@", [NSThread currentThread]);
      }];
    }
    
    - (void)didReceiveMemoryWarning {
      [super didReceiveMemoryWarning];
      // Dispose of any resources that can be recreated.
    }
    
    @end
    

    打印结果:

     NSOperationQueue[1658:89517] download2 --- <NSThread: 0x7f88a9e059d0>{number = 3, name = (null)}
     NSOperationQueue[1658:89518] download1 --- <NSThread: 0x7f88a9d901f0>{number = 2, name = (null)}
     NSOperationQueue[1658:89521] download3 --- <NSThread: 0x7f88a9d15d30>{number = 4, name = (null)}
    
     NSOperationQueue[1704:92509] download2 --- <NSThread: 0x7fd318f06540>{number = 2, name = (null)}
     NSOperationQueue[1704:92513] download1 --- <NSThread: 0x7fd318d0e460>{number = 3, name = (null)}
    

    提示:队列的取出是有顺序的,与打印结果并不矛盾。这就好比,选手A,BC虽然起跑的顺序是先A,后B,然后C,但是到达终点的顺序却不一定是A,B在前,C在后。

    4.1 最大并发数

    并发数:同时执⾏行的任务数 比如,同时开3个线程执行3个任务,并发数就是3

    最大并发数:同一时间最多只能执行的任务的个数

    最⼤并发数的相关⽅方法:

    //最大并发数,默认为-1
    @property NSInteger maxConcurrentOperationCount;
    
    - (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
    

    说明:

    • 如果没有设置最大并发数,那么并发的个数是由系统内存和CPU决定的,内存多就开多一点,内存少就开少一点。
    • 最⼤并发数的值并不代表线程的个数,仅仅代表线程的ID。
    • 最大并发数不要乱写(5以内),不要开太多,一般以2~3为宜,因为虽然任务是在子线程进行处理的,但是cpu处理这些过多的子线程可能会影响UI,让UI变卡。
    • 最大并发数的值为1,就变成了串行队列

    代码示例:

    #import "ViewController.h"
    
    @interface ViewController ()
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
      [super viewDidLoad];
      // Do any additional setup after loading the view, typically from a nib.
    }
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
      // 1.创建队列
      NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
      // 2.设置最大并发操作数(大并发操作数 = 1,就变成了串行队列)
      queue.maxConcurrentOperationCount = 2;
    
      // 3.添加操作
      [queue addOperationWithBlock:^{
        NSLog(@"download1 --- %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:0.01];
      }];
      [queue addOperationWithBlock:^{
        NSLog(@"download2 --- %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:0.01];
      }];
      [queue addOperationWithBlock:^{
        NSLog(@"download3 --- %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:0.01];
      }];
      [queue addOperationWithBlock:^{
        NSLog(@"download4 --- %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:0.01];
      }];
      [queue addOperationWithBlock:^{
        NSLog(@"download5 --- %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:0.01];
      }];
      [queue addOperationWithBlock:^{
        NSLog(@"download6 --- %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:0.01];
      }];
    }
    
    - (void)didReceiveMemoryWarning {
      [super didReceiveMemoryWarning];
      // Dispose of any resources that can be recreated.
    }
    
    @end
    

    打印结果:

    最大并发数[1909:113433] download2 --- <NSThread: 0x7ffef240ba70>{number = 3, name = (null)}
    最大并发数[1909:113432] download1 --- <NSThread: 0x7ffef24aee50>{number = 2, name = (null)}
    最大并发数[1909:113432] download4 --- <NSThread: 0x7ffef24aee50>{number = 2, name = (null)}
    最大并发数[1909:113431] download3 --- <NSThread: 0x7ffef251aa80>{number = 4, name = (null)}
    最大并发数[1909:113428] download5 --- <NSThread: 0x7ffef2603d90>{number = 5, name = (null)}
    最大并发数[1909:113432] download6 --- <NSThread: 0x7ffef24aee50>{number = 2, name = (null)}
    

    4.2 队列的暂停和恢复

    队列的暂停:当前任务结束后,暂停执行下一个任务,而非当前任务

    //暂停和恢复队列(YES代表暂停队列,NO代表恢复队列)
    - (void)setSuspended:(BOOL)b;
    
    //当前状态
    - (BOOL)isSuspended;
    

    暂停和恢复的使用场合:

    在tableview界面,开线程下载远程的网络界面,对UI会有影响,使用户体验变差。那么这种情况,就可以设置在用户操作UI(如滚动屏幕)的时候,暂停队列(不是取消队列),停止滚动的时候,恢复队列。


    代码示例:

    #import "ViewController.h"
    
    @interface ViewController ()
    @property(nonatomic, strong) NSOperationQueue *queue; //队列
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
      [super viewDidLoad];
    
      // 1.创建队列
      NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
      // 2.设置最大并发操作数(大并发操作数 = 1,就变成了串行队列)
      queue.maxConcurrentOperationCount = 1;
    
      // 3.添加操作
      [queue addOperationWithBlock:^{
        NSLog(@"download1 --- %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:3];
      }];
    
      [queue addOperationWithBlock:^{
        NSLog(@"download2 --- %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:3];
      }];
      [queue addOperationWithBlock:^{
        NSLog(@"download3 --- %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:3];
      }];
      [queue addOperationWithBlock:^{
        NSLog(@"download4 --- %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:3];
      }];
    
      self.queue = queue;
    }
    
    #pragma mark - 暂停和恢复
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
      if (self.queue.isSuspended) {
        self.queue.suspended = NO; // 恢复队列,继续执行
      } else {
        self.queue.suspended = YES; // 暂停(挂起)队列,暂停执行
      }
    }
    
    - (void)didReceiveMemoryWarning {
      [super didReceiveMemoryWarning];
      // Dispose of any resources that can be recreated.
    }
    
    @end
    

    打印结果:

    队列的暂停和恢复[2650:156206] download1 --- <NSThread: 0x7fd689f552b0>{number = 3, name = (null)}
    队列的暂停和恢复[2650:156205] download2 --- <NSThread: 0x7fd689c02e70>{number = 2, name = (null)}
    队列的暂停和恢复[2650:156206] download3 --- <NSThread: 0x7fd689f552b0>{number = 3, name = (null)}
    队列的暂停和恢复[2650:156385] download4 --- <NSThread: 0x7fd689ea11c0>{number = 4, name = (null)}
    

    4.3 队列的取消

    取消队列的所有操作:相等于调用了所有 NSOperation 的 -(void)cancel 方法,
    当前任务结束后,取消执行下面的所有任务,而非当前任务

    // 也可调用NSOperation的 -(void)cancel 方法取消单个操作
    - (void)cancelAllOperations;
    

    代码示例:

    #import "ViewController.h"
    
    @interface ViewController ()
    @property(nonatomic, strong) NSOperationQueue *queue; //队列
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
      [super viewDidLoad];
    
      // 1.创建队列
      NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
      // 2.设置最大并发操作数(大并发操作数 = 1,就变成了串行队列)
      queue.maxConcurrentOperationCount = 1;
    
      // 3.添加操作
      [queue addOperationWithBlock:^{
        NSLog(@"download1 --- %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:3];
      }];
    
      [queue addOperationWithBlock:^{
        NSLog(@"download2 --- %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:3];
      }];
      [queue addOperationWithBlock:^{
        NSLog(@"download3 --- %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:3];
      }];
      [queue addOperationWithBlock:^{
        NSLog(@"download4 --- %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:3];
      }];
    
      self.queue = queue;
    }
    
    #pragma mark - 取消队列的所有操作
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
      // 取消队列的所有操作(相等于调用了所有NSOperation的-(void)cancel方法)
      [self.queue cancelAllOperations];
    }
    
    - (void)didReceiveMemoryWarning {
      [super didReceiveMemoryWarning];
      // Dispose of any resources that can be recreated.
    }
    
    @end
    

    打印结果:

    队列的取消[3041:167756] download1 --- <NSThread: 0x7fcc09543b40>{number = 3, name = (null)}
    队列的取消[3041:167749] download2 --- <NSThread: 0x7fcc094505f0>{number = 2, name = (null)}
    

    4.4 操作优先级

    设置NSOperation在queue中的优先级,可以改变操作的执行优先级:

    @property NSOperationQueuePriority queuePriority;
    
    - (void)setQueuePriority:(NSOperationQueuePriority)p;
    

    优先级的取值:优先级高的任务,调用的几率会更大

    typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
    };
    

    4.5 操作依赖

    NSOperation之间可以设置依赖来保证执行顺序:不能循环依赖(不能A依赖于B,B又依赖于A)

    // 操作B依赖于操作A(一定要让操作A执行完后,才能执行操作B)
    [operationB addDependency:operationA];
    

    可以在不同queue的NSOperation之间创建依赖关系(跨队列依赖):

    注意:

    • 一定要在把操作添加到队列之前,进行设置操作依赖。
    • 任务添加的顺序并不能够决定执行顺序,执行的顺序取决于依赖。

    代码示例:

    #import "ViewController.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
      [super viewDidLoad];
      // Do any additional setup after loading the view, typically from a nib.
    }
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
      NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
      //创建对象,封装操作
      NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download1----%@", [NSThread currentThread]);
      }];
      NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download2----%@", [NSThread currentThread]);
      }];
      NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download3----%@", [NSThread currentThread]);
      }];
      NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 5; i++) {
          NSLog(@"download4----%@", [NSThread currentThread]);
        }
      }];
    
      NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download5----%@", [NSThread currentThread]);
      }];
      //操作的监听
      op5.completionBlock = ^{
        NSLog(@"op5执行完毕---%@", [NSThread currentThread]);
      };
    
      //设置操作依赖(op4执行完,才执行 op3)
      [op3 addDependency:op1];
      [op3 addDependency:op2];
      [op3 addDependency:op4];
    
      //把操作添加到队列中
      [queue addOperation:op1];
      [queue addOperation:op2];
      [queue addOperation:op3];
      [queue addOperation:op4];
      [queue addOperation:op5];
    }
    
    - (void)didReceiveMemoryWarning {
      [super didReceiveMemoryWarning];
      // Dispose of any resources that can be recreated.
    }
    
    @end
    

    打印结果:

    操作依赖[4196:150518] download5----<NSThread: 0x7ffa61d177d0>{number = 3, name = (null)}
    操作依赖[4196:150506] download1----<NSThread: 0x7ffa61ca6b90>{number = 4, name = (null)}
    操作依赖[4196:150509] download4----<NSThread: 0x7ffa61f0e470>{number = 2, name = (null)}
    操作依赖[4196:150510] download2----<NSThread: 0x7ffa61f0e800>{number = 5, name = (null)}
    操作依赖[4196:150518] op5执行完毕---<NSThread: 0x7ffa61d177d0>{number = 3, name = (null)}
    操作依赖[4196:150509] download4----<NSThread: 0x7ffa61f0e470>{number = 2, name = (null)}
    操作依赖[4196:150509] download4----<NSThread: 0x7ffa61f0e470>{number = 2, name = (null)}
    操作依赖[4196:150509] download4----<NSThread: 0x7ffa61f0e470>{number = 2, name = (null)}
    操作依赖[4196:150509] download4----<NSThread: 0x7ffa61f0e470>{number = 2, name = (null)}
    操作依赖[4196:150509] download3----<NSThread: 0x7ffa61f0e470>{number = 2, name = (null)}
    

    操作的监听

    可以监听一个操作的执行完毕:

    @property (nullable, copy) void (^completionBlock)(void);
    
    - (void)setCompletionBlock:(void (^)(void))block;
    

    代码详见4.5 操作依赖 示例代码

    5.0 线程间通信(图片下载示例)

    #import "ViewController.h"
    
    @interface ViewController ()
    @property(weak, nonatomic) IBOutlet UIImageView *imageView;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
      [super viewDidLoad];
      // Do any additional setup after loading the view, typically from a nib.
    }
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
      [self test2];
    }
    
    #pragma mark - 线程间通信(图片合成)
    - (void)test1 {
      // 1.队列
      NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
      __block UIImage *image1 = nil;
      // 2.下载图片1
      NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{
        // 图片的网络路径
        NSURL *url =
            [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/"
                                 @"8/1/9981681/200910/11/1255259355826.jpg"];
        // 加载图片
        NSData *data = [NSData dataWithContentsOfURL:url];
        // 生成图片
        image1 = [UIImage imageWithData:data];
      }];
    
      __block UIImage *image2 = nil;
      // 3.下载图片2
      NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{
        // 图片的网络路径
        NSURL *url = [NSURL
            URLWithString:
                @"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];
        // 加载图片
        NSData *data = [NSData dataWithContentsOfURL:url];
        // 生成图片
        image2 = [UIImage imageWithData:data];
      }];
    
      // 4.合成图片
      NSBlockOperation *combine = [NSBlockOperation blockOperationWithBlock:^{
        // 开启新的图形上下文
        UIGraphicsBeginImageContext(CGSizeMake(100, 100));
    
        // 绘制图片1
        [image1 drawInRect:CGRectMake(0, 0, 50, 100)];
        image1 = nil;
    
        // 绘制图片2
        [image2 drawInRect:CGRectMake(50, 0, 50, 100)];
        image2 = nil;
    
        // 取得上下文中的图片
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    
        // 结束上下文
        UIGraphicsEndImageContext();
    
        // 5.回到主线程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
          self.imageView.image = image;
        }];
      }];
    
      // 设置依赖操作
      [combine addDependency:download1];
      [combine addDependency:download2];
    
      //把操作添加到队列中
      [queue addOperation:download1];
      [queue addOperation:download2];
      [queue addOperation:combine];
    }
    
    #pragma mark - 线程间通信(图片下载)
    - (void)test2 {
      [[[NSOperationQueue alloc] init] addOperationWithBlock:^{
        // 图片的网络路径
        NSURL *url =
            [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/"
                                 @"8/1/9981681/200910/11/1255259355826.jpg"];
    
        // 加载图片
        NSData *data = [NSData dataWithContentsOfURL:url];
    
        // 生成图片
        UIImage *image = [UIImage imageWithData:data];
    
        // 回到主线程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
          self.imageView.image = image;
        }];
      }];
    }
    
    - (void)didReceiveMemoryWarning {
      [super didReceiveMemoryWarning];
      // Dispose of any resources that can be recreated.
    }
    
    @end
    

    6.0 自定义NSOperation

    自定义NSOperation的步骤很简单:

    • 重写- (void)main方法,在里面实现想执行的任务

    重写- (void)main方法的注意点:

    • 自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
    • 经常通过- (BOOL)isCancelled方法检测操作是否被取消,对取消做出响应

    ViewController.m

    #import "TDOperation.h"
    #import "ViewController.h"
    
    @interface ViewController ()
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
      [super viewDidLoad];
      // Do any additional setup after loading the view, typically from a nib.
    }
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
      // 1.创建队列
      NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
      // 2.创建自定义 TDGOperation
      TDOperation *op = [[TDOperation alloc] init];
    
      // 3.把操作(任务)添加到队列中,并自动调用 start 方法
      [queue addOperation:op];
    }
    
    - (void)didReceiveMemoryWarning {
      [super didReceiveMemoryWarning];
      // Dispose of any resources that can be recreated.
    }
    @end
    

    TDOperation.h(继承自:NSOperation)

    #import <Foundation/Foundation.h>
    
    @interface TDOperation : NSOperation
    @end
    

    TDOperation.m

    #import "TDOperation.h"
    
    @implementation TDOperation
    //需要执行的任务
    - (void)main {
      for (NSInteger i = 0; i < 3; i++) {
        NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
      }
      // 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
      if (self.isCancelled)
        return;
    
      for (NSInteger i = 0; i < 3; i++) {
        NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
      }
      // 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
      if (self.isCancelled)
        return;
    
      for (NSInteger i = 0; i < 3; i++) {
        NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
      }
      // 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
      if (self.isCancelled)
        return;
    }
    @end
    
    

    运行结果:

    自定义NSOperation[1567:84075] download1 -0-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
    自定义NSOperation[1567:84075] download1 -1-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
    自定义NSOperation[1567:84075] download1 -2-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
    自定义NSOperation[1567:84075] download2 -0-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
    自定义NSOperation[1567:84075] download2 -1-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
    自定义NSOperation[1567:84075] download2 -2-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
    自定义NSOperation[1567:84075] download3 -0-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
    自定义NSOperation[1567:84075] download3 -1-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
    自定义NSOperation[1567:84075] download3 -2-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
    

    6.1 自定义NSOperation队列的取消操作

    代码示例:

    ViewController.m
    
    #import "TDOperation.h"
    #import "ViewController.h"
    
    @interface ViewController ()
    @property(nonatomic, strong) NSOperationQueue *queue; //队列
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
      [super viewDidLoad];
    
      // 1.创建队列
      NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
      // 2.设置最大并发操作数(大并发操作数 = 1,就变成了串行队列)
      queue.maxConcurrentOperationCount = 2;
    
      // 3.添加操作 - 自定义 NSOperation
      [queue addOperation:[[TDOperation alloc] init]];
    
      self.queue = queue;
    }
    
    #pragma mark - 取消队列的所有操作
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
      // 取消队列的所有操作(相等于调用了所有NSOperation的-(void)cancel方法)
      [self.queue cancelAllOperations];
    }
    
    - (void)didReceiveMemoryWarning {
      [super didReceiveMemoryWarning];
      // Dispose of any resources that can be recreated.
    }
    
    @end
    

    TDOperation.h

    #import <Foundation/Foundation.h>
    
    @interface TDOperation : NSOperation
    
    @end
    

    TDOperation.m

    #import "TDOperation.h"
    
    @implementation TDOperation
    //需要执行的任务
    - (void)main {
      for (NSInteger i = 0; i < 1000; i++) {
        NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
      }
      // 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
      if (self.isCancelled)
        return;
    
      for (NSInteger i = 0; i < 1000; i++) {
        NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
      }
      // 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
      if (self.isCancelled)
        return;
    
      for (NSInteger i = 0; i < 1000; i++) {
        NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
      }
      // 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
      if (self.isCancelled)
        return;
    }
    @end
    

    6.2 多图下载

    沙盒结构:

    Documents
     Library
        - Caches
        - Preference
     tmp
    

    自定义NSOperation下载图片思路 – 有沙盒缓存


    代码示例:

    ViewController.m

    #import "TDApp.h"
    #import "ViewController.h"
    
    @interface ViewController ()
    @property(nonatomic, strong) NSArray *apps;                   //所有数据
    @property(nonatomic, strong) NSMutableDictionary *imageCache; //内存缓存的图片
    @property(nonatomic, strong) NSOperationQueue *queue;         //队列对象
    @property(nonatomic, strong) NSMutableDictionary *operations; //所有的操作对象
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
      [super viewDidLoad];
      // Do any additional setup after loading the view, typically from a nib.
    }
    
    #pragma mark - 数据源方法
    - (NSInteger)tableView:(UITableView *)tableView
     numberOfRowsInSection:(NSInteger)section {
      return self.apps.count;
    }
    
    #pragma mark - Cell
    - (UITableViewCell *)tableView:(UITableView *)tableView
             cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
      // 重用标识
      static NSString *ID = @"app";
      UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    
      TDApp *app = self.apps[indexPath.row];
    
    #pragma mark - app 名称
      cell.textLabel.text = app.name;
    
    #pragma mark - 下载量
      cell.detailTextLabel.text = app.download;
    
    #pragma mark - 图片
      // 1.先从内存缓存中取出图片
      UIImage *image = self.imageCache[app.icon];
    
      // 2.判断内存中是否有图片
      if (image) {
        // 2.1 内存中有图片,直接设置图片
        cell.imageView.image = image;
      } else {
        // 2.2 内存中没有图片,将图片文件数据写入沙盒中
    
        //(1)获得Library/Caches文件夹
        NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(
            NSCachesDirectory, NSUserDomainMask, YES) firstObject];
        //(2)获得文件名
        NSString *filename = [app.icon lastPathComponent];
        //(3)计算出文件的全路径
        NSString *file = [cachesPath stringByAppendingPathComponent:filename];
        //(4)加载沙盒的文件数据
        NSData *data = [NSData dataWithContentsOfFile:file];
    
        // 2.3 判断沙盒中是否有图片
        if (data) {
    
          // 有图片,直接利用沙盒中图片,设置图片
          UIImage *image = [UIImage imageWithData:data];
          cell.imageView.image = image;
          // 并将图片存到字典中
          self.imageCache[app.icon] = image;
    
        } else {
    
          // 没有图片,先设置一个占位图
          cell.imageView.image = [UIImage imageNamed:@"placeholder"];
    
          // 取出图片,并判断这张图片是否有下载操作
          NSOperation *operation = self.operations[app.icon];
          if (operation == nil) {
            // 如果这张图片暂时没有下载操作,则需要创建一个下载操作
            // 下载图片是耗时操作,放到子线程
            operation = [NSBlockOperation blockOperationWithBlock:^{
              // 下载图片
              NSData *data =
                  [NSData dataWithContentsOfURL:[NSURL URLWithString:app.icon]];
              // 如果数据下载失败
              if (data == nil) {
                // 下载失败,移除操作
                [self.operations removeObjectForKey:app.icon];
                return;
              }
    
                // 下载成功,将图片放在 image 中
              UIImage *image = [UIImage imageWithData:data];
              // 存到字典中
              self.imageCache[app.icon] = image;
    
              //回到主线程显示图片
              [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                [tableView reloadRowsAtIndexPaths:@[ indexPath ]
                                 withRowAnimation:UITableViewRowAnimationNone];
              }];
    
              // 将图片文件数据写入沙盒中
              [data writeToFile:file atomically:YES];
              // 下载完毕,移除操作
              [self.operations removeObjectForKey:app.icon];
            }];
    
            // 添加到队列中(队列的操作不需要移除,会自动移除)
            [self.queue addOperation:operation];
            // 并将图片存到字典中
            self.operations[app.icon] = operation;
          }
        }
      }
    
      return cell;
    }
    
    #pragma mark - 数据懒加载
    - (NSArray *)apps {
      if (!_apps) {
        NSArray *dictArray =
            [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]
                                                 pathForResource:@"apps.plist"
                                                          ofType:nil]];
    
        NSMutableArray *appArray = [NSMutableArray array];
        for (NSDictionary *dict in dictArray) {
          [appArray addObject:[TDApp appWithDict:dict]];
        }
        _apps = appArray;
      }
      return _apps;
    }
    
    #pragma mark - 懒加载
    - (NSMutableDictionary *)imageCache {
      if (!_imageCache) {
        _imageCache = [NSMutableDictionary dictionary];
      }
      return _imageCache;
    }
    
    #pragma mark - 懒加载
    - (NSOperationQueue *)queue {
      if (!_queue) {
        _queue = [[NSOperationQueue alloc] init];
        _queue.maxConcurrentOperationCount = 3;
      }
      return _queue;
    }
    
    #pragma mark - 懒加载
    - (NSMutableDictionary *)operations {
      if (!_operations) {
        _operations = [NSMutableDictionary dictionary];
      }
      return _operations;
    }
    
    - (void)didReceiveMemoryWarning {
      [super didReceiveMemoryWarning];
      // Dispose of any resources that can be recreated.
    }
    
    @end
    

    TDApp.h

    #import <Foundation/Foundation.h>
    
    @interface TDApp : NSObject
    
    @property(nonatomic, strong) NSString *icon;     // 图片
    @property(nonatomic, strong) NSString *download; //下载量
    @property(nonatomic, strong) NSString *name;     // 名字
    
    + (instancetype)appWithDict:(NSDictionary *)dict;
    
    @end
    

    TDApp.m

    #import "TDApp.h"
    
    @implementation TDApp
    
    + (instancetype)appWithDict:(NSDictionary *)dict {
      TDApp *app = [[self alloc] init];
      [app setValuesForKeysWithDictionary:dict];
      return app;
    }
    
    @end
    

    6.3 多图下载 - SDWebImage

    SDWebImage:

    • iOS中著名的网络图片处理框架

    • 包含的功能:图片下载、图片缓存、下载进度监听、gif处理等等

    • 框架地址:https://github.com/rs/SDWebImage

    • SDWebImage的图片缓存周期是:1周


    代码示例:

    ViewController.m

    #import "TDApp.h"
    #import "UIImageView+WebCache.h"
    #import "ViewController.h"
    
    @interface ViewController ()
    @property(nonatomic, strong) NSArray *apps;                   //所有数据
    @property(nonatomic, strong) NSMutableDictionary *imageCache; //内存缓存的图片
    @property(nonatomic, strong) NSOperationQueue *queue;         //队列对象
    @property(nonatomic, strong) NSMutableDictionary *operations; //所有的操作对象
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
      [super viewDidLoad];
      // Do any additional setup after loading the view, typically from a nib.
    }
    
    #pragma mark - 数据源方法
    - (NSInteger)tableView:(UITableView *)tableView
     numberOfRowsInSection:(NSInteger)section {
      return self.apps.count;
    }
    
    #pragma mark - Cell
    - (UITableViewCell *)tableView:(UITableView *)tableView
             cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
      // 重用标识
      static NSString *ID = @"app";
      UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    
      TDApp *app = self.apps[indexPath.row];
    
    #pragma mark - app 名称
      cell.textLabel.text = app.name;
    
    #pragma mark - 下载量
      cell.detailTextLabel.text = app.download;
    
    #pragma mark - 图片
      // expectedSize: 图片的总字节数  receivedSize: 已经接收的图片字节数
      [cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon]
          placeholderImage:[UIImage imageNamed:@"placeholder"]
          options:0 // 0 表示什么都不做
          progress:^(NSInteger receivedSize, NSInteger expectedSize) {
    
            NSLog(@"下载进度:%f", 1.0 * receivedSize / expectedSize);
          }
          completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType,
                      NSURL *imageURL) {
            NSLog(@"下载完图片");
          }];
      return cell;
    }
    
    #pragma mark - 数据懒加载
    - (NSArray *)apps {
      if (!_apps) {
        NSArray *dictArray =
            [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]
                                                 pathForResource:@"apps.plist"
                                                          ofType:nil]];
    
        NSMutableArray *appArray = [NSMutableArray array];
        for (NSDictionary *dict in dictArray) {
          [appArray addObject:[TDApp appWithDict:dict]];
        }
        _apps = appArray;
      }
      return _apps;
    }
    
    #pragma mark - 懒加载
    - (NSMutableDictionary *)imageCache {
      if (!_imageCache) {
        _imageCache = [NSMutableDictionary dictionary];
      }
      return _imageCache;
    }
    
    #pragma mark - 懒加载
    - (NSOperationQueue *)queue {
      if (!_queue) {
        _queue = [[NSOperationQueue alloc] init];
        _queue.maxConcurrentOperationCount = 3;
      }
      return _queue;
    }
    
    #pragma mark - 懒加载
    - (NSMutableDictionary *)operations {
      if (!_operations) {
        _operations = [NSMutableDictionary dictionary];
      }
      return _operations;
    }
    
    #pragma mark - 设置控制器的内存警告
    - (void)didReceiveMemoryWarning {
      [super didReceiveMemoryWarning];
    
      self.imageCache = nil;
      self.operations = nil;
      [self.queue cancelAllOperations];
    }
    
    @end
    

    TDApp.h

    #import <Foundation/Foundation.h>
    
    @interface TDApp : NSObject
    
    @property(nonatomic, strong) NSString *icon;     // 图片
    @property(nonatomic, strong) NSString *download; //下载量
    @property(nonatomic, strong) NSString *name;     // 名字
    
    + (instancetype)appWithDict:(NSDictionary *)dict;
    
    @end
    

    TDApp.m

    #import "TDApp.h"
    
    @implementation TDApp
    
    + (instancetype)appWithDict:(NSDictionary *)dict {
      TDApp *app = [[self alloc] init];
      [app setValuesForKeysWithDictionary:dict];
      return app;
    }
    
    @end
    

    7.0【区别】GCD & NSOperationQueue 队列类型的创建方式

    GCD 队列类型的创建方式:

    (1)并发队列:手动创建、全局

    (2)串行队列:手动创建、主队列


    NSOperationQueue的队列类型的创建方法:

    (1)主队列:[NSOperationQueue mainQueue]

    • 凡是添加到主队列中的任务(NSOperation),都会放到主线程中执行

    (2)其他队列(同时包含了串行、并发功能):[NSOperationQueue alloc]init]

    • 添加到这种队列中的任务(NSOperation),就会自动放到子线程中执行



    注:关于SDWebImage框架的详解会另外再写博客



    如果你觉得本篇文章对你有所帮助,请点击文章末尾右下角“推荐”,^_^

    作者:蓝田(Loto)

    出处:http://www.cnblogs.com/shorfng/

    如有疑问,请在下方 评论区回复 OR 发送邮件shorfng@126.com联系我。



    本文版权归作者和本网站共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。

  • 相关阅读:
    2019 ICPC Malaysia National H题
    欧拉定理证明
    P3384 【模板】树链剖分
    HDU 6070 Dirt Ratio(线段树、二分)
    51Nod 1571 最近等对(线段树、离线查询)
    51Nod 1781 Pinball(线段树、dp、离散化)
    51Nod 1494 选举拉票(权值线段树)
    51Nod 1766 树上的最远点对(欧拉序、lca、线段树区间合并)
    lintcode-179-更新二进制位
    lintcode-178-图是否是树
  • 原文地址:https://www.cnblogs.com/shorfng/p/5327918.html
Copyright © 2011-2022 走看看