likes
comments
collection
share

视图&图像相关

作者站长头像
站长
· 阅读数 6

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性能的建议:

  1. 避免写重复的约束和无用的约束。
  2. 一个视图中不要使用两套约束。
  3. 避免频繁地移除和添加约束,尽量在需要的地方更新约束。
  4. 对于一些内置宽高的控件(如UILabel、UIImageView、UIButton),可以使用intrinsicContentsize属性,但这可能会影响性能。
  5. 尽量少使用systemLayoutSizeFittingSize方法,因为每次调用都会创建和销毁一个布局引擎,消耗性能[1]

Learn more:

UIView & CALayer的区别

UIView & CALayer的区别

UIView和CALayer是iOS开发中常用的两个类,它们在界面的绘制和显示中起着重要的作用。下面是UIView和CALayer的区别:

总结: UIView和CALayer在界面的绘制和显示中扮演不同的角色。UIView负责处理用户的触摸事件和事件的传递,同时封装了CALayer的高级接口。而CALayer主要负责管理图像内容和执行动画。它们的关系是UIView依赖于CALayer来显示界面。


Learn more:

事件响应链

iOS事件响应链是指在iOS应用程序中,当用户触摸屏幕或进行其他交互操作时,系统会按照一定的规则将事件传递给相应的对象进行处理。事件的传递和响应是通过响应者链来完成的。

  1. 响应者链的构成:

    • 响应者链由一系列对象组成,每个对象都有机会接收和处理事件。
    • 响应者链的顺序是从最底层的视图开始,逐级向上,直到顶层的应用程序对象。
    • 响应者链的顺序一般是:视图 -> 视图控制器 -> 窗口 -> 应用程序对象。
  2. 响应者链的作用:

    • 当事件发生时,系统会将事件发送给第一响应者,即响应者链的第一个对象。
    • 如果第一响应者无法处理事件,系统会将事件沿着响应者链向上传递,直到找到能够处理事件的对象为止。
    • 如果整个响应者链都无法处理事件,事件将被丢弃。
  3. 事件的传递和响应:

    • 当用户触摸屏幕或进行其他交互操作时,系统会将事件打包成一个UIEvent对象,并将其放入当前活动应用程序的事件队列中。
    • UIApplication会从事件队列中取出事件,并将其传递给窗口对象。
    • 窗口对象会使用hitTest:withEvent:方法找到事件发生的初始点所在的视图。
    • hitTest:withEvent:方法会遍历视图层级结构,判断触摸点是否在每个视图内,并找到最合适的视图作为第一响应者。
    • 事件会沿着响应者链向上传递,直到找到能够处理事件的对象为止。

Learn more:

drawrect & layoutsubviews调用时机

UIView的drawRect和layoutSubviews方法是用于视图绘制和布局的重要方法。它们的调用时机如下:

调用时机:

注意事项:


Learn more:

iOS隐式动画 & 显示动画区别

在iOS中,隐式动画和显式动画是两种不同的动画方式。它们在实现方式和效果上有一些区别。

  1. 隐式动画:

    • 隐式动画是指在不显式创建动画的情况下,通过改变CALayer的可做动画的属性来实现动画效果。
    • 隐式动画是默认开启的,即使不进行任何设置,改变图层属性时也会自动产生动画效果。
    • 隐式动画的持续时间默认为0.25秒,可以通过CATransaction类来设置动画的持续时间。
    • 隐式动画的类型和持续时间取决于当前事务的设置和图层行为。
  2. 显式动画:

    • 显式动画是指通过用户自己创建动画来实现的,使用beginAnimations:context:和commitAnimations方法来创建动画。
    • 显式动画需要手动开启和关闭,需要在beginAnimations:context:和commitAnimations之间设置动画的属性和效果。
    • 显式动画可以更加灵活地控制动画的属性、持续时间、延迟等。

综上所述,隐式动画是默认开启的,通过改变图层属性来实现动画效果,而显式动画需要手动创建并设置动画的属性和效果。

1. 事务

事务,其实是Core Animation用来包含一系列属性动画集合的机制,通过指定事务来改变图层的可动画属性,这些变化都不是立刻发生变化的,而是在事务被提交的时候才启动一个动画过渡到新值。任何可以做动画的图层属性都会被添加到栈顶的事务。

//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秒的动画。

通过事务来设置动画:

