AutoLayout的原理,性能如何
AutoLayout的原理和性能
AutoLayout是苹果在2012年发布的一个框架,用于方便开发者适配不同尺寸的屏幕。它的语法可能有些蹩脚和冗长,但是通过一些封装库(如Masonry),可以更加简单易用。
AutoLayout的原理是基于Cassowary算法的一种实现。Cassowary是一种用于解决用户界面布局问题的算法,它将布局问题抽象成线性等式或不等式约束来进行求解。AutoLayout将视图的布局问题转换为线性等式,然后通过求解这些等式来确定视图的frame属性(origin和size)[1]。
AutoLayout的性能在不同版本的iOS系统中有所不同。在iOS12之前,AutoLayout的性能相对较低。根据测试数据,直接使用Frame布局的性能最好,而嵌套使用AutoLayout的性能消耗较大。在复杂页面中,AutoLayout的性能问题可能会导致应用卡顿和性能下降。然而,在iOS12之后,苹果对AutoLayout进行了优化,将其性能从指数关系优化到了线性关系。因此,在iOS12及以后的系统中,AutoLayout的性能得到了明显的提升[1][2]。
苹果提供了一些优化AutoLayout性能的建议:
- 避免写重复的约束和无用的约束。
- 一个视图中不要使用两套约束。
- 避免频繁地移除和添加约束,尽量在需要的地方更新约束。
- 对于一些内置宽高的控件(如UILabel、UIImageView、UIButton),可以使用intrinsicContentsize属性,但这可能会影响性能。
- 尽量少使用systemLayoutSizeFittingSize方法,因为每次调用都会创建和销毁一个布局引擎,消耗性能[1]。
Learn more:
- iOS Auto Layout的实现原理及性能。 - 简书
- 从 Auto Layout 的布局算法谈性能 - 面向信仰编程
- 【学习总结】03 | Auto Layout 是怎么进行自动布局的,性能如何? - 掘金
UIView & CALayer的区别
UIView & CALayer的区别
UIView和CALayer是iOS开发中常用的两个类,它们在界面的绘制和显示中起着重要的作用。下面是UIView和CALayer的区别:
-
响应事件:UIView可以处理用户的触摸事件,而CALayer不处理用户的交互,不参与事件的传递[1]。
-
继承关系:UIView继承自UIResponder类,可以响应事件,而CALayer继承自NSObject类,不能响应事件[2]。
-
功能:UIView是对CALayer的高级封装,除了负责显示UI外,还负责事件的处理和响应。CALayer主要负责管理基于图像内容的对象,允许对内容执行动画[2]。
-
可视属性:UIView和CALayer都可以设置可视属性,例如背景颜色、边框和阴影。但是CALayer的可视属性是在UIView的基础上进行封装和管理的[2]。
-
复用性:UIView和CALayer的设计考虑到了复用的需求。在macOS和App系统上,NSView和UIView在实现上有显著区别,但它们都依赖于CALayer。因此,为了复用,需要封装一个CALayer出来[2]。
总结: UIView和CALayer在界面的绘制和显示中扮演不同的角色。UIView负责处理用户的触摸事件和事件的传递,同时封装了CALayer的高级接口。而CALayer主要负责管理图像内容和执行动画。它们的关系是UIView依赖于CALayer来显示界面。
Learn more:
事件响应链
iOS事件响应链是指在iOS应用程序中,当用户触摸屏幕或进行其他交互操作时,系统会按照一定的规则将事件传递给相应的对象进行处理。事件的传递和响应是通过响应者链来完成的。
-
响应者链的构成:
- 响应者链由一系列对象组成,每个对象都有机会接收和处理事件。
- 响应者链的顺序是从最底层的视图开始,逐级向上,直到顶层的应用程序对象。
- 响应者链的顺序一般是:视图 -> 视图控制器 -> 窗口 -> 应用程序对象。
-
响应者链的作用:
- 当事件发生时,系统会将事件发送给第一响应者,即响应者链的第一个对象。
- 如果第一响应者无法处理事件,系统会将事件沿着响应者链向上传递,直到找到能够处理事件的对象为止。
- 如果整个响应者链都无法处理事件,事件将被丢弃。
-
事件的传递和响应:
- 当用户触摸屏幕或进行其他交互操作时,系统会将事件打包成一个UIEvent对象,并将其放入当前活动应用程序的事件队列中。
- UIApplication会从事件队列中取出事件,并将其传递给窗口对象。
- 窗口对象会使用hitTest:withEvent:方法找到事件发生的初始点所在的视图。
- hitTest:withEvent:方法会遍历视图层级结构,判断触摸点是否在每个视图内,并找到最合适的视图作为第一响应者。
- 事件会沿着响应者链向上传递,直到找到能够处理事件的对象为止。
Learn more:
drawrect & layoutsubviews调用时机
UIView的drawRect和layoutSubviews方法是用于视图绘制和布局的重要方法。它们的调用时机如下:
调用时机:
-
drawRect方法的调用时机:
-
layoutSubviews方法的调用时机:
注意事项:
-
在drawRect方法中,只能在该方法中获取contextRef进行绘图操作,不能在其他方法中获取并保存contextRef进行绘图[1]。
-
如果使用CALayer进行绘图,可以在drawInContext方法中进行绘制,或者在delegate中的相应方法中进行绘制[1]。
-
如果需要实时绘图,不能使用gestureRecognizer,而是需要使用touchbegan等方法来调用setNeedsDisplay实时刷新屏幕[1]。
-
UIImageView继承自UIView,但是不建议在UIImageView中重写drawRect方法进行自定义绘图,因为UIImageView是专门用于显示图片的控件,已经使用了最优的显示技术[1]。
-
setNeedsLayout
标记为需要重新布局,异步调用layoutIfNeeded
刷新布局,不立即刷新,在下一轮runloop结束前刷新,对于这一轮runloop
之内的所有布局和UI上的更新只会刷新一次,layoutSubviews
一定会被调用。 -
layoutIfNeeded
如果有需要刷新的标记,立即调用layoutSubviews
进行布局(如果没有标记,不会调用layoutSubviews
)。
Learn more:
- UIView之drawRect: & layoutSubviews的作用和机制 - 掘金
- LayoutSubviews和drawRect调用时机 - 简书
- layoutSubviews和drawRect调用时机的探究_drawrect 触发时机-CSDN博客
iOS隐式动画 & 显示动画区别
在iOS中,隐式动画和显式动画是两种不同的动画方式。它们在实现方式和效果上有一些区别。
-
隐式动画:
- 隐式动画是指在不显式创建动画的情况下,通过改变CALayer的可做动画的属性来实现动画效果。
- 隐式动画是默认开启的,即使不进行任何设置,改变图层属性时也会自动产生动画效果。
- 隐式动画的持续时间默认为0.25秒,可以通过CATransaction类来设置动画的持续时间。
- 隐式动画的类型和持续时间取决于当前事务的设置和图层行为。
-
显式动画:
- 显式动画是指通过用户自己创建动画来实现的,使用beginAnimations:context:和commitAnimations方法来创建动画。
- 显式动画需要手动开启和关闭,需要在beginAnimations:context:和commitAnimations之间设置动画的属性和效果。
- 显式动画可以更加灵活地控制动画的属性、持续时间、延迟等。
综上所述,隐式动画是默认开启的,通过改变图层属性来实现动画效果,而显式动画需要手动创建并设置动画的属性和效果。
1. 事务
事务,其实是Core Animation用来包含一系列属性动画集合的机制,通过指定事务来改变图层的可动画属性,这些变化都不是立刻发生变化的,而是在事务被提交的时候才启动一个动画过渡到新值。任何可以做动画的图层属性都会被添加到栈顶的事务。
objectivec
//1.动画属性的入栈
+ (void)begin;
//2.动画属性出栈
+ (void)commit;
//3.设置当前事务的动画时间
+ (void)setAnimationDuration:(CFTimeInterval)dur;
//4.获取当前事务的动画时间
+ (CFTimeInterval)animationDuration;
//5.在动画结束时提供一个完成的动作
+ (void)setCompletionBlock:(nullable void (^)(void))block;
现在再来考虑隐式动画,其实是Core Animation在每个RunLoop周期中会自动开始一次新的事务,即使你不显式的使用[CATranscation begin]开始一次事务,任何在一次RunLoop运行时循环中属性的改变都会被集中起来,执行默认0.25秒的动画。
通过事务来设置动画:
ini
[CATransaction begin]; //入栈
//1.设置动画执行时间
[CATransaction setAnimationDuration:3];
//2.设置动画执行完毕后的操作:颜色渐变之后再旋转90度
[CATransaction setCompletionBlock:^{
CGAffineTransform transform = self.colorLayer.affineTransform;
transform = CGAffineTransformRotate(transform, M_PI_2);
self.colorLayer.affineTransform = transform;
}];
CGFloat red = arc4random() % 255 / 255.0;
CGFloat green = arc4random() % 255 / 255.0;
CGFloat blue = arc4random() % 255 / 255.0;
UIColor *randomColor = [UIColor colorWithRed:red green:green blue:blue alpha:1];
_colorLayer.backgroundColor = randomColor.CGColor;
[CATransaction commit]; //出栈
2. 图层行为
我们上述的实验对象是一个独立图层,如果直接对UIView或者CALayer关联的图层layer改变动画属性,这样是没有隐式动画效果的,这说明虽然Core Animation对所有的CALayer动画属性设置了隐式动画,但UIView把它关联的图层的这个特性给关闭了。 为了更好的理解中一点,我们需要知道隐式动画是如何实现的: 我们把改变属性时CALayer自动执行的动画称作行为,当CALayer的属性被修改时,它会调用-actionForKey:方法传递属性名称,我们可以找到这个方法的具体说明如下:
python
/* Returns the action object associated with the event named by the
* string 'event'. The default implementation searches for an action
* object in the following places:
*
* 1. if defined, call the delegate method -actionForLayer:forKey:
* 2. look in the layer's `actions' dictionary
* 3. look in any `actions' dictionaries in the `style' hierarchy
* 4. call +defaultActionForKey: on the layer's class
*
* If any of these steps results in a non-nil action object, the
* following steps are ignored. If the final result is an instance of
* NSNull, it is converted to `nil'. */
- (nullable id<CAAction>)actionForKey:(NSString *)event;
翻译过来大概就是说:
- 图层会首先检测它是否有委托,并且是否实现CALayerDelegate协议指定的-actionForLayer:forKey方法;如果有,就直接调用并返回结果。
- 如果没有委托或者委托没有实现-actionForLayer:forKey方法,图层将会检查包含属性名称对应行为映射的actions字典
- 如果actions字典没有包含对应的属性,图层接着在它的style字典里搜索属性名.
- 最后,如果在style也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的+defaultActionForKey:方法
从流程上分析来看,经过一次完整的搜索动画之后,-actionForKey:要么返回空(这种情况不会有动画发生),要么返回遵循CAAction协议的对象(CALayer拿这个结果去对先前和当前的值做动画)。现在我们再来考虑UIKit是如何禁用隐式动画的: 每个UIView对它关联的图层都遵循了CALayerDelegate协议,并且实现了-actionForLayer:forKey方法。当不在一个动画块中修改动画属性时,UIView对所有图层行为都返回了nil,但是在动画Block范围就返回了非空值,下面通过一段代码来验证:
objectivec
@interface TestLayerAnimationVC ()
@property (nonatomic,weak)IBOutlet UIView *layerView;
@end
- (void)viewDidLoad {
[super viewDidLoad];
//测试图层行为:UIKit默认关闭了自身关联图层的隐式动画
NSLog(@"OutSide:%@",[self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);
[UIView beginAnimations:nil context:nil];
NSLog(@"InSide:%@",[self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);
[UIView commitAnimations];
}
//打印:
OutSide:<null>
InSide:<CABasicAnimation: 0x600001703100>
由此得出结论:当属性在动画块之外发生变化,UIView直接通过返回nil来禁用隐式动画。但是如果在动画块范围内,UIView则会根据动画具体类型返回响应的属性,
3. 关闭和开启隐式动画
当然,返回nil并不是禁用隐式动画的唯一方法,CATransaction也为我们提供了具体的方法,可以用来对所有属性打开或者关闭隐式动画,方法如下:
objectivec
+ (void)setDisableActions:(BOOL)flag;
UIView关联的图层禁用了隐式动画,那么对这种图层做动画的方法有有了以下几种方式:
- 使用UIView的动画函数(而不是依赖CATransaction)
- 继承UIView,并覆盖-actionforLayer:forkey:方法
- 直接创建显式动画
其实,对于单独存在的图层,我们也可以通过实现图层的-actionforLayer:forkey:方法,或者提供一个actions字典来控制隐式动画
4. 自定义图层行为
通过对事务和图层行为的了解,我们可以这样思考,图层行为其实是被Core Animation隐式调用的显式动画对象。我们可以发现改变隐式动画的这种图层行为有两种方式: 1.给layer设置自定义的actions字典 2.实现委托代理,返回遵循CAAction协议的动画对象 现在,我们尝试使用第一种方法来自定义图层行为,这里用到的是一个推进过渡的动画(也是遵循了CAAction的动画类),具体的代码如下:
ini
@interface TestLayerAnimationVC ()
@property (nonatomic,strong) CALayer *colorLayer;
@end
- (void)viewDidLoad {
[super viewDidLoad];
_colorLayer = [[CALayer alloc] init];
_colorLayer.frame = CGRectMake(30, 30, kDeviceWidth - 60, 60);
_colorLayer.backgroundColor = [UIColor orangeColor].CGColor;
//自定义动画对象
CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
_colorLayer.actions = @{@"backgroundColor":transition};
[self.view.layer addSublayer:_colorLayer];
}
- (IBAction)changeColor:(UIButton *)sender{
CGFloat red = arc4random() % 255 / 255.0;
CGFloat green = arc4random() % 255 / 255.0;
CGFloat blue = arc4random() % 255 / 255.0;
UIColor *randomColor = [UIColor colorWithRed:red green:green blue:blue alpha:1];
_colorLayer.backgroundColor = randomColor.CGColor;
}
Learn more:
什么是离屏渲染
离屏渲染(Off-Screen Rendering)是一种在图形处理单元(GPU)的当前屏幕缓冲区之外进行渲染操作的技术。当由于某些限制无法直接将渲染结果写入帧缓冲区时,渲染结果会先暂存在另外的内存区域,然后再写入帧缓冲区。这个过程被称为离屏渲染[1]。
离屏渲染在iOS开发中经常被提及,因为它可能会导致界面卡顿的问题。在OpenGL中,GPU屏幕渲染有两种方式:当前屏幕渲染和离屏渲染。当前屏幕渲染是在用于显示的屏幕缓冲区中进行,不需要额外创建新的缓存,也不需要切换上下文,因此性能较好。而离屏渲染则是在GPU的当前屏幕缓冲区之外开辟新的缓冲区进行操作[2]。
离屏渲染的代价相对较高,主要体现在以下两个方面:
- 创建新的缓冲区。
- 上下文切换。离屏渲染的整个过程需要多次切换上下文环境,从当前屏幕切换到离屏渲染,等待离屏渲染结束后,再将离屏缓冲区的渲染结果显示到屏幕上,这又需要将上下文环境从离屏切换回当前屏幕[2]。
以下情况会触发离屏渲染:
- shouldRasterize(光栅化)
- masks(遮罩)
- shadows(阴影)
- edge antialiasing(抗锯齿)
- group opacity(不透明)[2]
为了避免卡顿问题,应尽可能使用当前屏幕渲染,避免使用需要离屏渲染的技术。如果必须进行离屏渲染,对于相对简单的视图,可以使用CPU渲染;对于相对复杂的视图,可以使用一般的离屏渲染。需要注意的是,具体使用CPU渲染还是使用GPU离屏渲染需要进行性能上的具体比较[2]。
Learn more:
imageNamed & imageWithContentsOfFile区别
UIImage的imageNamed和imageWithContentsOfFile方法都是用于加载图片的方法,但它们有一些区别。
- imageNamed方法:
- 使用方法:UIImage *image = [UIImage imageNamed:@"imageName"];
- 缓存:imageNamed方法会将图像数据根据其名称缓存在系统内存中,以提高获取相同图片的性能[1].
- 自动释放:获取到的对象是autorelease的,不需要手动释放[1].
- 适用场景:适合加载小且使用频率较高的图片,例如在UITableViewCell或UICollectionViewCell中加载相同的图标[1].
- 注意事项:如果图片比较大或者图片较多,使用imageNamed方法可能会消耗较大的内存[3].
- imageWithContentsOfFile方法:
- 使用方法:UIImage *image = [UIImage imageWithContentsOfFile:@"filePath"];
- 缓存:imageWithContentsOfFile方法不会缓存图像数据,每次调用都会重新加载图片[1].
- 手动释放:获取到的对象是autorelease的,当autoreleasePool释放时才会释放[1].
- 适用场景:适合加载大的图片或者只使用一两次的图片,可以降低内存消耗[1].
- 注意事项:需要手动释放获取到的对象,不会进行系统缓存[1].
综上所述,根据不同的需求和场景选择使用相应的方法。如果图片较小且使用频率较高,可以使用imageNamed方法以提高性能和内存利用率。如果图片较大或者只使用一两次,可以使用imageWithContentsOfFile方法以降低内存消耗。
Learn more:
- iOS imageNamed和imageWithContentsOfFile区别 - 掘金
- iOS中imageNamed与imageWithContentsOfFile的区别 UIImage()_ios imagenamed_星星月亮0的博客-CSDN博客
- iOS 加载图片imageNamed 和 imageWithContentsOfFile区别-腾讯云开发者社区-腾讯云
图片是什么时候解码的,如何优化
iOS图片解码时机和优化方法
图片解码是将压缩的图片数据转换为可显示的像素数据的过程。在iOS中,图片解码的时机和优化方法对于应用的性能和用户体验非常重要。
-
图片解码时机:
- 图片解码通常发生在将图片显示在屏幕上之前。当需要显示图片时,iOS会自动进行解码操作,将图片数据转换为可显示的像素数据。
- UIImage的init方法对比:
- 使用
UIImage(named: String)
方法创建的UIImage对象,在加载到内存时就会进行解码操作,因此在使用该方法创建的UIImage对象时不会有额外的解码延迟。 - 使用
UIImage(contentsOfFile: String)
方法创建的UIImage对象,在加载到内存时不会进行解码操作,只有在使用该UIImage对象时才会进行解码,因此可能会有一定的解码延迟。
- 使用
-
图片解码优化方法:
- 使用合适的图片格式:对于大图,可以使用JPEG格式,因为它具有较小的文件大小和较快的加载速度。对于小图,可以使用PNG格式,因为它支持透明度,并且在解码速度上通常比JPEG快一些。
- 图片压缩:对于应用中的所有图片,可以进行压缩处理以减小文件大小,从而减少加载时间和内存占用。
- 图片缓存:使用图片缓存库,如SDWebImage或Kingfisher,可以将解码后的图片数据缓存到内存或磁盘中,以便在下次使用时直接加载,提高加载速度和性能。
- 异步加载:在加载大图或网络图片时,可以使用异步加载的方式,避免阻塞主线程,提高用户体验。
- 图片预加载:在需要显示大量图片的场景中,可以提前加载图片数据,避免在显示时出现卡顿现象。
Learn more:
图片渲染怎么优化
在iOS中,图片渲染的优化可以通过以下几种方法来实现:
-
使用合适的图片格式:选择合适的图片格式可以减小图片文件的大小,从而减少内存占用。常见的图片格式有JPEG、PNG和GIF等,其中JPEG适合存储照片,PNG适合存储透明图片,GIF适合存储动画图片。
-
压缩图片大小:通过压缩图片的尺寸和质量来减小图片文件的大小。可以使用图像处理工具或代码来实现图片压缩,例如使用UIImage的方法来调整图片的大小和质量。
-
使用缓存机制:将已经加载过的图片缓存起来,下次需要使用时直接从缓存中获取,避免重复加载和解码图片,提高性能和加载速度。
-
异步加载图片:在加载图片时,使用异步加载的方式,避免阻塞主线程,提高用户体验。可以使用GCD或NSOperationQueue来实现异步加载图片。
-
使用渐进式加载:渐进式加载是一种逐步显示图片的方式,先显示模糊的低分辨率图片,然后逐渐加载高分辨率的图片。这样可以提高用户感知的加载速度。
-
图片解码优化:图片解码是图片渲染的关键步骤,可以通过使用更高效的解码算法或库来优化解码过程,减少内存占用和解码时间。
-
使用矢量图形:矢量图形是基于数学公式描述的图形,可以无损缩放和渲染,不会出现像素失真。使用矢量图形可以减小图片文件的大小,并且适应不同屏幕的分辨率。
-
避免频繁的图片操作:频繁的图片操作会增加CPU和内存的消耗,影响性能。在处理图片时,尽量避免频繁的裁剪、旋转和滤镜等操作。
综上所述,通过选择合适的图片格式、压缩图片大小、使用缓存机制、异步加载图片、渐进式加载、图片解码优化、使用矢量图形和避免频繁的图片操作等方法,可以有效优化iOS中的图片渲染性能。
Learn more:
如果GPU的刷新率超过了iOS屏幕60Hz刷新率是什么现象,怎么解决
当GPU的刷新率超过了iOS屏幕的60Hz刷新率时,可能会出现以下现象:
-
屏幕撕裂(Screen Tearing):由于GPU的刷新速度超过了屏幕的刷新速度,导致屏幕上不同部分的图像在同一时间点上显示的是不同的帧,从而产生屏幕撕裂的现象。
-
视觉不连贯(Visual Inconsistency):当GPU的刷新率超过了屏幕的刷新率时,可能会导致图像在屏幕上的显示不连贯,出现闪烁或者不稳定的情况。
为了解决这个问题,可以考虑以下方法:
-
垂直同步(Vertical Sync):垂直同步是一种技术,通过将GPU的输出与显示器的刷新率同步,确保每一帧的图像都在显示器的垂直回扫期间显示。这样可以避免屏幕撕裂和视觉不连贯的问题。在iOS中,可以通过设置
CADisplayLink
的preferredFramesPerSecond
属性来实现垂直同步。 -
限制GPU的帧率(Limit GPU Frame Rate):可以通过限制GPU的帧率来确保其不超过屏幕的刷新率。这可以通过在应用程序中设置适当的帧率限制来实现,例如使用
CADisplayLink
的frameInterval
属性来设置帧率。 -
优化图形渲染(Optimize Graphics Rendering):优化图形渲染可以减少GPU的负载,从而降低其刷新率。可以通过以下方法来优化图形渲染:
- 减少不必要的图形绘制操作,只在需要更新的时候进行绘制。
- 使用合适的图像压缩格式,减少图像的内存占用和渲染时间。
- 避免过多的图层叠加和透明度设置,减少离屏渲染的次数。
- 使用合适的图形渲染技术,如Metal或OpenGL ES,以提高渲染效率。
请注意,以上方法可能需要根据具体的应用场景和需求进行调整和优化。
Learn more: