ios基礎篇(十四)—— 操做依賴、操做緩存池

1、NSOperation VS GCD

  • GCD
    • GCD是iOS4.0 推出的,主要針對多核cpu作了優化,是C語言的技術
    • GCD是將任務(block)添加到隊列(串行/並行/全局/主隊列),而且以同步/異步的方式執行任務的函數
    • GCD提供了一些NSOperation不具有的功能
      • 一次性執行
      • 延遲執行
      • 調度組
  • NSOperation
    • NSOperation是iOS2.0推出的,iOS4以後重寫了NSOperation
    • NSOperation將操做(異步的任務)添加到隊列(併發隊列),就會執行指定操做的函數
    • NSOperation裏提供的方便的操做
      • 最大併發數
      • 隊列的暫定/繼續
      • 取消全部的操做
      • 指定操做之間的依賴關係(GCD能夠用同步實現)

2、最大併發數

  • 什麼是併發數

同時執行的任務數數組

好比,同時開3個線程執行3個任務,併發數就是3緩存

  • 最大併發數的相關方法
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
  • 執行的過程
  • 一、把操做添加到隊列self.queue addOperationWithBlock
  • 二、去線程池去取空閒的線程,若是沒有就建立線程
  • 三、把操做交給從線程池中取出的線程執行
  • 四、執行完成後,把線程再放回線程池中
  • 五、重複2,3,4知道全部的操做都執行完

隊列的暫停、取消、恢復

取消隊列的全部操做
- (void)cancelAllOperations;
提示:也能夠調用NSOperation的- (void)cancel方法取消單個操做
暫停和恢復隊列
- (void)setSuspended:(BOOL)b; // YES表明暫停隊列,NO表明恢復隊列
- (BOOL)isSuspended;

搖獎機

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *lbl1;
@property (weak, nonatomic) IBOutlet UILabel *lbl2;
@property (weak, nonatomic) IBOutlet UILabel *lbl3;
@property (weak, nonatomic) IBOutlet UIButton *startButton;
//全局隊列
@property (nonatomic, strong) NSOperationQueue *queue;
@end

@implementation ViewController
//懶加載
- (NSOperationQueue *)queue {
    if (_queue == nil) {
        _queue = [NSOperationQueue new];
    }
    return _queue;
}
//點擊開始執行
- (IBAction)start:(UIButton *)sender {
    //當隊列中有操做的時候,不添加操做    
    if (self.queue.operationCount == 0) {
        //異步執行 添加操做
        [self.queue addOperationWithBlock:^{
            [self random];
        }];
        [self.startButton setTitle:@"暫停" forState:UIControlStateNormal];
        self.queue.suspended = NO;
    }else if(!self.queue.isSuspended) {
        //正在執行的時候,暫停
        //先把當前的操做執行完畢,暫停後續的操做
        self.queue.suspended = YES;
        [self.startButton setTitle:@"繼續" forState:UIControlStateNormal];
    }    
}
//隨機生成3個數字,顯示到label上
- (void)random {
    while (!self.queue.isSuspended) {
        [NSThread sleepForTimeInterval:0.05];
        //生成隨機數     [0,10)  0-9
        int num1 = arc4random_uniform(10);
        int num2 = arc4random_uniform(10);
        int num3 = arc4random_uniform(10);
        //回到主線程上更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            //給label賦值
            self.lbl1.text = [NSString stringWithFormat:@"%d",num1];
            self.lbl2.text = [NSString stringWithFormat:@"%d",num2];
            self.lbl3.text = [NSString stringWithFormat:@"%d",num3];            
        }];
    }
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%zd",self.queue.operationCount);
}

3、操做的優先級

  • 設置NSOperation在queue中的優先級,能夠改變操做的執行優先級
- (NSOperationQueuePriority)queuePriority;
- (void)setQueuePriority:(NSOperationQueuePriority)p;
  • iOS8之後推薦使用服務質量 qualityOfService

監聽操做完成

  • 能夠監聽一個操做的執行完畢
- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;
@interface ViewController ()
@property (nonatomic, strong) NSOperationQueue *queue;
@end

