iOS Core Animation - The Performance of a Lifetime

前言

iOS Core Animation 读书笔记(三)The Performance of a Lifetime

笔记

Tuning for Speed

CPU Vs GPU

  1. 将各个layer以及layer的动画渲染到屏幕上由系统来控制,这个控制器称为render server
  2. 绘制的过程如下:
    1. Layout:这个阶段建立好你的view树和layer树,并给layer设定好各种属性(frame,background color等等)。
    2. Display:这个阶段在每个layer上面画他们的backing image。这个阶段可能会调用-drawRect:或者-drawLayer:inContext:方法。
    3. Prepare:这个阶段Core Animation准备好要传递给render server的动画的相关参数。
    4. Commit:这个阶段Core Animation将所有的layer和动画属性一起打包,传递给render server,让它显示。
    5. render server计算每个layer每个属性以及动画过程的中间值,然后设置OpenGL的各个三角形属性等等
    6. 在屏幕上绘制可视化的三角形。
  3. 上述的6歌步骤,我们只能控制前两步。前5步由CPU执行,最后一步由GPU执行。
  4. GPU方面有几个影响绘制的性能的因素:
    1. Too much geometry:GPU其实一次可以handle百万个三角形,不太可能成为性能瓶颈。但是太多的layer,在渲染前,需要都传递给render server,这一点会影响CPU的速度,成为影响性能的一个原因。
    2. Too much overdraw:GPU有这有限的像素填充速率。半透明的layer互相堆叠,那么就得渲染多次在同一个像素点。
    3. Offscreen drawing
    4. Too-large images:GPU的最大的texture size是2048x2048/4096x4096。所画的图片超过这,GPU在显示的时候,就得先预处理一遍,这样会减慢速度。
  5. CPU上的操作主要会影响的是动画的开始,他会延迟动画的开始。体现在下面几个方面:
    1. Layout calculations:如果layer太复杂的话,计算所有的layer的frame等属性会花较长的时间。这个在使用autolayout的view上面体现的更明显。
    2. Lazy view loading:iOS只会在view要显示在屏幕上的时候,才会去加载view。这个对内存以及启动时间都很有帮助。但是当一个view显示时需要大量预先计算时,会对响应有很大的影响。
    3. Core Graphics drawing:也就是自己实现的-drawRect:方法和-drawLayer:inContext:方法会影响性能。
    4. Image decompression:PNG或是JPEG都是压缩过的图片。一张图片在显示在iOS屏幕之前,都要先解压缩到原来的大小。这个过程也会消耗CPU时间。

Efficient Drawing

  1. 尽量不使用-drawRect;方法。
  2. 可以使用CAShapeLayer等来代替-drawRect;方法。
  3. 如果非要使用该方法,在重新绘制的时候,不要重新绘制整个View,要绘制那些需要绘制的区域。使用-setNeedsDisplayInRect:等方法。
  4. 可以尝试异步绘图,如使用CATiledLayer以及drawsAsynchronously等等。

Image IO

  1. Image的本地加载和网络请求都会造成延迟。

  2. 可以尝试异步加载。

  3. PNG相比于JPEG,加载时间较长,但是解压的时间短得多。这时因为JPEG的解压算法相比于基于zip压缩的PNG更加复杂。

  4. UIImage的+imageNamed;方法与其他的方法不同的是,它在加载完图片后就立马开始解压,但是其他的方法会在开始画图时才去解压图片。还有一个方法就是直接将image制定为UIImageView的image属性或是layer的contents属性,但是这样的话只能在主线程中执行。

  5. 来看段代码:

    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
        //dequeue cell
        UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
    
        ...
        //switch to background thread
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
            //load image
            NSInteger index = indexPath.row;
            NSString *imagePath = self.imagePaths[index];
            UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
    
            //redraw image using device context
            UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, YES, 0); 
            [image drawInRect:imageView.bounds];
            image = UIGraphicsGetImageFromCurrentImageContext(); 
            UIGraphicsEndImageContext();
            //set image on main thread, but only if index still matches up
            dispatch_async(dispatch_get_main_queue(), ^{
                if (index == imageView.tag) {
                    imageView.image = image; 
                }
            }); 
        });
        return cell; 
    }
    
  6. +imageNamed:方法还会缓存解压之后的图片,以供以后使用。无论你是否还有指针指向这个UIImage。

Layer Performance

  1. CATextLayer和UILabel都是使用software drawing,所以会比那些使用hardware-accelerated的慢。所以要尽量避免改变有很多text的CATextLayer或是UILabel。
  2. shouldRasterize属性设置成YES,他会先在屏幕外面画好layer的image。然后这个image会被cache,之后会被画在真正的layer的位置上面。多次反复被使用。切记:如果一个layer的contents经常变化,不要使用这个。

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器