Private



Notes

界面渲染流程

点击 Button 点击屏幕,SpringBoard.app 通过 IPC(进程间通信) 转发消息给 App, 由 runloop 的 source1 处理,然后在下一个 runloop 由 source0 通过 UIApplication 把 UIEvent 转发给 UIWindow,然后再通过 hitTest ,找到相应的 view,然后看有没 UIResponder 响应,此时 button 响应,触发 CA::Transaction::commit() 提交中间状态,最终在 RunLoop 即将进入休眠(或者退出)时,由 QuartzCore Framework 内的 CoreAnimation 把中间状态通过 IPC 提交到 GPU。

Core Animation 的核心是 OpenGL ES 的一个抽象物,所以大部分的渲染是直接提交给 GPU 来处理。

UIView 是继承自 UIResponder 的,所以说 UIView 是可以响应事件的,而 CALayer 是不能的。 也就是说 UIView 负责处理用户交互,负责绘制内容的则是它持有的那个 CALayer。

CALayer 是负责绘制内容管理的一个类,而真正的绘制部分是由 CoreGraphics Framework 框架来处理的。CoreGraphics 中包括下图所示的各种类来处理绘制,比如:path 的绘图工作(如,CGPath)、变形操作(如,CGAffineTransform)、颜色管理(如,CGColor)、离屏渲染(如,CGBitmapContextCreateImage)、渲染模式(patterns)、渐变(gradients)、阴影效果、图形数据管理、图形创建、蒙版以及PDF文档的创建、显示和解析等等。

CoreGraphics 负责创建显示到屏幕上的数据模型,QuartzCore(CoreAnimation –> OpenGLES)负责把CoreGraphics 创建的数据模型真正显示到屏幕上。 CG 打头的类都是属于 CoreGraphics Framework

[CALayer drawInContext:] ()

着色器程序, 在 OpenGL ES 中着色器程序必须创建两种着色器:顶点着色器 (vertex shaders) 和片段着色器 (fragment shaders)

顶点着色器定义了在 2D 或者 3D 场景中几何图形是如何处理的。一个顶点指的是 2D 或者 3D 空间中的一个点。在图像处理中,有 4 个顶点:每一个顶点代表图像的一个角。顶点着色器设置顶点的位置,并且把位置和纹理坐标这样的参数发送到片段着色器。

顶点着色器,定义在 2D 或者 3D 场景中几何图形是如何处理的

经过 图元装配、光栅化 在光栅化(像素化)阶段,基本图元被转换为二维的片元(fragment),fragment 表示可以被渲染到屏幕上的像素,它包含位置,颜色,纹理坐标等信息

片段着色器实现了一个通用的可编程操作片段的方法.片段着色器执行由光栅化生成的每个片段。

片段着色器计算出每个像素的最终颜色

片段着色器的目的就是确定一个像素的颜色

再进行一些列测试,剪裁测试、模版测试、深度测试

混合、抖动

帧缓冲区

视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示

iOS 事件处理机制与图像渲染过程

界面渲染的整体流程

CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。

• Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。

• Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。

为什么 shouldRasterize, mask, shadows 等会触发离屏渲染?

猜测,因为需要更高级的功能来处理,可能“窗口系统提供的“帧缓冲区并没”高级“附件来处理,所以需要新创建一个新的帧缓冲区来处理,由于新的帧缓冲区不是默认的帧缓冲区,渲染命令对窗口的可视输出不会产生任何影响。出于这个原因,它被称为离屏渲染(off-screen rendering)

比如:阴影,需要深度缓冲(depth buffer)和模板缓冲(stencil buffer),而默认的帧缓冲区只有 color buffer,比如 GPUImage 的 FBO 默认只添加了 color buffer

// Attach color buffer to FBO on GL_COLOR_ATTACHMENT0
glFramebufferRenderbuffer(GLenum(GL_FRAMEBUFFER), GLenum(GL_COLOR_ATTACHMENT0), GLenum(GL_RENDERBUFFER), displayRenderbuffer)

FBO 是一个容器,自身不能用于渲染,需要与 纹理渲染缓冲(renderbuffer)对象 绑定在一起。 Render Buffer Object(RBO)即为渲染缓冲对象,分为 color buffer(颜色)、depth buffer(深度)、stencil buffer(模板)

关于 FBO离屏渲染

所谓的 FBO 就是Frame Buffer Object。之前我们使用 OpenGLES 渲染,都是直接渲染到屏幕上,FBO可以让我们的渲染不渲染到屏幕上,而是渲染到离屏Buffer中。这样的作用是什么呢?比如我们需要处理一张图片,在上传时增加时间的水印,这个时候不需要显示出来的。再比如我们需要对摄像头采集的数据,一个彩色原大小的显示出来,一个黑白的长宽各一半录制成视频。 像这些情况,我们就可以使用到 FBO离屏渲染 技术了,当然 FBO 并不是仅仅局限于此。

FBO 是一个容器,自身不能用于渲染,需要与一些可渲染的缓冲区绑定在一起,像纹理或者渲染缓冲区。

Render Buffer Object(RBO)即为渲染缓冲对象,分为color buffer(颜色)、depth buffer(深度)、stencil buffer(模板)。 在使用FBO做离屏渲染时,可以只绑定纹理,也可以只绑定Render Buffer,也可以都绑定或者绑定多个,视使用场景而定。如只是对一个图像做变色处理等,只绑定纹理即可。如果需要往一个图像上增加3D的模型和贴纸,则一定还要绑定depth Render Buffer。 同 Texture 使用一样,FrameBuffer 使用也需要调用 GLES20.glGenFrameBuffers 生成 FrameBuffer,然后在需要使用的时候调用 GLES20.glBindFrameBuffer。