@implementation ViewController
//懶加載
- (NSOperationQueue *)queue{
    if (_queue == nil) {
        _queue = [NSOperationQueue new];
    }
    return _queue;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    //操做1
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 20; i++) {
            NSLog(@"op1  %d",i);
        }
    }];
    //設置優先級最高
    op1.qualityOfService = NSQualityOfServiceUserInteractive;
    [self.queue addOperation:op1];    
    //等操做完成,執行  執行在子線程上
    [op1 setCompletionBlock:^{
        NSLog(@"============op1 執行完成========== %@",[NSThread currentThread]);
    }]; 
    //操做2
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 20; i++) {
            NSLog(@"op2  %d",i);
        }
    }];
    //設置優先級最低
    op2.qualityOfService = NSQualityOfServiceBackground;
    [self.queue addOperation:op2];
}
@end

4、操做依賴

  • NSOperation之間能夠設置依賴來保證執行順序

好比必定要讓操做A執行完後,才能執行操做B,能夠這麼寫網絡

[operationB addDependency:operationA]; // 操做B依賴於操做A

能夠在不一樣queue的NSOperation之間建立依賴關係併發

模擬軟件升級過程:下載—解壓—升級完成app

@interface ViewController ()
@property (nonatomic, strong) NSOperationQueue *queue;
@end
@implementation ViewController
- (NSOperationQueue *)queue {
    if (_queue == nil) {
        _queue = [NSOperationQueue new];
    }
    return _queue;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // 下載 - 解壓 - 升級完成
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下載");
    }];    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"解壓");
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"升級完成");
    }];
    //設置操做間的依賴
    [op2 addDependency:op1];
    [op3 addDependency:op2];
    //錯誤,會發生循環依賴,什麼都不執行
//    [op1 addDependency:op3]; 
    //操做添加到隊列中
    [self.queue addOperations:@[op1,op2] waitUntilFinished:NO];
    //依賴關係能夠誇隊列執行
    [[NSOperationQueue mainQueue] addOperation:op3];
}

案例-UITableView中顯示圖片

步驟1—數據模型準備

  • 把準備好的數據源(plist)轉換成咱們使用方便的對象集合

    從字典類型自定綁定屬性

  [obj setValuesForKeysWithDictionary:dict];

提供方法—---生成全部對象的一個集合dom

  + (NSArray *)appList;

搭建界面

  • UITabelViewController
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

把name和download都先顯示到界面上異步

同步方式下載圖片

  • 在cell生成的時候下載圖片
     //模擬網絡延時
    [NSThread sleepForTimeInterval:0.5];

    NSURL *url = [NSURL URLWithString:appInfo.icon];
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *img = [UIImage imageWithData:data];

問題:進入界面很慢,一滑動就卡函數

緣由:同步方式去下載圖片的時候,系統沒法很快執行界面渲染,俗稱「卡主線程」測試

問題:每次上拉下拉的時候都會重複下載圖片優化

ViewController.m

#import "ViewController.h"
#import "HMAppInfo.h"
@interface ViewController ()
@property (nonatomic, strong) NSArray *appInfos;
@end
//1 建立模型類,獲取數據,測試
//2 數據源方法
//3 同步下載圖片
@implementation ViewController
//懶加載
- (NSArray *)appInfos {
    if (_appInfos == nil) {
        _appInfos = [HMAppInfo appInfos];
    }
    return _appInfos;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //1 測試模型數據
//    NSLog(@"%@",self.appInfos);
}
//2 數據源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.appInfos.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    //1 建立可重用的cell
    static NSString *reuseId = @"appInfo";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseId];
    }
    //2 獲取數據,給cell內部子控件賦值
    HMAppInfo *appInfo = self.appInfos[indexPath.row];    
    cell.textLabel.text = appInfo.name;
    cell.detailTextLabel.text = appInfo.download;    
    //同步下載圖片
    //模擬網速比較慢
    [NSThread sleepForTimeInterval:0.5];
    NSURL *url = [NSURL URLWithString:appInfo.icon];
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *img = [UIImage imageWithData:data];
    cell.imageView.image = img;    
    //3 返回cell
    return cell;
}
@end

