iOS Core Animation
CALayer的一些重要属性
- contents
- contestGravity (UView-contentMode)
- contentsScale
- masksToBounds (UIView-clipsToBounds)
- contentsRect (The default contentsRect is {0, 0, 1, 1}, 代表整个backing image是可见的),如果设置成{0, 0, 0.5, 0.5},就变成左上角四分之一是可见的。(与CSS中的雪碧图概念类似),我们可以把一张图片设置成contents,然后再修改contentsRect属性,达到只使用一部分图片的效果。
- contentsCenter这个名字乍看上去具有误导性,它的默认值是{0, 0, 1, 1},这代表我们缩放图片的时候,整个图片的区域都会被缩放,如果我们修改contentCenter的值,就可以达到修改缩放区域的目的。聊天类的应用中该属性应该比较常用,比如聊天气泡。
设置backing image有两种方式。
一种是设置layer的contents属性,另一种方式是使用-drawRect:。
-drawRect:方法默认没有实现,因为UIView不要求一个custom backing image。If you don’t need this backing image, it’s a waste of memory and CPU time to create it, which is why Apple recommends that you don’t leave an empty -drawRect: method in your layer subclasses if you don’t intend to do any custom drawing.
Layer Geometry
- frame
The frame is not really a distinct property of the view or layer at all; it is a virtual property, computed from the bounds, position, and transform, and therefore changes when any of those properties are modified. Conversely, changing the frame may affect any or all of those values, as well.
(当对一个view进行transform的时候,bounds的宽高不一定和frame的宽高相等) - bounds
- position
- anchorPoint (想象用图钉把一张纸钉在墙上,钉子所在的点就是anchorPoint, position是anchorPoint所在的位置)
The anchorPoint can be placed outside of the layer bounds by specifying x or y values that are less than zero or greater than one.
- zPosition
- anchorPointZ (可以用该属性改变layer之间的展示顺序)
Visual Effects
Rounded Corners
- cornerRadius (默认情况下,该属性只会对CALayer的background color 起作用,the backing image or sublayers 不会受影响。如果我们把masksToBounds设置为YES, layer中的所有元素都会被切成圆角)
Layer Borders
- borderWidth
- borderColor
二者定义了一条被绘制在layer边上的线。该线条被绘制在layer bounds 内部,在layer中其它所有内容包括sublayers前面,(可以理解成border的zPostion值大一些???)
border总是依从layer的bounds,即使sublayer超出layer的bounds或者the backing image has an alpha mask containing transparent areas.Drop Shadows
drop shadow扮演一个表现深度的角色。它通常用来表明layering and priority,但有时被用来加强效果。 - shadowOPacity (默认值为0.0, 值的范围为0.0~1.0)。可以通过与shadowColor、shadowOffset、shadowRadius加强表现效果。
- shadowColor 阴影的颜色,默认黑色
- shadowOffset CGSize, 默认值为{0, -3}, 默认阴影在上方
- shadowRadius 阴影的模糊效果
Shadow Clipping
shandow依从the exact shape of layer’s contents, not just the bounds and cornerRadius。 - shadowPath 实时计算shadow很耗费CPU(especially if the layer contains multiple sublayers, each with alpha-masked backing images.)如果我们知道想要的shadow,可以通过指定shadowPath来提高性能。
1
2
3
4
5//create a square shadow
CGMutablePathRef squarePath = CGPathCreateMutable(); CGPathAddRect(squarePath, NULL, self.layerView1.bounds); self.layerView1.layer.shadowPath = squarePath; CGPathRelease(squarePath);
//create a circular shadow
CGMutablePathRef circlePath = CGPathCreateMutable(); CGPathAddEllipseInRect(circlePath, NULL, self.layerView2.bounds); self.layerView2.layer.shadowPath = circlePath; CGPathRelease(circlePath);
Layer Masking
- mask mask是CALayer类型,mask的颜色没有用,我们需要的是它的形状。如我们把一个白色的正方形Layer的mask设置为一个黑色三角形Layer,我们将会看到一个白色三角形的Layer。(可以想象成我们是把layer裁剪成mask的形状)
The really cool feature of CALayer masking is that you are not limited to using static images for your masks. Anything that can be composed out of layers can be used as the mask property, which means that your masks can be created dynamically using code, and even animated in real time.
Scaling Filters
- kCAFilterNearest和kCAFilterTrilinear
Group Opacity
- opacity (UIView has an equivalent property called alpha). 这个属性会影响sublayer的opacity
- shouldRasterize opacity会对它的sublayer起作用,它们的opacity会混合在一起,有可能不是我们想要的效果。这时就轮到shouldRasterize出场了,set shouldRasterize to YES, the layer and its sublayers will be collapsed into a single flat image before the opacity is applied.
rasterizationScale
By default, all layers are rasterized at a scale of 1.0,so if you use the shouldRasterize property, you should always ensure that you set the
rasterizationScale to match the screen to avoid views that look pixelated on a Retina display.1
2
3// enable rasterization for the translucent button
button2.layer.shouldRasterize = YES;
button2.layer.rasterizationScale = [UIScreen mainScreen].scale;UIViewGroupOpacity 我们可以在Info.plist中添加UIViewGroupOpacity并把它设置为YES.这会影响整个App
Transforms
Affine Transforms
- affineTransform (It’s equal to the UIView transform property)
- transform (It’s type is CATransform3D)
1 | CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2); |
We can create a computed transform using several functions1
2
3
4
5
6
7//create a new transform
CGAffineTransform transform = CGAffineTransformIdentity; //scale by 50%
transform = CGAffineTransformScale(transform, 0.5, 0.5); //rotate by 30 degrees
transform = CGAffineTransformRotate(transform, M_PI / 180.0 * 30.0); //translate by 200 points
transform = CGAffineTransformTranslate(transform, 200, 0);
//apply transform to layer
self.layerView.layer.affineTransform = transform;
Notice
A translation followed by rotation is not the same as a rotation followed by a translation.
The Shear Transform
3D Transforms
The Vanishing Point
Core Animation defines the vanishing point as being located at the anchorPoint of the layer being transformed.
When you change the position of a layer, you also change its vanishing point. This is important to remember when you are working in 3D.
The sublayerTransform Property
- sublayerTransform
- Backfaces, 如果我们将一个视图旋转180度,此时视图的景象与本来景象在镜子中的反射景象相同。
- doubleSided 该属性默认值为YES,如果设置为NO,那么layer翻转180度以后什么都看不到
Layer Flattening
Solid Objects
Light and Shadow
- GLKMatrix4
Specialized Layers
CAShapeLayer
优点
- 快速高效
- 搭配UIBezierPath可以快速的画出一些图形
- 可以
除了使用CAShapeLayer绘制圆角,UIBezierPath提供了分别控制矩形圆角大小的能力
1 | /define path parameters |
CATextLayer
渲染速度比UILabel快一些,使用上略麻烦,封装一些用起来简单一些。
Notice
contentScale默认值为1.0,rentina屏幕上字体会看起来模糊,将contentScale设为与屏幕scale一致即可
1 textLayer.contentsScale = [UIScreen mainScreen].scale;
CATransformLayer
CAGradientLayer
1 | //create gradient layer and add it to our container view |
CAReplicatorLayer
- 用于产生多个相同控件或者生成镜面效果
CAScrollLayer
- 可以滚动的layer
CATiledLayer
- 加载过大的图片时,不管UIImage-imageNamed:还是-imageWithContentsOfFile:
- Core Animation 强制使用CPU处理图片而不是GPU
- CATiledLayer通过把大图片分成多个小图片并且根据需要独立加载提供了一种高性能加载大图的解决方法
CAEmitterLayer
CAEmitterLayer作为CAEmitterCell的容器,可以制造烟、下雪、爆炸等场景。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 一个爆炸效果
//create particle emitter layer
CAEmitterLayer *emitter = [CAEmitterLayer layer];
emitter.frame = self.containerView.bounds;
[self.containerView.layer addSublayer:emitter];
//configure emitter
emitter.renderMode = kCAEmitterLayerAdditive;
emitter.emitterPosition = CGPointMake(emitter.frame.size.width / 2.0,
emitter.frame.size.height / 2.0);
//create a particle template
CAEmitterCell *cell = [[CAEmitterCell alloc] init];
cell.contents = (__bridge id)[UIImage imageNamed:@"Spark.png"].CGImage;
cell.birthRate = 150;
cell.lifetime = 5.0;
cell.color = [UIColor colorWithRed:1 green:0.5 blue:0.2 alpha:1.0].CGColor;
cell.alphaSpeed = -0.4;
cell.velocity = 50;
cell.velocityRange = 50;
cell.emissionRange = M_PI * 2.0;
//add particle template to emitter
emitter.emitterCells = @[cell];
CAEAGLLayer
利用GPU,性能好,使用略繁琐。
AVPlayerLayer 属于AVfoundation,不属于Core Animation
1 | // 简单的使用方式 |
Implict Animations
隐式动画总是自动执行,除非告诉它不要这么做。
Transactions
Core Aniamtion假设屏幕上的所有东西都会被animated。
Transactions被用来封装一组的动画
用法如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//begin a new transaction
[CATransaction begin];
//set the animation duration to 1 second
[CATransaction setAnimationDuration:1.0];
//randomize the layer background color
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:red
green:green
blue:blue
alpha:1.0].CGColor;
//commit the transaction
[CATransaction commit];
UIView的+beginAnimations:context:和+commitAnimations这一组方法以及+animateWithDuration:animations:的内部其实还是CATransaction方法。
Completion Block
1 | //begin a new transaction |
Layer Actions
CALayer执行动画时有下面这四个步骤
The animations that CALayer automatically applies when properties are changed are called actions. When a property of a CALayer is modified, it calls its -actionForKey: method, passing the name of the property in question. What happens next is quite nicely documented in the header file for CALayer, but it essentially boils down to this:
- The layer first checks whether it has a delegate and if the delegate implements the -actionForLayer:forKey method specified in the CALayerDelegate protocol. If it does, it will call it and return the result.
- If there is no delegate, or the delegate does not implement -actionForLayer:forKey, the layer checks in its actions dictionary, which contains a mapping of property names to actions.
- If the actions dictionary does not contain an entry for the property in question, the layer searches inside its style dictionary hierarchy for any actions that match the property name.
- Finally, if it fails to find a suitable action anywhere in the style hierarchy, the layer will fall back to calling the -defaultActionForKey: method, which defines standard actions for known properties.
UIView在animation block外部禁用了隐式动画。
禁用隐式动画有两种方式,
- [CATransaction setDisableActions:YES];
- return nil for the property actions
两个知识点
- UIView backing layer 禁用了隐式动画。想要启动一些属性的动画可以使用UIView animation block的相关方法或者是子类化UIView然后重写-actionForLayer:forKey: 方法或者使用explicit animation。
- For hosted(that is, nonbacking)layers, 我们可以通过实现-actionForLayer:forKey: layer delegate method或者提供actions dictionary 来控制动画。
提供action, CATransition的祖先实现了CAAction1
2
3
4
5
6
7
8
9
10
11
12
13//create sublayer
self.colorLayer = [CALayer layer];
self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
//add a custom action
CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
self.colorLayer.actions = @{@"backgroundColor": transition};
//add it to our view
[self.layerView.layer addSublayer:self.colorLayer];
Presentation Versus Model
我们看到的layer可以细分为两个,一个是presentationLayer,另一个是model Layer。当我们改变model Layer的proerpty时,model Layer的property立即被改变。我们看到的动画的主要由presetationLayer负责。可以通过访问一个layer的presentationLayer的property来获得一些实时的property value。
Explict Animations
显示动画可以让我们创建一些非线性的动画,比如说沿着曲线移动。
Property Animations
属性动画可以时layer的一个属性的一个值的改变,也可以是一组值。
属性动画也可以分成basic和keyframe
Basic Animations
- 一个需要注意的地方时basic animation作用于presenation layer, model layer并未改变,动画完成之后,model layer还是原来的值,这个地方需要我们自己去处理
CAAmimationDelegate
使用隐式动画时我们可以利用completion block 去检测动画是否已经完成。但是该方法不能作用于显示动画。我们可以通过实现CAAnimationDelegate协议来检测动画是否已经完成。1
2
3
4
5- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag {
//set the backgroundColor property to match animation toValue
[CATransaction begin];
[CATransaction setDisableActions:YES]; self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue; [CATransaction commit];
}
Question: 如何区分不同的animation?
1
2
3
4
5
6
7 [anim setValue:@"" forKey:@""];
给不同的animatin设置不同的key value, 然后在代理方法中拿到相关值进行区分。
- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag {
if ([[anim valueForKey:@""] isEqualToString:@""]) {
}
}
Keyframe Animations
CABasicAnimation向我们展示了隐式动画的机制,但是使用起来略繁琐。我们可以使用hosted layers的隐式动画或者UIView animation for views and backing layer来达到相同的效果,而且更简单。
相比CABasicAnimation,CAKeyframeAnimation更强大。CABasicAnimation需要a single start and end value,但是CAKeyframeAnimation可以使用一组值来做动画。
1 | //create a keyframe animation |
1 | //create the keyframe animation |
Virtual Properties
1 | //animate the ship rotation |
Notice
You cannot set properties like transform.position、transform.rotation or transform.scale directly; they can only be used for animation.
Animation Groups
CABasicAnmation and CAKeyFrameAnimation target individual properties, multiple such animations can be gathered together using CAAnimationGroup。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40//create a path
UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
[bezierPath moveToPoint:CGPointMake(0, 150)];
[bezierPath addCurveToPoint:CGPointMake(300, 150)
controlPoint1:CGPointMake(75, 0)
controlPoint2:CGPointMake(225, 300)];
//draw the path using a CAShapeLayer
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.path = bezierPath.CGPath;
pathLayer.fillColor = [UIColor clearColor].CGColor;
pathLayer.strokeColor = [UIColor redColor].CGColor;
pathLayer.lineWidth = 3.0f;
[self.containerView.layer addSublayer:pathLayer];
//add a colored layer
CALayer *colorLayer = [CALayer layer];
colorLayer.frame = CGRectMake(0, 0, 64, 64);
colorLayer.position = CGPointMake(0, 150);
colorLayer.backgroundColor = [UIColor greenColor].CGColor;
[self.containerView.layer addSublayer:colorLayer];
//create the position animation
CAKeyframeAnimation *animation1 = [CAKeyframeAnimation animation];
animation1.keyPath = @"position";
animation1.path = bezierPath.CGPath;
animation1.rotationMode = kCAAnimationRotateAuto;
//create the color animation
CABasicAnimation *animation2 = [CABasicAnimation animation];
animation2.keyPath = @"backgroundColor";
animation2.toValue = (__bridge id)[UIColor redColor].CGColor;
//create group animation
CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
groupAnimation.animations = @[animation1, animation2];
groupAnimation.duration = 4.0;
//add the animation to the color layer
[colorLayer addAnimation:groupAnimation forKey:nil];
Transitions
transition animation 作用于整个layer, transition animation 对old layer 进行截图,然后在新的视图上做文章。
- type 有kCATransitionFade(default)、kCATransitionMoveIn、kCATransitionMoveIn和kCATransitionReveal
- subtype kCATransitionMoveIn、kCATransitionMoveIn和kCATransitionReveal带有默认的方向,但是可以通过subtype去修改。subtype主要有以下值kCATransitionFromRight、kCATransitionFromLeft、kCATransitionFromTop和kCATransitionFromBottom。
Implicit Transitions
Notice
自己创建的layer,修改contents,会自动加上隐式动画crossfade。但是view backing layer禁用了所有的隐式动画
Animating Layer Tree Changes
- 可以在-tabBarController:didSelectViewController:方法中对controller的切换加一些transition animation
Custom Transitions
- 截图
Canceling an Animation in Progress
相关方法1
2
3- (CAAnimation *)animationForKey:(NSString *)key;
- (void)removeAnimationForKey:(NSString *)key;
- (void)removeAllAnimations;
正在进行中的动画不能被修改,但是可以remove。remove之后,layer呈现出 current model value的值。
Layer Time
The CAMediaTiming Protocol
The CAMediaTiming protocol定义了一些用来控制动画时间的proerpties。CALayer and CAAnimation都遵守该协议,所以时间可以从per-layer and per-animtion层面进行控制。
Duartion and Repetition
the total time = duration * repeatcount
the duration and repeatCount proeprties both default to zero. a value of zero in this case, is just used to mean “use the defaults”, which are 0.25 seconds and one iteration.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// a simple sample
CFTimeInterval duration = [self.durationField.text doubleValue];
float repeatCount = [self.repeatField.text floatValue];
//animate the ship rotation
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"transform.rotation";
animation.duration = duration;
animation.repeatCount = repeatCount;
animation.byValue = @(M_PI * 2);
animation.delegate = self;
[self.shipLayer addAnimation:animation forKey:@"rotateAnimation"];
//disable controls
[self setControlsEnabled:NO];1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// repaetaDuration 动画的“时间寿命”
// another sample
//apply perspective transform
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0 / 500.0;
self.containerView.layer.sublayerTransform = perspective;
//apply swinging animation
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"transform.rotation.y";
animation.toValue = @(-M_PI_2);
animation.duration = 2.0;
animation.repeatDuration = INFINITY;
animation.autoreverses = YES;
[doorLayer addAnimation:animation forKey:nil];
Relative Time
- beginTime 动画的开始时间(类似于delay).默认为0,从animation被add到visible layer的那一刻开始计算
- speed 如果duration是1, speed是2, 动画0.5s就会完成
- timeOffset 如果duartion是1s, timeOffset是0.5s, 这意味着动画从完整过程的一半开始执行
fillMode
Hierarchical Time
Each animation and layer has its own hierarchical concept of time, measured relative to its parent. Adjusting the timing for a layer will affect its own animations and those of its sublayers, but not its superlayer. The same goes for animations that are grouped hierarchically (using nested CAAnimationGroup instances).
Adjusting the duration and repeatCount/repeatDuration properties for a CALayer or CAGroupAnimation will not affect its children’s animations. The beginTime, timeOffset, and speed properties will impact child animations, however. In hierarchical terms, beginTime specifies a time offset between when the parent layer (or parent animation in the case of a grouped animation) starts animating and when the object in question should begin its own animation. Similarly, adjusting the speed property of a CALayer or CAGroupAnimation will apply a scaling factor to the animation speed for all of the children, as well.
Global Versus Local Time
Pause, Rewind, and Fast-Forward
self.window.layer.speed = 100; 使用该方法可以用于在自动化测试中加快测试速度。
Manual Animation
Easing
Animation Velocity
velocity = change / time
CAMediaTimingFunction
Notice
UIKit animation doesn’t work with hosted layers.
Easing and KeyFrame Animations
Custom Easing Functions
- functionWithName:
- functionWithControlPoints::::
1 | // 改变坐标系原点位置 |
Keyframe-Based Easing
1 | //reset ball to top of screen |
Timer-Based Animation
Frame Timeing
NSTimer
Notice
Every thread on iOS maintains an NSRunloop, which in simple terms is a loop that endlessly works through a list of tasks it needs to perform. For the main thread, these tasks might include the following:
- Processing touch events
- Sending and receiving network packets
- Executing code scheduled using GCD(Grand Central Dispatch)
- Handling timer actions
- Redrawing the screen
当床创建一个NSTimer的时候,它会被放到task list中,直到指定的时间带来时它都不会被执行。NSTimer等待的时间没有上限,只有等task list中前一个任务被执行完它才有可能被执行。
通常在指定的时间a few millseconds之内NSTimer会被执行,但是如果前一个任务完成的很慢,NStimer等待的时间可能会长一些。
The screen redrawing is scheduled to happen every sixtieth of a second, but just like a timer action, it may get delayed by an earlier task in the list that takes too long to execute.
因为这些delays是随机的,确保一个timer scheduled to fire every sixtieth of a second will always fire before the screen is redrawn是不可能的。有时候调用的太晚,动画看起来choppy, 有时候fire twice between updates, 导致a skipped frame
that will make the animation appear to jump forward.
改善方案如下- CADispalyLink that is designed to fire in lockstep with the screen refresh.
- 让我们动画基于the acutal recoreded frame 而不是假设frames准时执行。
- 调整animation timers的run loop mode从而使它不被其它时间延迟。
CADisplayLink
通过确保帧率尽可能的连续,CADisplayLink比NSTimer能产生更自然的动画。但是CADisplayLink也不能确保每一帧都按期执行。
CADisplayLink与NStimer的区别是,即使延迟了,NStimer也会执行,CADisplayLink会跳过那些错失的事项去到下一次指定的时间。
Mesauring Frame Duration
Run Loop Modes
每一添加到RunLoop的ta都有一个优先级。如果有太多的UI activity,为了确保流畅的UI体验iOS将会给予user interface相关任以优先级同时其它任务可能会被暂停一小段时间。
一个典型的例滚动UIScrollView。滚动期间,scrollView的重绘优先级高于其它任务所以标准的NSTimer和网络时间可能在滚动期间不会被执行。
Run Loop 的几种模式
- NSDefaultRunLoopMode-The standard pirority
- NSRunLoopCommonModes-High pirority
- UITrackingRunLoopMode
当你的动画在一个高帧率运行时,使用NSRunLoopCommonModes需要注意这可能导致其它任务比如NTimer或者其它的动画比如说滚动将会停止更新直到你的动画结束。
同一时间以多个run loop modes 使用一个CADisplayLink。
It’s possible to use a CADisplayLink with multiple run loop modes at the same time, so we could add it to both NSDefaultRunLoopMode and UITrackingRunLoopMode to ensure that it is not disrupted by scrolling, without interfering with the performance of other UIKit control animations, like this:
1
2
3 self.timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(step:)];
[self.timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[self.timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:UITrackingRunLoopMode];
Physical Simulation
Chipmunk
GPU-Bound Opearions
- Too much geometry
- Too much overdraw
- Offscreen drawing - 通常当一个特殊的效果不能直接被绘制在屏幕上时,就会发生离屏渲染。离屏渲染可能会使用CPU或者GPU,但是无论使用哪个都会为离屏图片申请额外的内存并且会在绘制上下文中进行切换。这两者都会拖慢GPU的性能。一些layer效果比如rounded corners, layer masks, drop shadows or layer rasterization都会迫使Core Animation提前进行离屏渲染。这并不代表说我们避免使用这些效果,而是说我们应该清楚地知道它们会有性能方面的影响。
- Too-large images - 如果试图绘制一个超出GPU支持的尺寸(根据具体设备稍有不同)的图片,那么每一次图片被展示的时候,CPU都必须要被用来处理图片,这也会拖慢性能。
CPU-Bound Operations
在Core Animation中,CPU的大部分工作都在animation开始之前。这通常不会影响frame rate,但是它可能会影响到动画的开始时间,造成界面响应不及时。
- Layout calculations - view层级非常复杂时,当一个view被呈现或者被修改时,计算所有layer的frame时可能会花一些时间。尤其是使用自动布局时,它比autoresizing更加耗费CPU。
- Lazy view loading
- Core Graphics drawing - -drawRect:和-drawLayer:inContext:,如果实现了CADelegate的这两个方法,就会在实际开始绘制之前就引入重大的性能影响。为了能够随意地在layer’s contetns 中绘制,Core Animation必须在内存中创建一个和View尺相等的backing image。一旦绘制完成,必须通过IPC把image data送到render server。在这个开销之上,Core Graphic的绘制非常慢,在性能要求严格的场景上,这可能不会是你想要的结果。
- Image decompression - PNG和JPG等图片通常都被压缩了,但是图片在被会知道屏幕上时必须要被解压缩。如果是大图片,也要花费一定的时间来解压缩。
IO-Bound Operations
IO(比如访问flash storage和network interface),IO相比正常的memory access要慢得多。所以如果你的动画是IO—bound可能会有问题。通常使用线程,缓存和预加载可以解决这些问题。
Measure,Don’t Guess
Test Reality, Not a Simulation
- shouldRasterize属性会离屏渲染layer一次,然后保存结果直到下次更新。
Efficent Drawing
Software Drawing
在Core Animation的上下文中,属于drawing通常代表software drawing(that is, drawing that is not GPU assited).iOS中的software drawing主要使用Core Graphics framework来完成。
相比硬件加速渲染和聚合执行Core Animaiton和OpenGL,software drawing真的很慢。
除了速度上的慢,software drawing还需要很多内存。 CALayerDelegate -drawLayer:inContext:会造成离屏渲染。
除非必要,尽可能少的绘制。
Vector Graphics
Asynchronous Drawing
UIKit单线程的性质意味着the backing images必须在主线程更新,这就表示绘制可能中断用户操作,让整个APP看起来卡住了。
解决方法时在其它线程提前绘制然后把相应的图片设置为layer的contents。CATiledLayer和drawsAsynchronously属性可以做这件事情。
CATailedLayer
drawsAsynchronously
Image IO
Loading and Latency
iOS 看门狗,APP 20s之内还没有启动完成,会被干掉。
Thread Loading
GCD and NSOperationQueue
Deferred Decompression
PNG文件比较大,加载慢,但是decompression比较快,尤其是Xcode using optimal settings for fast decoding 重新压缩了PNG,JPEG 文件较小加载更快但是decopression更慢。因为JPEG的decopression算法比PNG基于zip的算法更复杂。
+imageNamed:此方法加载图片之后立即解压缩,其它的图片加载方法都会推迟解压缩。+imageNamed:只对application resources bundle内的图片起作用。
另一个直接解压缩图片的方法是把图片赋值给layer的contens属性或者UIImageView的image的属性。但是这些操作必须在主线程中进行。
第三种方式是使用ImageIO framework, 代码如下:1
2
3
4
5
6
7NSInteger index = indexPath.row;
NSURL *imageURL = [NSURL fileURLWithPath:self.imagePaths[index]]; NSDictionary *options = @{(__bridge id)kCGImageSourceShouldCache: @YES}; CGImageSourceRef source = CGImageSourceCreateWithURL(
(__bridge CFURLRef)imageURL, NULL);
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0,
(__bridge CFDictionaryRef)options);
UIImage *image = [UIImage imageWithCGImage:imageRef]; CGImageRelease(imageRef);
CFRelease(source);
创建图片的时候使用kCGImageSourceShouldCache选项,这会强制解压缩图片并在image的生命周期内retain解压缩之后的图片。
第四种方式使用UIKit加载图片,然后立即把它绘制到CGContext中。图片在绘制之前必须被解压缩,所以此举会强制解压缩开始。这么做的优势在于绘制可以在后台线程中进行,因此不会阻塞UI。
CATailedLayer
Caching
The +imageNamed: Method
Custom Caching
NSCache
Hybrid Images
1 | //load color image |
JPEG2000
JPEG2000比相同大小JPEG图片拥有更好的质量,同时还提供透明度支持。但是JPEG2000的加载和展示速度比PNG和JPEG要慢。如果对App瘦身有较高的要求可以考虑JPEG2000。
PVRTC
Layer Performance
Inexplicit Drawing
The layer backing image可以显示的被创建,也可以隐形的被创建。
明白什么时候为什么会发生隐式创建可以避免不必要的绘制。
Text
CATextLayer和UILabel都是使用软件绘制,比较慢。包含文本的view的frame发生变化时,会造成文本重绘。要尽可能避免这一点。比如,如果要展示一个频繁变化size的角落里包含一段文本的layer,把text放到sublayer更合适。
Rasterization
启用shouldRasterize属性会使得layer被绘制成离屛图片。这个图片会被缓存并替换实际layer的contents和sublayers。
Rasterizing用的合适了就能改善性能,但是要避免Rasterizing layers whose content changes every frame,这会使caching的benefit无效,性能更糟糕。
To test whether you are using rasterization appropriately, use the Color Hits Green and Misses Red instrument to see if the rasterized image cache is being frequently flushed。
Offscreen Rendering
The layer attributes that trigger offscreen rendering are as follows:
- Rounded Corners (when combined with maskToBounds)
- layer masks
- Drop shadows
Offscreen rendering的影响和和enbale rasterization时很相似,除了开销没有rasterization a layer那么大,the sublayers are not affected, and the result is not cached, 所以没有长期的内存影响。
有太多离屛渲染的layers时候会对性能造成显著影响,但是可以启用rasterization来利用有利方面的影响(缓存),这种情况适用于不需要频繁重绘的layer。
对于需要离屛渲染的layer或者需要动画(或者有sublayer需要动画),可以使用CAShapeLayer,contentsCenter或者shadowPath去利用更少性能方面的影响区实现相似的表现。CAShapeLayer
分别使用cornerRadius或者masksToBounds不会造成性能方面的影响,but when combind, they trigger offscreen rendering. 使用CAShapeLayer和+bezierPathWithRoundedRect:cornerRadius:可以避免离屏渲染。
Stretchable Images
contentsCenter
shadowPath
Blending and Overdraw
混合图层或者忽略图层处理起来都很麻烦,可以尽可能的利用下面两点来优化性能:
- Set the backgroundColor of your view to a fixed, opaque color.
- Set the opaque property of the view to YES.