其實有些框架的實現原理,並沒有想象中那么難,思想也很簡單,主要是更新第三方框架的作者對自己寫的代碼,進行了多層封裝,使代碼的可讀性降低,也就使得框架看起來比較難.我來實現以下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.二級緩存處理
內存的大小畢竟有限的,在開發中我們一般采用二級緩存處理.
//保存圖片到內存緩存中 [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