iOS资料汇总

NSOperation相关

2016-07-31  本文已影响43人  CoderZb

GCD和NSOperation比较

GCD和NSOperation的对比:
(0)GCD中的队列分为主队列,串行队列,全局并发队列,并发队列。NSOperationQueue中的队列分为主队列和非主队列。所以前提要知道是在GCD中还是NSOperationQueue,才能知道具体某个队列怎么使用。
1)GCD是纯C语言的API,而操作队列则是Object-C的对象。
2)在GCD中,任务用块(block)来表示,而块是个轻量级的数据结构;
  相反操作队列中的『操作』NSOperation则是个更加重量级的Object-C对象。
3)具体该使用GCD还是使用NSOperation需要看具体的情况

NSOperation和NSOperationQueue的好处有:
1)NSOperationQueue可以方便的调用cancel方法来取消某个操作,而GCD中的任务是无法被取消的(安排好任务之后就不管了)。
2)NSOperation可以方便的指定操作间的依赖关系。
3)NSOperation可以通过KVO提供对NSOperation对象的精细控制(如监听当前操作是否被取消或是否已经完成等)
4)NSOperation可以方便的指定操作优先级。操作优先级表示此操作与队列中其它操作之间的优先关系,优先级高的操作先执行,优先级低的后执行。
5)通过自定义NSOperation的子类可以实现操作重用(实现代码复用性),

NSOperationQueue中的两种队列


操作依赖+操作监听


NSOperation的3个子类


第一个子类:NSBlockOperation(常用)

简单使用:
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1---%@",[NSThread currentThread]);
    }];
    
[op1 start];


---------
具体应用:
具体应用----->内部已经将调用的start方法封装在底层,不需要写出来了
    //1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //2.封装操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1----%@",[NSThread currentThread]);
        
    }];   
   
   //3.添加操作到队列
    [queue addOperation:op1];

---
具体应用的简便方法
    //1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    //2.封装操作+添加操作到队列
    [queue addOperationWithBlock:^{
        NSLog(@"6---%@",[NSThread currentThread]);
    }];


第二个子类:NSInvocationOperation

NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];

[op1 start];

---
具体应用:
    //1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    //2.封装操作
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    //3.添加操作到队列
    //addOperation方法内部调用了start方法,start方法内部调用了main方法
    [queue addOperation:op1];
    
    -(void)download1{
    NSLog(@"%s---%@",__func__,[NSThread currentThread]);
}

第三个子类:自定义NSOperation

简单使用:
ZBOperation *op1 = [[ZBOperation alloc]init];//ZBOperation继承NSOperation

[op1 start];//调用start本质是调用main方法


--------
具体应用:

    //1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //2.封装任务
    ZBOperation *op1 = [[ZBOperation alloc]init];
    
     //3.添加操作到队列
    [queue addOperation:op1];
    
    
    ZBOperation继承NSOperation。并且在NSOperation.m文件中重写了main方法
 
    -(void)main{
    NSLog(@"1----%@",[NSThread currentThread]);
    }

最大并发数:maxConcurrentOperationCount

27-10.png
27-11.png

NSOperation实现线程间通信

ViewController.m文件
#import "ViewController.h"

@interface ViewController ()

/** 声明全局变量,防止出了花括号被销毁*/
@property (nonatomic ,strong) UIImage *image1;
@property (nonatomic ,strong) UIImage *image2;

//已经和storyboard的UIImageView控件关联
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end