HMAppInfo.h

@interface HMAppInfo : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *icon;
@property (nonatomic, copy) NSString *download;
+ (instancetype)appInfoWithDic:(NSDictionary *)dic;
//獲取全部的模型數據
+ (NSArray *)appInfos;
@end

HMAppInfo.m

#import "HMAppInfo.h"
@implementation HMAppInfo
+ (instancetype)appInfoWithDic:(NSDictionary *)dic {
    HMAppInfo *appInfo = [[self alloc] init];
    //kvc給屬性賦值
    [appInfo setValuesForKeysWithDictionary:dic];
    return appInfo;
}
+ (NSArray *)appInfos{
    //加載plist
    NSString *path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
    NSArray *array = [NSArray arrayWithContentsOfFile:path];    
    NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:10];
    //便利數組的另外一種方式
    [array enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        //字典轉模型
        HMAppInfo *appInfo = [self appInfoWithDic:obj];
        [mArray addObject:appInfo];
    }];    
    //返回  對可變數組進行copy操做。變成不可變數組
    return mArray.copy;
}
@end

異步方式下載圖片

  • 異步方式下載圖片
//異步加載網絡圖片
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    }];
  • 問題1:圖片顯示不出,點擊才顯示
  • 緣由:cell中的imageView是懶加載的,在初始化的時候圖片沒有指定,因此圖片大小爲0x0
  • 解決:設置佔位圖片

ViewController.m 

#import "ViewController.h"
#import "HMAppInfo.h"
#import "HMAppInfoCell.h"
@interface ViewController ()
@property (nonatomic, strong) NSArray *appInfos;
//全局隊列
@property (nonatomic, strong) NSOperationQueue *queue;
@end
//1 建立模型類,獲取數據,測試
//2 數據源方法
//3 同步下載圖片--若是網速比較慢,界面會卡頓
//4 異步下載圖片--圖片顯示不出來,點擊cell或者上下拖動圖片能夠顯示
    //解決,使用佔位圖片
@implementation ViewController
//懶加載
- (NSArray *)appInfos {
    if (_appInfos == nil) {
        _appInfos = [HMAppInfo appInfos];
    }
    return _appInfos;
}
- (NSOperationQueue *)queue {
    if (_queue == nil) {
        _queue = [NSOperationQueue new];
    }
    return _queue;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //1 測試模型數據
//    NSLog(@"%@",self.appInfos);
}
//2 數據源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.appInfos.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    //1 建立可重用的cell
    static NSString *reuseId = @"appInfo";
    HMAppInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId];
    if (cell == nil) {
        cell = [[HMAppInfoCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseId];
    }
    //2 獲取數據,給cell內部子控件賦值
    HMAppInfo *appInfo = self.appInfos[indexPath.row];    
    //cell內部的子控件都是懶加載的
    //當返回cell以前,會調用cell的layoutSubviews方法
    cell.textLabel.text = appInfo.name;
    cell.detailTextLabel.text = appInfo.download;
    //設置佔位圖片
    cell.imageView.image = [UIImage imageNamed:@"user_default"];
    //異步下載圖片
    //模擬網速比較慢
    [self.queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSURL *url = [NSURL URLWithString:appInfo.icon];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *img = [UIImage imageWithData:data];        
        //主線程上更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            cell.imageView.image = img;
        }];
    }];
    //3 返回cell
    return cell;
}
@end

HMAppInfoCell.m 

#import "HMAppInfoCell.h"
@implementation HMAppInfoCell
- (void)layoutSubviews {
    [super layoutSubviews];    
    NSLog(@"layoutSubviews");
}
@end
  • 問題2:頻繁滾動時,反覆下載相同圖片
  • 解決:圖片緩存,模型類中增長image的屬性

 

  • 問題3:頻繁滾動時,而且超出屏幕的圖片下載速度比較慢的狀況,圖片可能錯位,而且圖片來回跳
  • 緣由:cell的重用
  • 解決:當異步下載完成後,回到主線程從新加載對應的cell

ViewController.m

