UIImageView 序列幀動畫的實現以及記憶體的優化

Sweetこ尛晴天づ發表於2018-08-20

最近筆者專案中又一次遇到了UIImageView的序列幀動畫,各種百度,為了不讓下一次遇到序列幀動畫的時候還需要百度,也為了讓後來查資料的人有一個系統的理解,我準備將這些百度來的資料以及自己的理解寫成一個demo供大家參考學習;

UIImageView動圖三種實現方式:

實現方式一: SDWebImage實現

接到需求最初的想法就是用第三方來實現播放動畫gif動畫了;用SDWebImage來實現gif的播放程式碼如下:

- (void)achieveGifWithSDWebImage {
    NSString  *filePath = [[NSBundle bundleWithPath:[[NSBundle mainBundle] bundlePath]] pathForResource:@"icon_image.gif" ofType:nil];
    NSData  *imageData = [NSData dataWithContentsOfFile:filePath];
    self.imageView.image = [UIImage sd_animatedGIFWithData:imageData];
}
複製程式碼

記憶體佔用情況

沒有載入動畫之前:

UIImageView 序列幀動畫的實現以及記憶體的優化

載入動畫中:

UIImageView 序列幀動畫的實現以及記憶體的優化

退出動畫之後:

UIImageView 序列幀動畫的實現以及記憶體的優化

這種實現方式省心省力,只需要自己手動載入一下資源就好,記憶體不用自己手動控制,就可以釋放;

實現方式二:UIImageView實現

其實UIImageView有一套自己播放gif圖的方式的,不過其播放的不是gif格式的動圖,而是一幀幀的進行播放的:

- (void)createAnimation {
    // 1.將序列圖加入陣列
    NSMutableArray *imagesArray = [[NSMutableArray alloc] init];
    for (NSInteger i = 1;i <= 26;i++)
    {
//        UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"icon_image_00%02ld.png",i]];
//        [imagesArray addObject:image];
        // 計算檔名
        NSString *filename = [NSString stringWithFormat:@"icon_image_00%02ld.png",i];
        NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:nil];
        UIImage *image = [UIImage imageWithContentsOfFile:path];
        [imagesArray addObject:image];
    }
    // 設定序列圖陣列
    self.imageView.animationImages = imagesArray;
    // 設定播放週期時間
    self.imageView.animationDuration = 0.1*imagesArray.count;
    // 設定播放次數
    self.imageView.animationRepeatCount = 0;
    // 播放動畫
    [self.imageView startAnimating];
}
複製程式碼

注意: 這裡載入圖片資源沒有用到UIImage中的imageNamed:方法,而是用了imageWithContentsOfFile方法,有什麼樣的區別呢?本文末尾有關於此問題的解答。

記憶體佔用情況

此種方式載入動畫的記憶體佔用情況與sd載入gif效果一樣,只是需要手動做一步釋放,否則佔用記憶體不能及時釋放:

    // 停止動畫
    [self.imageView stopAnimating];
    // 將圖片資源進行釋放
    self.imageView.animationImages = nil;
    // 載入到記憶體中的資源要進行釋放  最好用_imagesArray方式,self.imagesArray方式有的時候釋放不掉;
    _imagesArray = nil;
複製程式碼

這兩個種實現方式有一個共同的特點,就是動畫結束後沒有一個明確的回撥,如果是一個動畫結束之後另外一個動畫緊接著開始了,只靠定時器來完成總是會出現這種或者那種問題,要麼是動畫錯位,要麼是因為網路問題播放不流暢導致出各種不可預知的問題,這個時候,有一個能準確檢測到動畫結束的回撥對我們來說是至關重要的,那麼下面我們來看看如何進行實現?應該以何種方式進行實現呢?

能準確監視動畫結束事件的實現方式----UIImageview的關鍵幀動畫CAKeyframeAnimation

 //存放圖片的陣列
    NSMutableArray *array = [NSMutableArray array];
    for(NSUInteger i = 1;i < 26 ;i++) {
//        UIImage *img = [UIImage imageNamed:[NSString stringWithFormat:@"icon_image_00%02ld",i]];
        // 計算檔名
        NSString *filename = [NSString stringWithFormat:@"icon_image_00%02ld.png",i];
        NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:nil];
        UIImage *image = [UIImage imageWithContentsOfFile:path];
        CGImageRef cgimg = image.CGImage;
        [array addObject:(__bridge UIImage *)cgimg];
    }
    _imagesArray = array;
    // 建立CAKeyframeAnimation
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
    // 動畫結束之後的回撥
    animation.delegate = self;
    animation.duration = array.count * 0.1;
    animation.repeatCount = 2;
    // 設定animation的唯一標示,這樣在delegate回撥的時候能夠區分開來
    [animation setValue:@"animation1" forKey:@"customType"];
    animation.values = array;
    [self.imageView.layer addAnimation:animation forKey:@""];
複製程式碼

在動畫結束的時候想釋放資源記憶體,只需要這樣就可以了:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    // 動畫結束回撥
    NSString *keyPathValue = [anim valueForKey:@"customType"];
    if ([keyPathValue isEqualToString:@"animation1"]) {
        NSLog(@"這個動畫結束了");
        _imagesArray = nil;
    }
}
複製程式碼

兩種圖片載入方式的區別:

+(UIImage *)imageNamed:(NSString *)name
+(UIImage *)imageWithContentsOfFile:(NSString *)name

查閱官方文件可以總結如下:

imageNamed:在載入圖片時會根據名字在主目錄中查詢,首先會在記憶體快取中查詢,如果沒有再從磁碟快取中獲取,之後系統會快取該圖片到記憶體中。 imageWithContentsOfFile:僅載入圖片,影像資料不會快取。

因此我們可以得到這樣的結論

  1. 當我們頻繁的使用一個圖片的時候(例如:在一個tableview的cell中會載入一個圖示,那麼用imageNamed:方法效率會很高)使用imageNamed方法;
  2. 然而iOS的記憶體非常珍貴並且在記憶體消耗過大時,會強制釋放記憶體,即會遇到memory warnings。而在iOS系統裡面釋放影像的記憶體是一件比較麻煩的事情,有可能會造成記憶體洩漏。例如:當一個UIView物件的animationImages是一個裝有UIImage物件動態陣列NSMutableArray,並進行逐幀動畫。當使用imageNamed的方式載入影像到一個動態陣列NSMutableArray,這將會很有可能造成記憶體洩露。原因很顯然的。
  3. 載入比較大的圖片的時候切記使用imageNamed:,應該使用imageWithContentsOfFile:;

相關文章