@implementation ViewController

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self combie];
}
//思想:下载图片的操作一定要放在子线程做(规定).步骤1,2可以实现下载图片的在子线程做
//     刷新UI的操作一定要在主线程执行(规定)。可以通过[NSOperationQueue mainQueue]的形式回到主线程
-(void)combie
{
    //1.创建队列
    NSOperationQueue *queue =[[NSOperationQueue alloc]init];
    
    //2.封装操作
    NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{
        //2.1 请求url
        NSURL *url = [NSURL URLWithString:@"http://img.qiyenet.net/upload/image/2016/03/05/1457133748832510.png"];
        
        //2.2 下载图片的二进制数据
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        //2.3 转换图片
        self.image1 = [UIImage imageWithData:data];
    }];
    
    //3.下载图片2
    NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{
        //3.1 请求url
        NSURL *url = [NSURL URLWithString:@"http://www.52tq.net/uploads/allimg/160226/1021043B3-3.jpg"];
        
        //3.2 下载图片的二进制数据
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        //3.3转换图片
        self.image2 = [UIImage imageWithData:data];
    }];
    
    //4.合并图片
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        UIGraphicsBeginImageContext(CGSizeMake(200, 200));
        [self.image1 drawInRect:CGRectMake(0, 0, 100, 200)];
        [self.image2 drawInRect:CGRectMake(100, 0, 100, 200)];
        self.image1 = nil;
        self.image2 = nil;
        
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        
        //线程间通信(在主线程中执行)
        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
            self.imageView.image = image;
            
        }];
    }];

    
    //5.设置依赖关系
    [op addDependency:download1];
    [op addDependency:download2];
    
    //6.添加操作到队列
    [queue addOperation:op];
    [queue addOperation:download1];
    [queue addOperation:download2];

}

@end


多图下载综合案例(重复下载+卡顿)

#import "ViewController.h"
#import "ZBApp.h"

@interface ViewController ()
@property (nonatomic, strong)NSArray *apps;
/** 图片缓存*/
@property (nonatomic ,strong) NSMutableDictionary *images;
@end

@implementation ViewController

-(NSMutableDictionary *)images
{
    if (_images == nil) {
        _images = [NSMutableDictionary dictionary];
    }
    return _images;
}
-(NSArray *)apps
{
    if (_apps == nil) {
        
        //字典数组
        NSArray *arrayM = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]pathForResource:@"apps.plist" ofType:nil]];
        
        //字典数组 --- >模型数组
        NSMutableArray *arrayMode = [NSMutableArray array];
        for (NSDictionary *dict in arrayM) {
            ZBApp *x = [ZBApp appWithDict:dict];
            [arrayMode addObject:x];
        }
        _apps = arrayMode;
    }
    return _apps;
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.apps.count;
}

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    //1.创建cell
    static NSString *ID = @"app";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    
    //2.设置cell的数据
    //2.1 拿到该行cell 对应的数据
    ZBApp *appM = self.apps[indexPath.row];

    //2.2 设置标题
    cell.textLabel.text = appM.name;

    //2.3 设置子标题
    cell.detailTextLabel.text = appM.download;
    
    //2.4 设置图片
    //出现的问题1:重复下载问题:当再次浏览之前已经下载过的图片的时候,用户依旧会花流量下载这张图片
    //出现的问题2:卡顿,UI不流畅问题:因为是在在主线程下载图片,通过打印也可以得知,所以会卡顿,放在子线程里下载即可
    NSURL *url = [NSURL URLWithString:appM.icon];
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];
    cell.imageView.image = image;
    NSLog(@"%zd--直接下载--%@",indexPath.row,[NSThread currentThread]);
    //3.返回cell
    return cell;
}
46-13.gif
 UIImage *image = [self.images objectForKey:appM.icon];//NSMutableDictionary获取元素的全写形式
    //和上面的代码等价 UIImage *image = self.images[appM.icon];//NSMutableDictionary获取元素的简写形式
   
    if(image){
        //直接设置
        cell.imageView.image = image;
        NSLog(@"%zd使用了内存缓存",indexPath.row);

    }else{// 自己下载
            NSURL *url = [NSURL URLWithString:appM.icon];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];
            cell.imageView.image = image;
            
            //把图片存到内存缓存中
            [self.images setObject:image forKey:appM.icon];
            
            NSLog(@"%zd--直接下载--%@",indexPath.row,[NSThread currentThread]);
            }

多图下载综合案例(优化完毕)

代码中的设置图片的过程(也就是SDWebImage的底层实现原理):
27-16.png

文字描述上述截图(SDWebImage的底层实现原理)