#import "ViewController.h"
#import "HMAppInfo.h"
#import "HMAppInfoCell.h"
@interface ViewController ()
@property (nonatomic, strong) NSArray *appInfos;
//全局隊列
@property (nonatomic, strong) NSOperationQueue *queue;
@end
//1 建立模型類,獲取數據,測試
//2 數據源方法
//3 同步下載圖片--若是網速比較慢,界面會卡頓
//4 異步下載圖片--圖片顯示不出來,點擊cell或者上下拖動圖片能夠顯示
    //解決,使用佔位圖片
//5 圖片緩存--把網絡上下載的圖片,保存到內存
    //解決,圖片重複下載,把圖片緩存到內存中,節省用戶的流量 (拿空間換取執行時間)
//6 解決圖片下載速度特別慢,出現的錯行問題。
    //當圖片下載完成以後,從新加載對應的cell
@implementation ViewController
//懶加載
- (NSArray *)appInfos {
    if (_appInfos == nil) {
        _appInfos = [HMAppInfo appInfos];
    }
    return _appInfos;
}
- (NSOperationQueue *)queue {
    if (_queue == nil) {
        _queue = [NSOperationQueue new];
    }
    return _queue;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //1 測試模型數據
//    NSLog(@"%@",self.appInfos);
}
//2 數據源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.appInfos.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    //1 建立可重用的cell
    static NSString *reuseId = @"appInfo";
    HMAppInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId];
    if (cell == nil) {
        cell = [[HMAppInfoCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseId];
    }
    //2 獲取數據,給cell內部子控件賦值
    HMAppInfo *appInfo = self.appInfos[indexPath.row];   
    //cell內部的子控件都是懶加載的
    //當返回cell以前,會調用cell的layoutSubviews方法
    cell.textLabel.text = appInfo.name;
    cell.detailTextLabel.text = appInfo.download;    
    //判斷有沒有圖片緩存
    if (appInfo.image) {
        NSLog(@"從緩存加載圖片...");
        cell.imageView.image = appInfo.image;
        return cell;
    } 
    //設置佔位圖片
    cell.imageView.image = [UIImage imageNamed:@"user_default"];    
    //異步下載圖片
    //模擬網速比較慢
    [self.queue addOperationWithBlock:^{
//        [NSThread sleepForTimeInterval:0.5];
        //模擬圖片下載速度慢
        if (indexPath.row > 9) {
            [NSThread sleepForTimeInterval:5];
        }        
        //下載圖片
        NSLog(@"下載網絡圖片...");
        NSURL *url = [NSURL URLWithString:appInfo.icon];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *img = [UIImage imageWithData:data];        
        //緩存圖片
        appInfo.image = img;        
        //主線程上更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
//            cell.imageView.image = img;
            //解決圖片顯示錯行
            [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
        }];
    }];
    //3 返回cell
    return cell;
}
@end

操做緩存池

  • 問題:因爲滑動過快致使屢次下載相同圖片
  • 解決:操做緩存
  • 定義一個字典,把每次操做都放在裏面,避免相同操做的出現 
  NSMutableDictionary *operationCaches;
@interface ViewController ()
@property (nonatomic, strong) NSArray *appInfos;
//全局隊列
@property (nonatomic, strong) NSOperationQueue *queue;
//圖片的緩存池
@property (nonatomic, strong) NSMutableDictionary *imageCache;
//下載操做緩存池
@property (nonatomic, strong) NSMutableDictionary *downloadCache;
@end
//1 建立模型類,獲取數據,測試
//2 數據源方法
//3 同步下載圖片--若是網速比較慢,界面會卡頓
//4 異步下載圖片--圖片顯示不出來,點擊cell或者上下拖動圖片能夠顯示
    //解決,使用佔位圖片
//5 圖片緩存--把網絡上下載的圖片,保存到內存--圖片存儲在模型對象中
    //解決,圖片重複下載,把圖片緩存到內存中,節省用戶的流量 (拿空間換取執行時間)
//6 解決圖片下載速度特別慢,出現的錯行問題。
    //當圖片下載完成以後,從新加載對應的cell
