其他04 代理和Block/动画/设计模式/性能/解码

1,077 阅读9分钟

1、代理和Block

block其实是一个对象,代理是一种设计模式(委托-代理模式),

  (1)全局静态 block,不会访问任何外部变量,执行完就销毁。
       ^{ NSLog(@"Hello World!"); }();

  (2)保存在栈中的 block,当函数返回时会被销毁,和第一种的区别就是调用了外部变量。[UIView animateWithDuration:3 animations:^{ self.view.backgroundColor = [UIColor redColor]; }]; 

 (3)保存在堆中的 block,当引用计数为 0 时会被销毁。



2、动画

1. 简单动画 :
       animateWithDuration:animations
 带调节参数的 
+ animateWithDuration:delay:options:animations:completion:

2. 关键帧动画

 animateKeyframesWithDuration:

3, View 转换 
    + transitionWithView:duration:options:animations:completion:

+ transitionFromView:toView:duration:options:completion:

4. CALayer Animation  : CALayer 动画之前,首先需要引入 QuartzCore.framework

  CABasicAnimation 用于创建一个 CALayer 上的基本动画效果,
  CAKeyframeAnimation 关键帧动画
  CAAnimationGroup) 组动画(
  CATransition  切换动画

5. 更高级的动画效果  CADisplayLink  + Draw

6. UIDynamicAnimator 具有物理仿真效果的动画

7. CAEmitterLayer 是 Core Animation 提供的一个粒子发生器系统,可以用于创建各种粒子动画

3、设计模式

1. 单例模式

Cocoa 库本身在一些地方也使用了单例模式,这种写法的优点是,可以延迟加载,按需分配内存以节省开销。

这并非一个线程安全的写法,比如两个或多个线程并发的调用 sharedInstance 方法,有可能会得到多个实例,

 static
    if (!instance) { 
        instance = [[super allocWithZone:NULL] init]; 
    } 

这里列出两种方法来创建一个线程安全的单例
1 . 可以使用@synchronized进行加锁
2. GCD中的dispatch_once方法, 是最普遍也是苹果最推荐的方法

2. 工厂模式

工厂模式  本质上是    使用方法  来 简化类的选择 和 初始化过程

@implementation OperationFactory

+ (Operation *) createOperat:(char)operate{
    Operation *oper = nil;
    switch (operate) {
        case '+':
        {
            oper = [[OperationAdd alloc] init];
            break;
        }
        case '-':
        {
            oper = [[OperationSub alloc] init];
            break;
        }
    return oper;
}

由于 Objective-C 本身的动态特性,还可以用反射来改写:

@implementation OperationFactory
+ (Operation *) createOperat:(NSString *)operate{
    Operation *oper = nil;
    Class class = NSClassFromString(operate);
    oper = [(Operation *)[class alloc] init];
    if ([oper respondsToSelector:@selector(getResult)]) {
        [oper getResult];
    }
    return oper;
}
@end

使用时,可以传入类名,来获取对应类的对象:

Operation *oper = [OperationFactory createOperat: @"OperationAdd"];
oper.numberA = 10;
oper.numberB = 20;
NSLog(@"%f", oper.getResult);


3. 委托模式 (Delegate)

委托模式通常使用协议(protocol)来实现

4. 观察者模式
     两种用于实现观察者模式的办法,一直是使用NSNotification,另一种是KVO(Key Value Observing


6、性能

1. 离屏渲染

离屏渲染往往会带来界面卡顿的问题,这里将会讨论 当前屏幕渲染、离屏渲染 以及 CPU 渲染

当前屏幕渲染,在用于显示的屏幕缓冲区中进行,不需要额外创建新的缓存,也不需要开启新的上下文,所以性能较好,但是受到缓存大小限制等因素,一些复杂的操作无法完成

离屏渲染,指的是在 GPU 的当前屏幕缓冲区外开辟新的缓冲区进行操作。
离屏渲染的代价是很高的,体现在 :
1. 创建新的缓冲区
2. 
上下文切换。离屏渲染的整个过程,需要多次切换上下文环境:先从当前屏幕切换到离屏,等待离屏渲染结束后,将离屏缓冲区的渲染结果显示到到屏幕上,这又需要将上下文环境从离屏切换到当前屏幕。

以下属性时,会触发离屏渲染:

  • shouldRasterize(光栅化)
  • masks(遮罩)
  • shadows(阴影)
  • edge antialiasing(抗锯齿)
  • group opacity(不透明)

 避免使用 layer 的 border、corner、shadow、mask 等技术


CPU 渲染和离屏渲染的区别?
1. GPU的浮点运算能力比CPU强,CPU渲染的效率可能不如离屏渲染。
   如果仅仅是实现一个简单的效果,直接使用 CPU 渲染的效率又可能比离屏渲染好,毕竟普通的离屏渲染要涉及到缓冲区创建和上下文切换等耗时操作。
2. 对一些简单的绘制过程来说,这个过程有可能用CoreGraphics,全部用CPU来完成反而会比GPU做得更好。一个常见的 CPU 渲染的例子是:重drawRect 方法,并且使用任何 Core Graphics 的技术进行了绘制操作,就涉及到了 CPU 渲染。整个渲染过程由 CPU 在 App 内同步地完成,渲染得到的bitmap最后再交由GPU用于显示。

总之,具体使用 CPU 渲染还是使用 GPU 离屏渲染更多的时候需要进行性能上的具体比较才可以。

一个常见的性能优化的例子 : 加圆角

三种加圆角的方式:

  • 设置 cornerRadius  
     cornerRadius+ masksToBounds 会触发两次离屏渲染,如果加之滚动 就会性能问题
    通过缓存优化  shouldRasterize 光栅化YES + rasterizationScale  ,光栅化 会把使视图渲染内容被缓存起来,下次绘制的时候可以直接显示缓存,
    注意:png 图片 在 UIImageView 这样处理圆角是不会产生离屏渲染的( ios9 后不会,前会)
  • UIBezierPath
    drawRect:+ UIBezierPath
    这种方法会触发一次离屏渲染
    但是这种方式会导致内存暴增,并且同样会触发离屏渲染。
    
  • Core Graphics (为 UIView 加圆角)与直接截取图片(和 为 UIImageView 加圆角)

这种做法的原理是利用 Core Graphics 自己画出了一个圆角矩形。

UIGraphicsBeginImageContextWithOptions
UIGraphicsGetCurrentContext
CGContextMoveToPoint
CGContextDrawPath
UIGraphicsEndImageContext

这个方法返回的是 UIImage,有了这个图片后,就可以创建一个 UIImageView 并插入到视图层级的底部:

总结

  • 如果能够只用 cornerRadius 解决问题,就不用优化。
  • 如果必须设置 masksToBounds,可以参考圆角视图的数量,如果数量较少(一页只有几个)也可以考虑不用优化。
  • UIImageView 的圆角通过直接截取图片实现,其它视图的圆角可以通过 Core Graphics 画出圆角矩形实现。



7. 硬解码

硬编码:不使用CPU进行编码,使用显卡GPU,专用的DSP、FPGA、ASIC芯片等硬件进行编码。

1. 将H264码流转换为解码前CMSampleBuffer对象

H264码流由NALU单元组成,纳鲁单元包含视频图像数据CMBlockBuffer和H264的参数信息则可以组合成FormatDesc


** H264码流中 解码前的CMSampleBuffer对象,包括CMTime、CMVideoFormatDesc、CMBlockBuffer等,我们解码的任务就是从H264码流里面提取上面三处的信息,合成解码后的CMSampleBuffer对象,提供给硬解码接口进行解码工作。

2. 硬解码后的图像显示

下面我们就看一下硬解码后的图像显示,具体的显示方式有两种:

  • 通过系统提供的AVSampleBufferDisplayLayer来解码并显示。
  • 通过VTDecompression接口来,将CMSampleBuffer解码成图像,将图像通过UIImageView或者OpenGL上显示。

通过系统提供的AVSampleBufferDisplayLayer来解码并显示

AVSampleBufferDisplayLayer是苹果提供的一个专门显示解码后的H264数据的显示层,它是CALayer的子类,因此使用方式和其它CALayer类似。使用方法enqueueSampleBuffer :进行显示该层内置了硬件解码功能,将原始的CMSampleBuffer解码后的图像直接显示在屏幕上面,如下图所示。

通过  VTDecompression  接口来,将CMSampleBuffer解码成图像,将图像通过UIImageView或者OpenGL上显示
将解码后的CMSampleBuffer数据解码成 CVPixelBufferRef 数据,
将 CVPixelBufferRef 装换成 UIImage并显示

  • 解码方式一

    • 优点: 该方式通过系统提供的AVSampleBufferDisplayLayer显示层来解码并显示。该层内置了硬件解码功能,将原始的CMSampleBuffer解码后的图像直接显示在屏幕上,非常的简单方便,且执行效率高,占用内存相对较少。

    • 缺点: 从解码的数据中不能直接获取图像数据并对其做相应处理,解码后的数据不能直接进行其他方面的应用(一般要做较复杂的转换)。

    • 解码方式二

      • 优点: 该方式通过VTDecompressionSessionDecodeFrame接口,得到CVPixelBufferRef数据,我们可以直接从CVPixelBufferRef数据中获取图像数据并对其做相应处理,方便于其他应用。

      • 缺点: 解码中执行效率相对降低,占用的内存也会相对较大。


8. 硬编码

硬编码我们也经常见,比如说,我们直播录制视频,就要先通过摄像头采集图像,然后进行硬编码,最后将硬编码后的数据组合成H264码流通过网络传播。

1. 视频采集

硬件设备就是摄像头了,通过AVFoundation框架中的AVCaptureSession类来采集图像,并设定好input和output,同时设定deleagte代理和输出队列,在代理delegate方法中,处理采集好的图像。图像输出的格式是未编码的CMSampleBuffer形式。

2. 使用VTCompressionSession进行硬编码

来对该帧进行硬编码,编码成功的CMSampleBuffer转换成H264码流,通过网络传播



9. 软编码

视频采集后 H264进行编码,iOS 11 之后,iPhone 7以上的设备可以支持新的编码器H265编码器,使得同等质量视频占用的存储空间更小。所以本例中可以使用两种方式实现视频数据的编码

  • 软编码:使用CPU进行编码。


    这里借助FFmpeg
        //打开视频文件,将文件格式的上下文传递到AVFormatContext类型的结构体中
    
        //查找文件中视频流的信息
        //找到该视频流对应的解码器
        //打开解码器
         //pFrame = av_frame_alloc后面就是将YUV数据转换为RGB,然后将RGB图像转换为UIImage,
    便可以在屏幕上显示出来了
    可以参考的是 KXMovie

软硬区别对比

  • 软编码:实现直接、简单,参数调整方便,升级易,但CPU负载重,性能较硬编码低,低码率下质量通常比硬编码要好一点。
  • 硬编码: 性能高,低码率下通常质量低于软编码器,但部分产品在GPU硬件平台移植了优秀的软编码算法(如X264)的,质量基本等同于软编码。


    苹果在iOS 8.0系统之前,没有开放系统的硬件编码解码功能,不过Mac OS系统一直有,被称为Video ToolBox的框架来处理硬件的编码和解码,终于在iOS 8.0后,苹果将该框架引入iOS系统
  • H265优点

      • 压缩比高,在相同图片质量情况下,比JPEG高两倍
      • 能增加如图片的深度信息,透明通道等辅助图片。
      • 支持存放多张图片,类似相册和集合。(实现多重曝光的效果)
      • 支持多张图片实现GIF和livePhoto的动画效果。
      • 无类似JPEG的最大像素限制
      • 支持透明像素
      • 分块加载机制
      • 支持缩略图