入口是UIImageView+WebCache这个分类,在分类里面执行下载图片的方法sd_setImageWithURL:placeholderImage: ;
1.首先以url作为数据的索引先在内存中寻找是否有对应的缓存(内存缓存),如果找到了,就设置图片显示;
2.如果在内存缓存中没找到,就会通过MD5处理过的key来磁盘中查询数据(磁盘缓存,沙盒缓存),如果找到了,就把磁盘中的数据加载到内存中,并设置图片显示;
3.如果在磁盘中没找到,就会检查操作缓存,看有没有正在下载,有,就什么也不做;没有,就会向远程服务器发出请求,下载图片,下载后的图片会加入到缓存中,并写入磁盘。
注意:获取图片的过程都是在子线程中执行,获取图片后回到主线程显示图片
    ```

---


- 从内存缓存中取数据比在磁盘缓存中去数据快。就比如你把钱放在兜里(内存缓存)和放在银行里(磁盘缓存),你要花钱时,哪个能马上拿出来使用呢
- 注意点:apps.plist文件中的图片都是网络上的图片,而不是导入的项目中的图片
- 字典转模型的方法 setValuesForKeysWithDictionary:从字面意思可以看出来,后面的参数必须是字典NSDictionary类型的。即可逆推,必须先把数组先转换成NSDictionary类型的。然后才可以把字典转成模型。
- 磁盘缓存(即沙盒缓存,在Library/Caches路径下)
  - 只要用户下载了一次,重新打开时,不需要耗费流量

---

####ViewController.m文件
```objc
#import "ViewController.h"
#import "ZBApp.h"

@interface ViewController ()
@property (nonatomic, strong)NSArray *apps;
/** 队列*/
@property (nonatomic ,strong) NSOperationQueue *queue;
/** 图片缓存*/
@property (nonatomic ,strong) NSMutableDictionary *images;
@property (nonatomic ,strong) NSMutableDictionary *operations;
@end

@implementation ViewController

#pragma mark --------------------
#pragma mark lazy loading
-(NSOperationQueue *)queue
{
   if (_queue == nil) {
        _queue = [[NSOperationQueue alloc]init];
       // 最大并发数,最多允许5个操作
       _queue.maxConcurrentOperationCount = 5;
   }
   return _queue;
}

-(NSMutableDictionary *)images
{
   if (_images == nil) {
       _images = [NSMutableDictionary dictionary];
   }
   return _images;
}
-(NSArray *)apps
{
   if (_apps == nil) {
       
       //字典数组
       NSArray *arrayM = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]pathForResource:@"apps.plist" ofType:nil]];
       
       //字典数组 --- >模型数组
       NSMutableArray *arrayMode = [NSMutableArray arrayWithCapacity:arrayM.count];
       for (NSDictionary *dict in arrayM) {
           [arrayMode addObject:[ZBApp appWithDict:dict]];
       }
       _apps = arrayMode;
   }
   return _apps;
}

-(NSMutableDictionary *)operations
{
   if (_operations == nil) {
       _operations = [NSMutableDictionary dictionary];
   }
   return _operations;
}

