多線程-異步下載圖片Demo

注意點及細節(jié)處理:
1. 耗時操作放在子線程:下載操作如果耗時較長,在主線程執(zhí)行就會卡死主界面
2. 設置占位圖:由于異步執(zhí)行下載圖片,在設置圖片前Cell的數(shù)據(jù)源方法已經(jīng)執(zhí)行完畢,Cell的ImageView的frame為零,就會導致圖片框不顯示,滾動和點擊Cell后顯示圖片

未設置占位圖.gif

3. 圖片緩存池:避免移動網(wǎng)絡下重復下載圖片,對已經(jīng)下載的圖片進行緩存處理,當刷新Cell時,從內(nèi)存獲取圖片,執(zhí)行效率更高,并且節(jié)省流量; 放在模型中的缺點是當內(nèi)存緊張時,不方便清理緩存圖片
4. 操作緩存池: 防止同一張圖片多次下載
5. Cell圖片混亂: 當異步操作耗時足夠長,快速滾動Cell時,會從緩存池中獲取Cell,這時的Cell中可能有未執(zhí)行完的任務而導致圖片換亂,解決辦法就是在主線以無動畫的方式程刷新TableView,而不是根據(jù)下載好的圖片進行賦值(在刷新UI前,圖片肯定已經(jīng)下載完成并進行了緩存,刷新后會從緩存中提取圖片)

Cell圖片錯亂.gif

6. 沙盒本地緩存圖片
7. 使用block時注意循環(huán)引用問題

關鍵代碼:

#import "JSAppsTableController.h"
#import "JSAppsModel.h"
#import "JSAppCell.h"

static NSString * const reuseIdentifier = @"reuseIdentifier";

@implementation JSAppsTableController{
    
    NSArray <JSAppsModel *>      *_data;             //  數(shù)據(jù)容器
    NSOperationQueue             *_queue;            //  全局隊列
    NSMutableDictionary          *_operationCache;   //  操作緩存池
    NSMutableDictionary          *_imageCache;       //  圖片緩存池
    NSString                     *_cachePath;        //  沙盒路徑
    
}

- (instancetype)initWithFileName:(NSString *)fileName{
    self = [super init];
    if (self) {
        
        // 成員變量初始化
        _data = [JSAppsModel loadAppsDataWithFileName:fileName];
        _queue = [[NSOperationQueue alloc] init];
        _queue.maxConcurrentOperationCount = 5;
        _operationCache = [NSMutableDictionary dictionaryWithCapacity:5];
        _imageCache = [NSMutableDictionary dictionaryWithCapacity:5];
        _cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject;
        
    }
    return self;
}

- (void)viewDidLoad{
    [super viewDidLoad];
    [self.tableView registerClass:[JSAppCell class] forCellReuseIdentifier:reuseIdentifier];
    
}

#pragma mark -- UITableViewDataSource

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    JSAppCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier forIndexPath:indexPath];
    
    JSAppsModel *model = _data[indexPath.row];
    cell.textLabel.text = model.name;
    cell.detailTextLabel.text = model.download;
    
    // 設置占位圖
    cell.imageView.image = [UIImage imageNamed:@"user_default"];
    
    // 從緩存中取出圖片
    if ([_imageCache objectForKey:model.icon]) {
        cell.imageView.image = [_imageCache objectForKey:model.icon];
        NSLog(@"從內(nèi)存圖片緩存池中獲取圖片");
        return cell;
    }
    
    // 從沙盒中獲取圖片
    NSData *data = [NSData dataWithContentsOfFile:[_cachePath stringByAppendingPathComponent:model.icon.lastPathComponent]];
    if (data) {
        cell.imageView.image = [UIImage imageWithData:data];
        
        // 進行內(nèi)存緩存
        [_imageCache setObject:[UIImage imageWithData:data] forKey:model.icon];
        
        NSLog(@"從本地沙盒獲取圖片:(%@)",[_cachePath stringByAppendingPathComponent:model.icon.lastPathComponent]);
        return cell;
    }
    
    // 判斷操作是否存在
    if ([_operationCache objectForKey:model.icon]) {
        NSLog(@"圖片正在下載中...");
        return cell;
    }
    
    /*    block內(nèi)部使用self,不能夠說一定存在循環(huán)引用
         self -> _operationCache和_queue -> downLoadImageOperation -> block -> self
            _operationCache     ->    手動清除downLoadImageOperation
            _queue              ->    當隊列中的操作執(zhí)行完畢后downLoadImageOperation會自動銷毀
     */
    //__weak typeof(self) weakSelf = self;
    
    
    // 異步下載圖片
    NSBlockOperation *downLoadImageOperation = [NSBlockOperation blockOperationWithBlock:^{
        
        [NSThread sleepForTimeInterval:5]; // 模擬延遲
        
        // 下載圖片
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:model.icon]];
        UIImage *image = [UIImage imageWithData:data];
        
        // 將下載好的圖片進行內(nèi)存緩存
        // model.downloadImage = image;
        [_imageCache setObject:image forKey:model.icon];
        
        // 將下載好的圖片做本地緩存
        [data writeToFile:[_cachePath stringByAppendingPathComponent:model.icon.lastPathComponent] atomically:YES];
        
        // 返回主線程刷新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            
            // cell.imageView.image = image; 避免重用Cell中有正在執(zhí)行的下載操作導致圖片混亂,直接刷新TableView從內(nèi)存獲取圖片
            [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
            
            // 清除操作緩存池中對應的操作
            [_operationCache removeObjectForKey:model.icon];
            
            /*      [_operationCache removeAllObjects];
             假設操作的完成時間足夠長,因為下載操作異步執(zhí)行,CPU會隨機執(zhí)行線程上的操作,如果設置了優(yōu)先級或執(zhí)行某一線程的概率較高,那么可以肯定,完成有先后,只是不夠明顯
             一旦某個操作提前完成執(zhí)行了清空操作緩存池,當再次滾動TableView的時候,可能還會出現(xiàn)同一個下載操作重復添加到隊列中的問題
             所以不應該使用removeAllObjects方法來移除
             */
            
        }];
    }];
    
    // 將操作添加到隊列中: 隊列當中的操作執(zhí)行完畢之后,會自動從隊列中銷毀
    [_queue addOperation:downLoadImageOperation];
    
    // 將每一個下載圖片的操作都添加到操作緩存池中(如果操作已經(jīng)存在,就不再重復執(zhí)行)
    [_operationCache setObject:downLoadImageOperation forKey:model.icon];
    
    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    return 68;
}

