iOS-SDWebimage底層實現原理


其實有些框架的實現原理,並沒有想象中那么難,思想也很簡單,主要是更新第三方框架的作者對自己寫的代碼,進行了多層封裝,使代碼的可讀性降低,也就使得框架看起來比較難.我來實現以下SDWebimage的的曾實現.

實現過程中可能遇到的問題:

1.UI卡頓: 當界面中需要下載多張圖片的時候,由於圖片下載是耗時操作,會短暫的占據着主線程的執行,也就會是UI界面看起來卡頓.

  解決辦法: 需要把耗時操作放在子線程中執行(若是對多線程不了解,我之前的博客寫過關於多線程的知識)

 NSBlockOperation *download = [self.operations objectForKey:app.icon];
            
            if (download) {
                
                NSLog(@"該圖片正在下載,請稍等");
            }else
            {
                //封裝下載圖片的操作
                download = [NSBlockOperation blockOperationWithBlock:^{
                    
                    NSURL *url = [NSURL URLWithString:app.icon];
                    
                    //耗時操作,模擬網速慢的情況
                    for (NSInteger i =0; i <1000000000; i++) {
                        
                    }
                    
                    NSData *data = [NSData dataWithContentsOfURL:url];
                    UIImage *image = [UIImage imageWithData:data];
                    
                    NSLog(@"下載第%zd行cell對應的圖片",indexPath.row);
                    
                    //容錯處理
                    if (image == nil) {
                        [self.operations removeObjectForKey:app.icon];
                        return ;
                    }
                    
                    //保存圖片到內存緩存中
                    [self.images setObject:image forKey:app.icon];
                    
                    
                    //保存數據到沙盒(磁盤緩存)
                    [data writeToFile:fullPath atomically:YES];
                    
                    //線程間通信 主線程中設置圖片
                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                        //cell.imageView.image = image;
                        //刷新
                        //[tableView reloadData];
                        [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
                    }];
                }];
                
                //把操作保存到操作緩存中
                [self.operations setObject:download forKey:app.icon];
                
                //把操作添加到隊列
                [self.queue addOperation:download];
            }

2.重復加載問題(一)

在下載圖片的地方,打印相關的下載的圖片的信息,你會發現圖片會重復下載

解決辦法: 1.內存緩存處理  2.二級緩存處理

內存的大小畢竟有限的,在開發中我們一般采用二級緩存處理.

二級緩存處理過程: 
       1.在顯示圖片之前,先檢查內存緩存中時候有該圖片
       2.如果內存緩存中有圖片,那么就直接使用,不下載
       3.如果內存緩存中無圖片,那么再檢查是否有磁盤緩存
       4.如果磁盤緩存中有圖片,那么直接使用,還需要保存一份到內存緩存中(方便下一次使用)
       5.如果磁盤緩存中無圖片,那么再去下載,並且把下載完的圖片保存到內存緩存和磁盤緩存
 //保存圖片到內存緩存中
  [self.images setObject:image forKey:app.icon];
                    
                    
  //保存數據到沙盒(磁盤緩存)
   [data writeToFile:fullPath atomically:YES];

3.圖片不顯示

原因: 圖片的顯示操作是異步執行的,也就是說需要重新刷新該行的cell

 //線程間通信 主線程中設置圖片
                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                        //cell.imageView.image = image;
                        //刷新
                        //[tableView reloadData];
                        [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
                    }];

4.圖片重復下載問題(二)

這種情況需要模擬一定的情況,例如網絡信號不好(網絡信號不好可以用耗時操作代替),造成這種情況的原因,就是在網絡情況不好的情況下,用戶重復滑動.就會重復的發送下載圖片的請求,最終造成重復下載.

解決辦法: 我在這里用的操作隊列,對應的解決辦法,就是把對應的事件以字典的方式存儲到內存,放置重復發送下載圖片的請求

 //把操作保存到操作緩存中
                [self.operations setObject:download forKey:app.icon];

5.圖片顯示錯亂的問題