//7 當收到內存警告,要清理內存,若是圖片存儲在模型對象中,很差清理內存
    //圖片的緩存池
//8 當有些圖片下載速度比較慢,上下不停滾動的時候,會重複下載圖片,會浪費流量
    //判斷當前是否有對應圖片的下載操做,若是沒有添加下載操做,若是有不要重複建立操做
    //下載操做的緩存池
@implementation ViewController
//懶加載
- (NSArray *)appInfos {
    if (_appInfos == nil) {
        _appInfos = [HMAppInfo appInfos];
    }
    return _appInfos;
}
- (NSOperationQueue *)queue {
    if (_queue == nil) {
        _queue = [NSOperationQueue new];
    }
    return _queue;
}
- (NSMutableDictionary *)imageCache {
    if (_imageCache == nil) {
        _imageCache = [NSMutableDictionary dictionaryWithCapacity:10];
    }
    return _imageCache;
}
- (NSMutableDictionary *)downloadCache {
    if (_downloadCache == nil) {
        _downloadCache = [NSMutableDictionary dictionaryWithCapacity:10];
    }
    return _downloadCache;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //1 測試模型數據
//    NSLog(@"%@",self.appInfos);
}
//2 數據源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.appInfos.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    //1 建立可重用的cell
    static NSString *reuseId = @"appInfo";
    HMAppInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId];
    if (cell == nil) {
        cell = [[HMAppInfoCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseId];
    }
    //2 獲取數據,給cell內部子控件賦值
    HMAppInfo *appInfo = self.appInfos[indexPath.row];
    
    //cell內部的子控件都是懶加載的
    //當返回cell以前,會調用cell的layoutSubviews方法
    cell.textLabel.text = appInfo.name;
    cell.detailTextLabel.text = appInfo.download;    
    //判斷有沒有圖片緩存
    if (self.imageCache[appInfo.icon]) {
        NSLog(@"從緩存加載圖片...");
        cell.imageView.image = self.imageCache[appInfo.icon];
        return cell;
    }    
    //設置佔位圖片
    cell.imageView.image = [UIImage imageNamed:@"user_default"];    
    //判斷下載操做緩存池 中是否有對應的操做
    if (self.downloadCache[appInfo.icon]) {
        NSLog(@"正在拼命下載圖片...");
        return cell;
    }
    //異步下載圖片
    //模擬網速比較慢    
    //下載操做
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        //        [NSThread sleepForTimeInterval:0.5];
        //模擬圖片下載速度慢
        if (indexPath.row > 9) {
            [NSThread sleepForTimeInterval:10];
        }        
        //下載圖片
        NSLog(@"下載網絡圖片...");        
        NSURL *url = [NSURL URLWithString:appInfo.icon];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *img = [UIImage imageWithData:data];        
        //緩存圖片
        self.imageCache[appInfo.icon] = img;        
        //主線程上更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            //            cell.imageView.image = img;
            //解決圖片顯示錯行
            [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
        }];
    }];    
    //把操做添加到隊列中
    [self.queue addOperation:op];
    //把操做添加到下載操做緩存池中
    self.downloadCache[appInfo.icon] = op;    
    //3 返回cell
    return cell;
}
//接收到內存警告
- (void)didReceiveMemoryWarning {
    //清理內存
    [self.imageCache removeAllObjects];
    //
    [self.downloadCache removeAllObjects];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    //點擊cell的時候,輸出當前隊列的操做數
    NSLog(@"隊列的操做數:%zd",self.queue.operationCount);
    
}
@end
@interface ViewController ()
@property (nonatomic, strong) NSArray *appInfos;
//全局隊列
@property (nonatomic, strong) NSOperationQueue *queue;
//圖片的緩存池
@property (nonatomic, strong) NSMutableDictionary *imageCache;
//下載操做緩存池
@property (nonatomic, strong) NSMutableDictionary *downloadCache;
@end
//1 建立模型類,獲取數據,測試
//2 數據源方法
//3 同步下載圖片--若是網速比較慢,界面會卡頓
//4 異步下載圖片--圖片顯示不出來,點擊cell或者上下拖動圖片能夠顯示
    //解決,使用佔位圖片