#pragma mark -- UITableViewDataDelegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    
    NSLog(@"----->ImageCacheCounts:%zd ----> OperationCacheCounts:%zd ---->CurrentOperationCounts:%zd",_imageCache.count,_operationCache.count,_queue.operationCount);
    
}

- (void)dealloc{
    NSLog(@"%s",__func__);
}

- (void)didReceiveMemoryWarning{
//    [super didReceiveMemoryWarning];
    [_imageCache removeAllObjects];
    
}


@end

源代碼:https://github.com/ShenYj/AsyncDownLoadImageDemo

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末彤叉,一起剝皮案震驚了整個濱河市康栈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌馆截,老刑警劉巖蟹地,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件积暖,死亡現(xiàn)場離奇詭異,居然都是意外死亡怪与,警方通過查閱死者的電腦和手機夺刑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來分别,“玉大人遍愿,你說我怎么就攤上這事≡耪叮” “怎么了沼填?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長括授。 經(jīng)常有香客問我坞笙,道長岩饼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任薛夜,我火速辦了婚禮籍茧,結果婚禮上,老公的妹妹穿的比我還像新娘梯澜。我一直安慰自己寞冯,他們只是感情好,可當我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布晚伙。 她就那樣靜靜地躺著吮龄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪咆疗。 梳的紋絲不亂的頭發(fā)上漓帚,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天,我揣著相機與錄音午磁,去河邊找鬼胰默。 笑死,一個胖子當著我的面吹牛漓踢,可吹牛的內(nèi)容都是我干的牵署。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼喧半,長吁一口氣:“原來是場噩夢啊……” “哼奴迅!你這毒婦竟也來了?” 一聲冷哼從身側響起挺据,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤取具,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后扁耐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體暇检,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年婉称,在試婚紗的時候發(fā)現(xiàn)自己被綠了块仆。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡王暗,死狀恐怖悔据,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情俗壹,我是刑警寧澤科汗,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站绷雏,受9級特大地震影響头滔,放射性物質(zhì)發(fā)生泄漏怖亭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一坤检、第九天 我趴在偏房一處隱蔽的房頂上張望依许。 院中可真熱鬧,春花似錦缀蹄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至悬襟,卻和暖如春衅码,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背脊岳。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工逝段, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人割捅。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓奶躯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親亿驾。 傳聞我的和親對象是個殘疾皇子嘹黔,可洞房花燭夜當晚...
    茶點故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,094評論 25 707
  • 發(fā)現(xiàn) 關注 消息 iOS 第三方庫、插件莫瞬、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,098評論 4 62
  • .一.進程 進程:是指在系統(tǒng)中正在運行的一個應用程序,每個進程之間是獨立的儡蔓,每個進程均運行在其專用且受保護的內(nèi)存空...
    IIronMan閱讀 4,486評論 1 33
  • ——桃李春風一杯酒,江湖夜雨十年燈 “淺珊疼邀,不...
    念華L閱讀 351評論 0 2
  • 相信大多數(shù)人都不愿意承認自己是敏感的喂江,更不愿意承認自己是自卑的,我也是旁振。很多雞湯文里都寫到你要學會接納你自己获询,我們...
    扒小怪閱讀 953評論 0 0