[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:方法传递属性名称,我们可以找到这个方法的具体说明如下:

/* 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;

翻译过来大概就是说:

  1. 图层会首先检测它是否有委托,并且是否实现CALayerDelegate协议指定的-actionForLayer:forKey方法;如果有,就直接调用并返回结果。
  2. 如果没有委托或者委托没有实现-actionForLayer:forKey方法,图层将会检查包含属性名称对应行为映射的actions字典
  3. 如果actions字典没有包含对应的属性,图层接着在它的style字典里搜索属性名.
  4. 最后,如果在style也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的+defaultActionForKey:方法

从流程上分析来看,经过一次完整的搜索动画之后,-actionForKey:要么返回空(这种情况不会有动画发生),要么返回遵循CAAction协议的对象(CALayer拿这个结果去对先前和当前的值做动画)。现在我们再来考虑UIKit是如何禁用隐式动画的: 每个UIView对它关联的图层都遵循了CALayerDelegate协议,并且实现了-actionForLayer:forKey方法。当不在一个动画块中修改动画属性时,UIView对所有图层行为都返回了nil,但是在动画Block范围就返回了非空值,下面通过一段代码来验证:

@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也为我们提供了具体的方法,可以用来对所有属性打开或者关闭隐式动画,方法如下:

+ (void)setDisableActions:(BOOL)flag;

UIView关联的图层禁用了隐式动画,那么对这种图层做动画的方法有有了以下几种方式:

  1. 使用UIView的动画函数(而不是依赖CATransaction)
  2. 继承UIView,并覆盖-actionforLayer:forkey:方法
  3. 直接创建显式动画

其实,对于单独存在的图层,我们也可以通过实现图层的-actionforLayer:forkey:方法,或者提供一个actions字典来控制隐式动画

4. 自定义图层行为

通过对事务和图层行为的了解,我们可以这样思考,图层行为其实是被Core Animation隐式调用的显式动画对象。我们可以发现改变隐式动画的这种图层行为有两种方式: 1.给layer设置自定义的actions字典 2.实现委托代理,返回遵循CAAction协议的动画对象 现在,我们尝试使用第一种方法来自定义图层行为,这里用到的是一个推进过渡的动画(也是遵循了CAAction的动画类),具体的代码如下:

@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:

  1. iOS中隐式动画和显示动画的区别原创 - CSDN博客
  2. iOS动画-CALayer隐式动画原理与特性-腾讯云开发者社区-腾讯云
  3. iOS中显式动画和隐式动画的细微区别 - 简书

什么是离屏渲染

离屏渲染在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方法都是用于加载图片的方法,但它们有一些区别。

  1. imageNamed方法:
  1. imageWithContentsOfFile方法:

综上所述,根据不同的需求和场景选择使用相应的方法。如果图片较小且使用频率较高,可以使用imageNamed方法以提高性能和内存利用率。如果图片较大或者只使用一两次,可以使用imageWithContentsOfFile方法以降低内存消耗。


Learn more:

图片是什么时候解码的,如何优化

iOS图片解码时机和优化方法

图片解码是将压缩的图片数据转换为可显示的像素数据的过程。在iOS中,图片解码的时机和优化方法对于应用的性能和用户体验非常重要。

  1. 图片解码时机:

    • 图片解码通常发生在将图片显示在屏幕上之前。当需要显示图片时,iOS会自动进行解码操作,将图片数据转换为可显示的像素数据。
    • UIImage的init方法对比:
      • 使用UIImage(named: String)方法创建的UIImage对象,在加载到内存时就会进行解码操作,因此在使用该方法创建的UIImage对象时不会有额外的解码延迟。
      • 使用UIImage(contentsOfFile: String)方法创建的UIImage对象,在加载到内存时不会进行解码操作,只有在使用该UIImage对象时才会进行解码,因此可能会有一定的解码延迟。
  2. 图片解码优化方法:

    • 使用合适的图片格式:对于大图,可以使用JPEG格式,因为它具有较小的文件大小和较快的加载速度。对于小图,可以使用PNG格式,因为它支持透明度,并且在解码速度上通常比JPEG快一些。
    • 图片压缩:对于应用中的所有图片,可以进行压缩处理以减小文件大小,从而减少加载时间和内存占用。
    • 图片缓存:使用图片缓存库,如SDWebImage或Kingfisher,可以将解码后的图片数据缓存到内存或磁盘中,以便在下次使用时直接加载,提高加载速度和性能。
    • 异步加载:在加载大图或网络图片时,可以使用异步加载的方式,避免阻塞主线程,提高用户体验。
    • 图片预加载:在需要显示大量图片的场景中,可以提前加载图片数据,避免在显示时出现卡顿现象。

Learn more:

图片渲染怎么优化

在iOS中,图片渲染的优化可以通过以下几种方法来实现:

  1. 使用合适的图片格式:选择合适的图片格式可以减小图片文件的大小,从而减少内存占用。常见的图片格式有JPEG、PNG和GIF等,其中JPEG适合存储照片,PNG适合存储透明图片,GIF适合存储动画图片。

  2. 压缩图片大小:通过压缩图片的尺寸和质量来减小图片文件的大小。可以使用图像处理工具或代码来实现图片压缩,例如使用UIImage的方法来调整图片的大小和质量。

  3. 使用缓存机制:将已经加载过的图片缓存起来,下次需要使用时直接从缓存中获取,避免重复加载和解码图片,提高性能和加载速度。

  4. 异步加载图片:在加载图片时,使用异步加载的方式,避免阻塞主线程,提高用户体验。可以使用GCD或NSOperationQueue来实现异步加载图片。

  5. 使用渐进式加载:渐进式加载是一种逐步显示图片的方式,先显示模糊的低分辨率图片,然后逐渐加载高分辨率的图片。这样可以提高用户感知的加载速度。

  6. 图片解码优化:图片解码是图片渲染的关键步骤,可以通过使用更高效的解码算法或库来优化解码过程,减少内存占用和解码时间。

  7. 使用矢量图形:矢量图形是基于数学公式描述的图形,可以无损缩放和渲染,不会出现像素失真。使用矢量图形可以减小图片文件的大小,并且适应不同屏幕的分辨率。

  8. 避免频繁的图片操作:频繁的图片操作会增加CPU和内存的消耗,影响性能。在处理图片时,尽量避免频繁的裁剪、旋转和滤镜等操作。

综上所述,通过选择合适的图片格式、压缩图片大小、使用缓存机制、异步加载图片、渐进式加载、图片解码优化、使用矢量图形和避免频繁的图片操作等方法,可以有效优化iOS中的图片渲染性能。


Learn more:

如果GPU的刷新率超过了iOS屏幕60Hz刷新率是什么现象,怎么解决

当GPU的刷新率超过了iOS屏幕的60Hz刷新率时,可能会出现以下现象:

  1. 屏幕撕裂(Screen Tearing):由于GPU的刷新速度超过了屏幕的刷新速度,导致屏幕上不同部分的图像在同一时间点上显示的是不同的帧,从而产生屏幕撕裂的现象。

  2. 视觉不连贯(Visual Inconsistency):当GPU的刷新率超过了屏幕的刷新率时,可能会导致图像在屏幕上的显示不连贯,出现闪烁或者不稳定的情况。

为了解决这个问题,可以考虑以下方法:

  1. 垂直同步(Vertical Sync):垂直同步是一种技术,通过将GPU的输出与显示器的刷新率同步,确保每一帧的图像都在显示器的垂直回扫期间显示。这样可以避免屏幕撕裂和视觉不连贯的问题。在iOS中,可以通过设置CADisplayLinkpreferredFramesPerSecond属性来实现垂直同步。

  2. 限制GPU的帧率(Limit GPU Frame Rate):可以通过限制GPU的帧率来确保其不超过屏幕的刷新率。这可以通过在应用程序中设置适当的帧率限制来实现,例如使用CADisplayLinkframeInterval属性来设置帧率。

  3. 优化图形渲染(Optimize Graphics Rendering):优化图形渲染可以减少GPU的负载,从而降低其刷新率。可以通过以下方法来优化图形渲染:

    • 减少不必要的图形绘制操作,只在需要更新的时候进行绘制。
    • 使用合适的图像压缩格式,减少图像的内存占用和渲染时间。
    • 避免过多的图层叠加和透明度设置,减少离屏渲染的次数。
    • 使用合适的图形渲染技术,如Metal或OpenGL ES,以提高渲染效率。

请注意,以上方法可能需要根据具体的应用场景和需求进行调整和优化。


Learn more:

  1. 阿里、字节:一套高效的iOS面试题之视图&图形 | 迈腾大队长
  2. IOS面试考察(七):图像相关问题_gpu的刷新率超过了ios屏幕60hz刷新-CSDN博客