#pragma mark --------------------
#pragma mark UITableViewDataSource
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
   return self.apps.count;
}

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
   return 1;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
   //1.创建cell
   static NSString *ID = @"app";
   
   UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
   
   //2.设置cell的数据
   //2.1 拿到该行cell 对应的数据
   ZBApp *appM = self.apps[indexPath.row];
   
   //2.2 设置标题
   cell.textLabel.text = appM.name;
   
   //2.3 设置子标题
   cell.detailTextLabel.text = appM.download;
   
   /*  //精华流程:呕心沥血.  总结:都是要保存到内存缓存,下次访问,直接从内存缓存里面取就行
    
       //解决卡顿问题:在子线程中下载图片
    
       //三大方面彻底解决了图片重复下载的问题:1.判断内存中有没有这张图片(术语:内存缓存)
                                        2.判断磁盘(沙盒--->Library/Caches)中有没有这张图片(术语:磁盘缓存)
                                        3.判断图片有没有正在下载。(属于:操作缓存)
                                        总结:从1,2,3三大方面彻底解决了图片重复下载的问题
      
       //解决数据错乱的问题:1.清空图片-------> cell.imageView.image =nil;
                         2.设置占位图片---> cell.imageView.image = [UIImage imageNamed:@"Snip20200808_172"];
           数据错乱问题原因:之前下载的图片会放到内存缓存中,因为是循环利用,所以会循环利用到其他的cell上,但是其他的cell有自己要显示的内容,其他的cell的内容自己下载,然后显示到自己的cell上
    
       //设置并显示图片的过程大解析:
    
               有--->直接设置图片
先检查内存缓存--<                         有---->使用二进制数据+设置图片,并保存到内存缓存
               没有-->检查磁盘(沙盒)缓存<                               有--->等待图片下载完就行,你不需要做任何事情
                                     没有---->判断图片有没有正在下载<
                                                                  没有--->封装操作(操作的block里实现下载图片),将操作添加到队列中
    */
   //2.4 设置图片
   //检查缓存
   // 根据字典中的key获取value,这里的可以就是appM.icon
   UIImage *image = [self.images objectForKey:appM.icon];//NSMutableDictionary获取元素的全写形式
   //和上面的代码等价 UIImage *image = self.images[appM.icon];//NSMutableDictionary获取元素的简写形式
   if(image)
   {
       //直接设置
       cell.imageView.image = image;
       NSLog(@"%zd使用了内存缓存",indexPath.row);
   }else
   {
       NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];// a
       //得到图片的名称(图片的最后一个结点 ,就是最后一个/后面的名称)
       NSString *filename = [appM.icon lastPathComponent];// b
       //拼接文件的全路径(沙盒路径+图片链接中的最后一个下划线+下划线后面的内容)--->将来下载的图片保存到这个全路径中
       NSString *fullPath = [cachesPath stringByAppendingPathComponent:filename];// c
       NSLog(@"%@",fullPath);
       // fullPath = cachesPath+ / + filename(每一行cell对应不同的filename)
       // 即fullPath 等于  Library/Caches + / + t017bc3cfcf3981b197.png
     
       //去检查磁盘缓存
       //a,b,c都是为d做准备,即a,b,c最终就是得到文件(图片)的全路径fullPath)
       NSData *data = [NSData dataWithContentsOfFile:fullPath];// d
       data = nil;
       if(data)
       {
           UIImage *image = [UIImage imageWithData:data];
           cell.imageView.image = image;
           
           //把图片存到内存缓存中(将appM.icon这个key对应的image的内容存到可变字典中),解决重复下载的问题
           [self.images setObject:image forKey:appM.icon];
           
           NSLog(@"%zd使用了磁盘缓存",indexPath.row);
       }else
       {
           //清空图片或者是设置占位图片  什么图片都可以 这里是设置了占位图片,网上的图片下载到本地之前先用占位图片顶替上。当网上的图片下载完毕之后,自动替换掉占位图
           //cell.imageView.image = [UIImage imageNamed:@"Snip20200808_172"];
           
           
           //检查操作缓存
           
           //创建非主队列(让这个非主队列存储16个任务)-->也是并发队列
           //通过判断dowbloadOperation有没有值,可以得知某一行cell的图片有没有被下载。因为[self.queue addOperation:dowbloadOperation];

           NSBlockOperation *dowbloadOperation = [self.operations objectForKey:appM.icon];
           //等价于 NSBlockOperation *dowbloadOperation = self.operations[appM.icon];
           if (dowbloadOperation) {
               //如果存在,那么什么都不做
           }else
           {   //因为下载图片(a,b,c)是耗时操作(需要一定的时间才能下载完),所以应将下载图片放到子线程NSBlockOperation中执行
              
               dowbloadOperation = [NSBlockOperation blockOperationWithBlock:^{//注意:以下都是block块里面的内容.折叠就可以看出来
                   NSURL *url = [NSURL URLWithString:appM.icon];//a
                   NSData *data = [NSData dataWithContentsOfURL:url];//b 请求超时时间为30秒,超过30秒,请求失败
//                    模拟延迟下载图片
//                    for(int i = 0;i<1000000000;i++){
//                    
//                    }
       /***************************************这行才是真正的下载图片***************************************************/
                   UIImage *image = [UIImage imageWithData:data];//c
                   
                   //容错操作。 如果apps.plist文件中的某个图片路径不存在,就把这个图片移除,用占位图片顶替。如果没有这个容错操作,那么程序将会崩掉.[已验证]
                   if (image == nil) {
                       [self.images removeObjectForKey:appM.icon];
                       return;
                   }
                   //把图片存到内存缓存中。就是把图片存到了self.images可变字典中
                   [self.images setObject:image forKey:appM.icon];
                   NSLog(@"%zd直接下载---%@",indexPath.row,[NSThread currentThread]);
                   
                   //把图片保存到磁盘缓存(规定:只有用二进制数据才能够将图片保存到磁盘缓存)
                   [data writeToFile:fullPath atomically:YES];
                   
                   //设置图片(必须放在主线程中(等同主队列),图片才能显示,如果放在子线程,则显示不了图片)
                   [[NSOperationQueue mainQueue]addOperationWithBlock:^{

                       //刷新指定的行(局部刷新)   第一个参数:数组  第二个参数:动画
                       //之所以不用写cell.imageView.image = image;的原因是,执行reloadRowsAtIndexPaths方法刷新指定行时,程序又会执行cellForRowAtIndexPath方法,所以必定执行if(image){}里面的内容。所以图片会显示
                       [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
                   }];
                   
               }];
   
               //添加操作到缓存中
               /*
                *********************************************************************************
                *   目的:防止重复下载
                *   出现重复下载的原因:网速的原因。详细原因如下面4个步骤。如果用户浏览的图片马上能显示,不用10秒,就不会有下面的问题了
                *   1.用户浏览某一行的cell的图片,就会调用cellForRowAtIndexPath方法,这一行cell的图片需要10秒才能下载完,然后用户没等这行cell的图片下载完(没有下载完,图片就不会存到内存缓存
                *   中),就去浏览下一页的cell了。
                *   2.当4秒后,用户再次浏览这一行cell的图片时,这张图片需要6秒才可以下载完,说明这时候图片仍没有存到内存缓存中
                *   3.这个时候要显示这一行cell的时候,系统底层又会自动调用cellForRowAtIndexPath方法
                *   4.在cellForRowAtIndexPath方法中:4.1先去检查内存缓存,内存缓存没有这张图片
                *                                  4.2再检查磁盘缓存,磁盘缓存也没有
                *                                  4.3此时就封装操作,将操作添加到队列中去下载图片,此时这张图片已经有两个下载任务
                *   ,所以最终造成了同一张图片被直接下载了两次,所以就出现了重复下载的问题。
                *   解决办法:检查操作缓存,显示某一行cell的图片,调用cellForRowAtIndexPath方法,最终会检查操作中有没有这个图片的下载任务(dowbloadOperation),dowbloadOperation有值,就什么也不做,这就解决了重复下载图片的问题
                *********************************************************************************
                
                */
               
               //self.operations的数组中执行的是并发队列,一次就可以下载多张图片
               [self.operations setObject:dowbloadOperation forKey:appM.icon];
               
                //将dowbloadOperation中保存的操作添加到队列中进行下载图片
               [self.queue addOperation:dowbloadOperation];
           }
       }
   }
   
   //3.返回cell
   return cell;
}
-(void)didReceiveMemoryWarning{
   //如果出现了内存警告,就做如下操作,保命要紧啊。
   
   //移除内存缓存
   [self.images removeAllObjects];
   
   //取消队列中的操作
   [self.queue cancelAllOperations];
}


@end

ZBApp.h文件

#import <Foundation/Foundation.h>

@interface ZBApp : NSObject
/** 名称*/
@property (nonatomic ,strong) NSString *name;
/** 图标的地址*/
@property (nonatomic ,strong) NSString *icon;
/** 下载量*/
@property (nonatomic ,strong) NSString *download;

+(instancetype)appWithDict:(NSDictionary *)dict;
@end


ZBApp.m文件

#import "ZBApp.h"

@implementation BApp

+(instancetype)appWithDict:(NSDictionary *)dict
{
    ZBApp *appM = [[ZBApp alloc]init];
    [appM setValuesForKeysWithDictionary:dict];
    return appM;
}
@end


本Demo核心代码:


//检查内存缓存
UIImage *image = [self.images objectForKey:appM.icon];

//检查磁盘缓存
NSData *data = [NSData dataWithContentsOfFile:fullPath];

//检查操作缓存
NSBlockOperation *dowbloadOperation = [self.operations objectForKey:appM.icon];
    
    
将得到的对象利用if进行判断是否有缓存,自己设定有缓存应该做什么操作,没有缓存应该做什么操作
         

上一篇下一篇

猜你喜欢

热点阅读