为什么在 -drawRect: 用了 CoreGraphics 会造成内存高涨?

Core Graphics绘制 - 如果对视图实现了-drawRect:方法,或者CALayerDelegate的-drawLayer:inContext:方法,那么在绘制任何东西之前都会产生一个巨大的性能开销。为了支持对图层内容的任意绘制,Core Animation必须创建一个内存中等大小的寄宿图。然后一旦绘制结束之后,必须把图片数据通过IPC传到渲染服务器。在此基础上,Core Graphics绘制就会变得十分缓慢,所以在一个对性能十分挑剔的场景下这样做十分不好。

OpenGL ES

Android OpenGLES2.0(十二)——FBO离屏渲染 模板缓冲区 LearnOpenGL-帧缓冲区 内存恶鬼drawRect

Autorelease

  1. Autoreleasepool 与 Runloop 的关系 主线程默认为我们开启 Runloop,Runloop 会自动帮我们创建 Autoreleasepool,并进行Push、Pop 等操作来进行内存管理,在即将退出 Loop 时调用 _objc_autoreleasePoolPop() 来释放自动释放池

  2. Autorelease 对象什么时候释放? Autorelease 对象是在当前的 runloop 迭代结束时释放的,而它能够释放的原因是系统在每个 runloop 迭代中都加入了自动释放池 Push 和 Pop

  3. 什么对象自动加入到 Autoreleasepool 中 ##### 第一种 当使用 alloc/new/copy/mutableCopy 进行初始化时,会生成并持有对象(也就是不需要 pool 管理,系统会自动的帮他在合适位置 release) id obj = [NSMutableArray array]; 这种情况会自动将返回值的对象注册到autorealeasepool,代码等效于:

    ```
    @autorealsepool{
     id __autorealeasing obj = [NSMutableArray array];
    

    }


    ##### 第二种
    __weak修饰符只持有对象的弱引用

    ##### 第三种
    id的指针或对象的指针在没有显式指定时会被附加上__autorealeasing修饰符
    
# Runtime

struct objc_object {
Class isa OBJC_ISA_AVAILABILITY; };

struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;

if !OBJC2

Class super_class;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
**struct objc_method_list **methodLists**;
**struct objc_cache *cache**;
struct objc_protocol_list *protocols;

endif

};

struct objc_method {
SEL method_name; char method_types; / char指针,其实存储着方法的参数类型和返回值类型 / IMP method_imp; / 指向了方法的实现,本质上是一个函数指针 */ };

struct objc_ivar { char ivar_name
char
ivar_type
int ivar_offset

ifdef LP64

int space                                                

endif

}



## isa

每一个对象都有一个名为 isa 的指针,指向该对象的类。每一个类描述了成员变量的列表,成员函数的列表,还有对象能够接收的消息列表

## KVO

当你观察一个对象时,一个新的类会动态被创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。自然,重写的 setter 方法,并插入 `willChangeValueForKey` 和 `didChangeValueForKey`。最后把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。

## +(void)load; +(void)initialize;有什么用处?

在Objective-C中,runtime会自动调用每个类的两个方法。+load会在类初始加载时调用,+initialize会在第一次调用类的类方法或实例方法之前被调用。这两个方法是可选的,且只有在实现了它们时才会被调用。 
共同点:两个方法都只会被调用一次。

## 消息发送、消息转发
`[obj foo];`

在objc动态编译时,会被转意为:

objc_msgSend(obj, @selector(foo));

objc_msgSend ( id self, SEL op, … );


第一个参数 `id` 是一个指向类实例的指针,而这个类实例就是 `objc_object`,里面是含有一个 isa 指针,
isa 指针指向对象的类 `objc_class`,

`objc_class` 结构体内有:方法列表,成员变量列表,还有一个方法的 `cache`,

先在 `cache` 里有没缓存到这个方法,如果没有再去方法列表找,如果还没找到就到 `superclass` 好,如果找到了这个函数,就执行他的 IMP

如果最终还没找到,通常情况下抛出 `unrecognized selector sent to … ` 的异常。但在抛去异常之前,有三次拯救程序

1. 如果没有找到,Runtime 会发送 +resolveInstanceMethod: 或者 +resolveClassMethod: 尝试去 resolve 这个消息;

2. 如果 resolve 方法返回 NO,Runtime 就发送 -forwardingTargetForSelector: 允许你把这个消息转发给另一个对象;

3. 如果没有新的目标对象返回, Runtime 就会发送 -methodSignatureForSelector: 和 -forwardInvocation: 消息。你可以发送 -invokeWithTarget: 消息来手动转发消息或者发送 -doesNotRecognizeSelector: 抛出异常


###  Category
#### 为什么 Category 不能添加实例变量
在 Runtime 中,objc_class 结构体大小是固定的,不可能往这个结构体中添加数据,只能修改。所以 ivars 指向的是一个固定区域,之所以能在 Category 添加方法,那是因为 `methodLists` 是指向 `objc_method_list` 指针的指针

struct objc_ivar_list *ivars struct objc_method_list **methodLists


#### 为什么 Category 不能添加一个实例变量,而能添加属性,从 Category 的结构体可以看出

typedef struct category_t { const char name; classref_t cls; struct method_list_t instanceMethods; struct method_list_t classMethods; struct protocol_list_t protocols; struct property_list_t *instanceProperties; } category_t; “`

使用 Category 需要注意的地方

  1. 在 Category 中是不能添加实例变量
  2. Category 是在运行时加载,而不是在编译
  3. Category 的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的 Category 的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休,殊不知后面可能还有一样名字的方法。

集合

招聘一个靠谱的 iOS

Objective-C Runtime

Objective-C 消息发送与转发机制原理

深入理解Objective-C:Category