由於cell的重用導致,用戶下拉或者上拉,當網絡不好的情況,該cell的圖片還沒有被加載,但是對應的cell已經被顯示,就會顯示cell被重用之前的數據,造成數據混亂

解決辦法: 設置每個cell中image為nil或者設置默認圖片.

6.中間還要加一些容錯處理.

例如: 若是服務器返回的url是錯誤的,就會造成程序閃退,需要做處理

 NSData *data = [NSData dataWithContentsOfURL:url];
                    UIImage *image = [UIImage imageWithData:data];
                    
                    NSLog(@"下載第%zd行cell對應的圖片",indexPath.row);
                    
                    //容錯處理
                    if (image == nil) {
                        [self.operations removeObjectForKey:app.icon];
                        return ;
                    }

7.內存緩存的處理

-(void)didReceiveMemoryWarning
{
    //清空內存緩存
    [self.images removeAllObjects];
    
    //取消隊列中所有的操作
    [self.queue cancelAllOperations];
}

完整的代碼如下:

模型中的代碼:

#import <Foundation/Foundation.h>

@interface BOAppModel : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *icon;
@property (nonatomic, strong) NSString *download;

- (instancetype)initWithDict:(NSDictionary *)dict;
+ (instancetype)appModelWithDict:(NSDictionary *)dict;
@end
#import "BOAppModel.h"

@implementation BOAppModel
- (instancetype)initWithDict:(NSDictionary *)dict{
    if (self = [super init]) {
        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
}
+ (instancetype)appModelWithDict:(NSDictionary *)dict{
    
    return [[BOAppModel alloc] initWithDict:dict];
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
    
}
@end

控制器空代碼如下:

#import "ViewController.h"
#import "BOAppModel.h"

@interface ViewController ()
@property (nonatomic, strong) NSArray *apps;
@property (nonatomic, strong) NSCache *images;
@property (nonatomic, strong) NSMutableDictionary *operations;
@property (nonatomic, strong) NSOperationQueue *queue;
@end

@implementation ViewController

#pragma mark ------------------
#pragma mark lazy loading

-(NSOperationQueue *)queue
{
    if (_queue == nil) {
        _queue = [[NSOperationQueue alloc]init];
    }
    return _queue;
}

-(NSCache *)images
{
    if (_images == nil) {
        _images = [[NSCache alloc] init];
    }
    return _images;
}
-(NSMutableDictionary *)operations
{
    if (_operations == nil) {
        _operations = [NSMutableDictionary dictionary];
    }
    return _operations;
}

-(NSArray *)apps
{
    if (_apps == nil) {
        
        //加載plist文件中的數據
        NSArray *arrayM = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]];
        
        //字典轉模型(字典數組-->模型數組)
        NSMutableArray *arr = [NSMutableArray array];
        for (NSDictionary *dict in arrayM) {
            [arr addObject: [BOAppModel appModelWithDict:dict]];
        }
        
        _apps = arr;
        
    }
    return _apps;
}