//5 圖片緩存--把網絡上下載的圖片,保存到內存--圖片存儲在模型對象中
    //解決,圖片重複下載,把圖片緩存到內存中,節省用戶的流量 (拿空間換取執行時間)
//6 解決圖片下載速度特別慢,出現的錯行問題。
    //當圖片下載完成以後,從新加載對應的cell
//7 當收到內存警告,要清理內存,若是圖片存儲在模型對象中,很差清理內存
    //圖片的緩存池
//8 當有些圖片下載速度比較慢,上下不停滾動的時候,會重複下載圖片,會浪費流量
    //判斷當前是否有對應圖片的下載操做,若是沒有添加下載操做,若是有不要重複建立操做
    //下載操做的緩存池
@implementation ViewController
//懶加載
- (NSArray *)appInfos {
    if (_appInfos == nil) {
        _appInfos = [HMAppInfo appInfos];
    }
    return _appInfos;
}
- (NSOperationQueue *)queue {
    if (_queue == nil) {
        _queue = [NSOperationQueue new];
    }
    return _queue;
}
- (NSMutableDictionary *)imageCache {
    if (_imageCache == nil) {
        _imageCache = [NSMutableDictionary dictionaryWithCapacity:10];
    }
    return _imageCache;
}
- (NSMutableDictionary *)downloadCache {
    if (_downloadCache == nil) {
        _downloadCache = [NSMutableDictionary dictionaryWithCapacity:10];
    }
    return _downloadCache;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    //1 測試模型數據
//    NSLog(@"%@",self.appInfos);
}
//2 數據源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.appInfos.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    //1 建立可重用的cell
    static NSString *reuseId = @"appInfo";
    HMAppInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId];
    if (cell == nil) {
        cell = [[HMAppInfoCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseId];
    }
    //2 獲取數據,給cell內部子控件賦值
    HMAppInfo *appInfo = self.appInfos[indexPath.row];    
    //cell內部的子控件都是懶加載的
    //當返回cell以前,會調用cell的layoutSubviews方法
    cell.textLabel.text = appInfo.name;
    cell.detailTextLabel.text = appInfo.download;    
    //判斷有沒有圖片緩存
    if (self.imageCache[appInfo.icon]) {
        NSLog(@"從緩存加載圖片...");
        cell.imageView.image = self.imageCache[appInfo.icon];
        return cell;
    }    
    //設置佔位圖片
    cell.imageView.image = [UIImage imageNamed:@"user_default"];    
    //判斷下載操做緩存池 中是否有對應的操做
    if (self.downloadCache[appInfo.icon]) {
        NSLog(@"正在拼命下載圖片...");
        return cell;
    }
    //異步下載圖片
    //模擬網速比較慢    
    //下載操做
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        //        [NSThread sleepForTimeInterval:0.5];
        //模擬圖片下載速度慢
        if (indexPath.row > 9) {
            [NSThread sleepForTimeInterval:10];
        }        
        //下載圖片
        NSLog(@"下載網絡圖片...");        
        NSURL *url = [NSURL URLWithString:appInfo.icon];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *img = [UIImage imageWithData:data];        
        //緩存圖片
        self.imageCache[appInfo.icon] = img;        
        //主線程上更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            //            cell.imageView.image = img;
            //解決圖片顯示錯行
            [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
        }];
    }];    
    //把操做添加到隊列中
    [self.queue addOperation:op];
    //把操做添加到下載操做緩存池中
    self.downloadCache[appInfo.icon] = op;    
    //3 返回cell
    return cell;
}
//接收到內存警告
- (void)didReceiveMemoryWarning {
    //清理內存
    [self.imageCache removeAllObjects];
    [self.downloadCache removeAllObjects];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    //點擊cell的時候,輸出當前隊列的操做數
    NSLog(@"隊列的操做數:%zd",self.queue.operationCount);
}
@end

注意

  • Block的循環引用
  • 代碼重構
    • 把下載圖片的代碼提取成方法
相關文章
相關標籤/搜索