1. 程式人生 > >UITableView滑動卡頓解決方案

UITableView滑動卡頓解決方案

      UITableView是一個非常常用的基本檢視,在各類app中隨處可見。對於一般佈局簡單的tableView,效能上基本上看不出來什麼問題。但是對於cell中檢視繁多的tableView,有時候可能就會出現滑動不流暢的現象,以下是本人的一些解決方案,僅供參考。

     1."讓出"主執行緒,讓主執行緒減負。所謂"讓出"主執行緒,指的是不要什麼操作都放在主執行緒裡。放在主執行緒中的一般都是檢視相關的操作,比如新增子檢視、更新子檢視、刪除子檢視等。

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //處理一些跟當前檢視沒關係的事情
        //...
        
        //只用來操作與當前檢視有關係的事情,比如:重新整理tableView
        dispatch_async(dispatch_get_main_queue(), ^{
            [tableView reload];
        });
    });

     2.正確重用cell。正確重用cell不僅僅要重用cell檢視,還需要好好重用cell的子檢視。

    static NSString *Identifier = @"WeatherCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Identifier];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:<#(UITableViewCellStyle)#> 
                                      reuseIdentifier:<#(NSString *)#>] 
    }
上面的程式碼在有cell可重用的時候,不會再建立新的cell,但是下面的一句話基本上會讓重用粉身碎骨。
    //清空子檢視
    [cell.contentView.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
         [obj removeFromSuperview];
    }];
上面這段程式碼之所以會出現,原因眾所周知:cell重用時會出現重疊。"解決"了重疊問題,那麼新問題來了,重新建立檢視既消耗記憶體還佔用時間,嚴重會出現滑動出現卡頓現象,而且都刪除了重建還能叫重用麼?


舉例來說:


     對於上面這樣的cell,雖然趕不上微博的複雜程度,但是子檢視數量也是挺多的,不合理的處理,會使得頁面滑動時有明顯示卡頓感。實現方案如下:

     1.圖片實現非同步下載、非主執行緒。

    //門店商品圖片描述
    UIImageView *imageView = (UIImageView *)[cell.contentView viewWithTag:Merchant_Table_Cell_Desc_Image_View_Tag];
    imageView.alpha = 1;
    [imageView setImageWithDict:[NSDictionary dictionaryWithObject:merchant.imageName?merchant.imageName:@"" forKey:@"file_name"]];
此方法是UIImageView category中的一個方法,該方法的核心部分如下:
    //下載時讓出主執行緒
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        //指示器
        UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
        indicator.frame = CGRectMake((self.frame.size.width - 30)/2 , (self.frame.size.height - 30)/2, 30, 30);
        [indicator startAnimating];
        [self addSubview:indicator];
        
        //待載入圖片的名稱(我的車庫圖片為:cars/A3.jpg格式)
        NSString *fileName = [dict valueForKey:@"file_name"];
        //快取檔案全路徑
        NSString *filePath = [[datas objectForKey:@"documentPath"] stringByAppendingPathComponent:[fileName lastPathComponent]];
        
        NSFileManager *manager = [NSFileManager defaultManager];
        //下載檔案介面不會建立上級目錄,在呼叫之前建立資料夾路徑
        if (![manager fileExistsAtPath:filePath  isDirectory:NULL]) {
            [manager createDirectoryAtPath:[datas objectForKey:@"documentPath"] withIntermediateDirectories:YES attributes:nil error:nil];
        }
        
        NSMutableDictionary *param = [[NSMutableDictionary alloc] init];
        //下載的圖片名
        [param setValue:[dict valueForKey:@"file_name"] forKey:@"file_name"];
        //下載圖片儲存的位置
        [param setValue:filePath forKey:@"filePath"];
        //從網路獲取圖片
        [VICBaseService downloadFileWithDictionary:param andSuccessBlock:^{
            //操作檢視時,移除指示器、更新圖片,拿到主執行緒來做
            dispatch_async(dispatch_get_main_queue(), ^{
                [indicator removeFromSuperview];
                //從本地快取中獲取
                UIImage *image = [UIImage imageWithContentsOfFile:filePath]
                
                if (!image) {
                    [self setDefaultImage:nil withCustomerViewMode:flag andContentMode:mode];
                }else{
                    self.image = image;
                    [self customerImageViewMode:flag withContentMode:mode];
                }
                
            });
            
        } andErrorBlock:^(NSError *error) {
            //如果本地儲存了不完整的圖片,刪除圖片(不佔用主執行緒)
            if ([manager fileExistsAtPath:filePath  isDirectory:NULL]) {
                [manager removeItemAtPath:filePath error:nil];
            }
<pre name="code" class="objc">            //操作檢視時,移除指示器、更新圖片,拿到主執行緒來做
            dispatch_async(dispatch_get_main_queue(), ^{
                [indicator removeFromSuperview];
                
                [self setDefaultImageWithImage:image withCustomerViewMode:flag andContentMode:mode];
                
            });
            
        } andProgressBlock:^(double progress) {
            
        }];
        
    });
   

    2.重用cell並重用cell中的子檢視。新建一個UITableViewCell的子類,重寫下面的方法。

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    
    //門店商品圖片描述
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 87, 87)];
    //設定子檢視的tag,方便重用時後能獲取
    imageView.tag = Merchant_Table_Cell_Desc_Image_View_Tag;
    [self.contentView addSubview:imageView];

    //根據這個思路完成所有檢視的構建
}
    在上述方法中,只設置基礎屬性,跟資料有關的屬性放在-tableView:cellForRowAtIndexPath:中去做。上圖中的"A"、"證"、"券""碼"。"A"跟商家等級走的,可能是B、C或者是D,所以不用在重寫的方法中實現該屬性,而"證"、"券""碼"可以直接寫死在自定義的cell中。

   之前有說到重用cell時會遇到cell子檢視重疊的問題或者其它問題,問題的根源都是由於重用時子檢視的屬性不會被重置。對於重疊問題,思路如下:建立cell時儘可能的使cell包含所有的子檢視,並且在-tableView:cellForRowAtIndexPath:建立cell之後呼叫如下方法:

    [cell.contentView.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        [obj setAlpha:0];
    }];
不錯,我們不再是把子檢視移除,而是隱藏,我們只顯示我們需要的檢視,當前cell不需要的檢視,我們不用去動它,更不要刪除它,因為其它的cell如果會用到的話,就必須重新建立了,跟我們一開始的錯誤做法有什麼區別呢?
    UIImageView *imageView = (UIImageView *)[cell.contentView viewWithTag:Merchant_Table_Cell_Desc_Image_View_Tag];
    imageView.alpha = 1;//只顯示我們需要的檢視
    [imageView setImageWithDict:[NSDictionary dictionaryWithObject:merchant.imageName?merchant.imageName:@"" forKey:@"file_name"]];
這樣的話,我們重用cell容器,裡面的子檢視便不用重新建立,據測試,大量的建立再刪除子檢視會導致滑動具有明顯示卡頓感,並且有可能導致記憶體溢位。看看你的tableView是否也有這樣情況,時間允許的話,拆掉這個破窗戶吧。