#pragma mark ------------------
#pragma mark UItableViewDataSource

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

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

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    //01 創建cell
    static NSString *ID = @"app";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    
    //02 設置cell
    //001 拿到該行cell對應的數據
    XMGApp *app = self.apps[indexPath.row];
    //002 設置標題
    cell.textLabel.text = app.name;
    //003 設置子標題
    cell.detailTextLabel.text = [NSString stringWithFormat:@"%@",app.download];
    //004 設置圖片
    
    /*
     內存緩存處理:
     [1]在顯示圖片之前,先檢查緩存中是否有該圖片(是否已經下載過)
     [2]如果緩存中有圖片,那么就直接使用,不下載
     [3]如果緩存中無圖片,那么再去下載,並且把下載完的圖片保存到內存緩存
     */
    /*
     二級(內存-磁盤)緩存處理:
     [1]在顯示圖片之前,先檢查內存緩存中是否有該圖片
     [2]如果內存緩存中有圖片,那么就直接使用,不下載
     [3]如果內存緩存中無圖片,那么再檢查是否有磁盤緩存
     [4]如果磁盤緩存中有圖片,那么直接使用,還需要保存一份到內存緩存中(方便下一次)
     [5]如果磁盤緩存中無圖片,那么再去下載,並且把下載完的圖片保存到內存緩存和磁盤緩存
     */
    
    //檢查內存緩存
    UIImage *image = [self.images objectForKey:app.icon];
    
    if (image)
    {
        cell.imageView.image = image;
         NSLog(@"第%zd行cell對應的圖片從內存緩存中獲取",indexPath.row);
    }else
    {
        //文件名稱
        NSString *fileName = [app.icon lastPathComponent];
        
        //獲得緩存路徑
        NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        
        //拼接文件的全路徑
        NSString *fullPath = [cache stringByAppendingPathComponent:fileName];

        //檢查磁盤緩存
        NSData *data = [NSData dataWithContentsOfFile:fullPath];
        data = nil;
        if (data)
        {
            //如果有磁盤緩存,那么久直接使用
            UIImage *image = [UIImage imageWithData:data];
            cell.imageView.image = image;
            
            //還需要保存一份到內存緩存中
            [self.images setObject:image forKey:app.icon];
            
            NSLog(@"第%zd行cell對應的圖片使用了磁盤緩存--%@",indexPath.row,fullPath);
        }else
        {
            //開子線程下載圖片
            /* 對操作進行緩存處理
             如果沒有內存緩存也沒有磁盤緩存,則
             001 先檢查該圖片的下載操作是否已經存在(該圖片是否正在下載)
             002 如果下載操作已經存在,那么就等待即可
             003 如果下載操作不存在,那么再去下載
             */
            
            //清空cell的圖片
            //cell.imageView.image = nil;
            //設置占位圖片
            cell.imageView.image = [UIImage imageNamed:@"Snip20161203_14"];
            
            NSBlockOperation *download = [self.operations objectForKey:app.icon];
            
            if (download) {
                
                NSLog(@"該圖片正在下載,請稍等");
            }else
            {
                //封裝下載圖片的操作
                download = [NSBlockOperation blockOperationWithBlock:^{
                    
                    NSURL *url = [NSURL URLWithString:app.icon];
                    
                    //耗時操作,模擬網速慢的情況
                    for (NSInteger i =0; i <1000000000; i++) {
                        
                    }
                    
                    NSData *data = [NSData dataWithContentsOfURL:url];
                    UIImage *image = [UIImage imageWithData:data];
                    
                    NSLog(@"下載第%zd行cell對應的圖片",indexPath.row);
                    
                    //容錯處理
                    if (image == nil) {
                        [self.operations removeObjectForKey:app.icon];
                        return ;
                    }
                    
                    //保存圖片到內存緩存中
                    [self.images setObject:image forKey:app.icon];
                    
                    
                    //保存數據到沙盒(磁盤緩存)
                    [data writeToFile:fullPath atomically:YES];
                    
                    //線程間通信 主線程中設置圖片
                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                        //cell.imageView.image = image;
                        //刷新
                        //[tableView reloadData];
                        [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
                    }];
                }];
                
                //把操作保存到操作緩存中
                [self.operations setObject:download forKey:app.icon];
                
                //把操作添加到隊列
                [self.queue addOperation:download];
            }
        }
    }
    
    //03 返回cell
    return cell;
}

-(void)didReceiveMemoryWarning
{
    //清空內存緩存
    [self.images removeAllObjects];
    
    //取消隊列中所有的操作
    [self.queue cancelAllOperations];
}

/*
 001 UI卡頓 ---> 開子線程下載圖片
 002 重復下載 --->內存緩存 -->磁盤緩存
 003 圖片不顯示 --->圖片的顯示操作是異步執行的
 004 新的重復下載
 005 圖片顯示錯亂的問題-->cell的重用
 006 自己尋找
 */
/*
 沙盒文件:
    Documents 官方規定不能緩存處理
    Library
        cache 緩存文件
        偏好設置
    Tmp 臨時文件存儲路徑(隨時可能被刪除)
 */
@end

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM