iOS 异步绘制

207 阅读9分钟

异步绘制(Asynchronous Drawing)

异步绘制则更专注于在后台线程进行实际的图形绘制操作,然后将绘制结果应用到 UI 上。这种做法不仅避免了主线程的阻塞,还能充分利用多线程的优势来提高图形绘制的效率和性能。

Core Graphics 和 Core Animation 是实现异步绘制的常用框架。例如,可以使用 CATiledLayer 来实现异步绘制大规模的图形。

异步绘制主要用于在后台线程执行实际的图形绘制操作,以提高图形绘制的效率和性能。

异步绘制通常使用 Core Graphics 或 Core Animation 等图形框架,并利用专门的图层(如 CATiledLayer)来进行后台绘制。

Facebook 的 Texture(以前称为 AsyncDisplayKit)框架主要基于异步绘制(Asynchronous Drawing)来提高用户界面的性能和响应速度。Texture 通过将耗时的界面绘制和布局操作放在后台线程中执行,来避免阻塞主线程,从而提供流畅的用户体验。

Texture 的核心功能之一是异步绘制。它通过在后台线程进行实际的图形绘制操作,然后将绘制结果合成到主线程上显示。这一过程主要通过以下机制实现:

ASDisplayNode:

ASDisplayNode 是 Texture 的基本单元,类似于 UIView,但它支持异步绘制。

ASDisplayNode 在后台线程执行 drawRect: 方法,通过 Core Graphics 进行绘制,并生成一个包含绘制内容的图像。

异步绘制队列:

Texture 使用专门的绘制队列来处理 ASDisplayNode 的绘制任务。绘制任务被分配到后台线程中执行,确保不会阻塞主线程。

绘制合成:

绘制完成后,生成的图像被合成到主线程的视图层次结构中,确保界面更新的流畅性。

主线程操作UI的疑问

iOS 开发中 UI 相关的操作不是要求必须在主线程么 ?为什么 Texture 可以把界面元素的渲染和布局操作移到后台线程 ?

在 iOS 开发中,确实有一个重要的原则:所有 UI 相关的操作必须在主线程上执行。这是因为 UIKit 不是线程安全的,多线程操作 UI 可能会导致不可预见的行为和崩溃。然而,Texture 框架通过巧妙的设计和实现,将界面元素的渲染和布局操作移到后台线程,而不违反这一原则。

Texture 如何实现异步渲染和布局
异步计算布局:

Texture 将布局计算移到后台线程。这些计算包括尺寸和位置的计算,而不是直接操作 UI。这意味着实际的 UI 更新还是在主线程上进行的,但计算工作已经在后台完成,从而减轻了主线程的负担。

// Example of layout calculation in background thread
[node measureWithSizeRange:ASSizeRangeMake(minSize, maxSize)];
异步绘制内容:

Texture 将绘制操作也移到了后台线程。具体来说,绘制操作会生成一个图像或其他内容,然后这些内容会在主线程上应用到 UI 组件中。这样,虽然绘制在后台完成,但实际的 UI 更新还是在主线程上进行的。

self.imageNode.imageModificationBlock = ^UIImage *(UIImage *image) {
    // Perform image modifications in background thread
    UIGraphicsBeginImageContextWithOptions(image.size, NO, [[UIScreen mainScreen] scale]);
    [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
    UIImage *modifiedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return modifiedImage;
};
双缓冲技术(Double Buffering):

Texture 使用双缓冲技术来确保 UI 更新的线程安全。后台线程进行布局和绘制后,生成的内容会被传递给主线程,主线程只负责最终的 UI 更新。这种方法确保了 UI 操作在主线程上进行,同时最大限度地利用了后台线程的计算能力。

[ASDisplayNode performBlockOnMainThread:^{
    // Apply the result of the background rendering to the UI
    [self.layer setContents:(__bridge id _Nullable)(image.CGImage)];
}];
节点(Node)系统:

Texture 的核心概念是节点(Node)系统,每个节点都是一个独立的实体,负责其自己的布局和渲染。节点的布局计算和绘制操作可以在后台线程中进行,而最终的 UI 更新还是在主线程上完成。

ASDisplayNode *node = [[ASDisplayNode alloc] init];
node.backgroundColor = [UIColor redColor];
node.frame = CGRectMake(0, 0, 100, 100);

Texture 通过将布局计算和绘制操作移到后台线程来提高性能,但它始终确保最终的 UI 更新在主线程上进行,从而遵守 iOS 的 UI 线程安全原则。通过这种方式,Texture 能够实现异步渲染和布局,从而提高应用的响应速度和性能,而不违反 UIKit 的线程安全要求。

小结

Texture 通过将布局计算和绘制操作移到后台线程来提高性能,但它始终确保最终的 UI 更新在主线程上进行,从而遵守 iOS 的 UI 线程安全原则。通过这种方式,Texture 能够实现异步渲染和布局,从而提高应用的响应速度和性能,而不违反 UIKit 的线程安全要求。

Texture 的异步绘制操作

Texture 将绘制操作移到后台线程,具体是怎么实现的 ?

Texture 框架通过几个关键机制将绘制操作移到后台线程,并确保这些操作最终在主线程上应用,以保持线程安全。以下是 Texture 实现异步绘制的具体步骤和原理:

1. 异步绘制任务分配

Texture 使用 ASDisplayNode 进行异步绘制任务的分配。每个 ASDisplayNode 都有一个 display 方法,该方法负责节点的绘制。Texture 会在后台线程中执行这个方法。

ASDisplayNode *node = [[ASDisplayNode alloc] init];
node.displayBlock = ^UIImage * {
    // 这是在后台线程中执行的绘制操作
    UIGraphicsBeginImageContextWithOptions(size, NO, 0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 在 context 上执行绘制操作
    // ...
    
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return image;
};
2. 使用异步绘制队列

Texture 维护了一个全局的异步绘制队列 ASDisplayQueue,它负责管理所有节点的异步绘制任务。每个绘制任务都会被放到这个队列中执行。

ASDisplayQueue *displayQueue = [ASDisplayQueue sharedDisplayQueue];
[displayQueue addDisplayBlock:^{
    // 执行节点的 displayBlock
    UIImage *image = node.displayBlock();
    
    // 在主线程上应用绘制结果
    dispatch_async(dispatch_get_main_queue(), ^{
        node.contents = (id)image.CGImage;
    });
}];
3. 双缓冲机制

为了确保主线程的 UI 更新是线程安全的,Texture 使用了双缓冲机制。在后台线程完成绘制操作后,绘制的结果(通常是 UIImage)会被缓冲,然后在主线程上应用到相应的 CALayer。

- (void)displayAsync:(BOOL)asynchronously {
    if (asynchronously) {
        dispatch_async(_asyncDisplayQueue, ^{
            UIImage *image = [self displayBlock]();
            dispatch_async(dispatch_get_main_queue(), ^{
                self.layer.contents = (id)image.CGImage;
            });
        });
    } else {
        UIImage *image = [self displayBlock]();
        self.layer.contents = (id)image.CGImage;
    }
}

4. 高效的图层绘制

Texture 使用 CATiledLayer 来处理大规模图形的异步绘制。CATiledLayer 会将图形分成若干小块,每块都可以在后台线程中独立绘制。这种方式不仅提高了绘制效率,还减少了内存占用。

CATiledLayer *tiledLayer = [CATiledLayer layer];
tiledLayer.tileSize = CGSizeMake(256, 256);
tiledLayer.levelsOfDetail = 4;
tiledLayer.levelsOfDetailBias = 3;

5. 异步图像加载

Texture 提供了 ASNetworkImageNode 来异步加载和显示网络图像。图像下载和处理在后台线程中进行,完成后在主线程上更新 UI。

ASNetworkImageNode *imageNode = [[ASNetworkImageNode alloc] init];
imageNode.URL = [NSURL URLWithString:@"https://example.com/image.jpg"];

小结

Texture 框架通过将绘制操作分配到后台线程执行,并使用双缓冲机制和 CATiledLayer 等技术,确保异步绘制的结果能够安全、高效地应用到主线程的 UI 上。这些技术使得 Texture 能够提供高性能的异步渲染和绘制能力,同时保持 UIKit 的线程安全原则。

具体用到的框架

Texture 框架主要使用 Core Graphics 和 Core Animation 来实现异步绘制操作。以下是它们在 Texture 中的具体应用方式:

Core Graphics

Core Graphics 是一个强大的二维图形绘制框架,Texture 使用 Core Graphics 来处理后台线程中的绘制操作。这包括生成图像、绘制文本、绘制图形等。Core Graphics 允许在后台线程中执行这些绘制操作,然后将生成的图像应用到 UI 上。

示例:异步绘制图像

ASDisplayNode *node = [[ASDisplayNode alloc] init];
node.displayBlock = ^UIImage * {
    // 在后台线程中执行绘制操作
    CGSize size = CGSizeMake(100, 100);
    UIGraphicsBeginImageContextWithOptions(size, NO, 0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 使用 Core Graphics 绘制内容
    CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
    CGContextFillRect(context, CGRectMake(0, 0, 100, 100));
    
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return image;
};

在这个例子中,displayBlock 在后台线程中执行,使用 Core Graphics 在 UIGraphicsGetCurrentContext() 上进行绘制操作。生成的 UIImage 最终会在主线程上应用到节点的内容中。

Core Animation

Core Animation 是一个强大的动画和图层绘制框架,Texture 使用 Core Animation 来管理和优化图层的显示,特别是在处理大规模图形和复杂动画时。CATiledLayer 是 Core Animation 中一个重要的类,Texture 使用它来实现异步绘制大规模图形。

示例:使用 CATiledLayer 实现异步绘制

class AsyncDrawingView: UIView {
    override class var layerClass: AnyClass {
        return CATiledLayer.self
    }

    override func draw(_ rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()
        // 在此处执行绘制操作
        CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor);
        CGContextFillRect(context, rect);
    }
}

let tiledLayer = AsyncDrawingView.layer as! CATiledLayer
tiledLayer.levelsOfDetail = 4
tiledLayer.levelsOfDetailBias = 3
tiledLayer.tileSize = CGSize(width: 256, height: 256)

在这个例子中,CATiledLayer 将视图的内容分成多个小块,并在后台线程中独立绘制这些小块。这种方式不仅提高了绘制效率,还减少了内存占用。

Texture 的异步绘制流程

定义节点和绘制块:

每个 ASDisplayNode 可以定义一个 displayBlock,该块在后台线程中执行,使用 Core Graphics 进行绘制操作。

后台线程绘制:

Texture 使用 ASDisplayQueue 将绘制任务分配到后台线程执行。在这些任务中,使用 Core Graphics 进行实际的绘制操作。

主线程应用绘制结果:

在后台线程完成绘制操作后,生成的 UIImage 或其他内容会被传递到主线程,并应用到节点的 CALayer 中。

使用 CATiledLayer 优化大规模绘制:

对于需要处理大规模图形的场景,Texture 使用 CATiledLayer 将绘制任务分成小块,并在后台线程中并行绘制这些小块。

小结

Texture 框架在实现异步绘制时,主要使用 Core Graphics 来处理后台线程中的绘制操作,并结合 Core Animation(特别是 CATiledLayer)来优化图层显示和管理。这种结合使得 Texture 能够提供高性能的异步渲染和绘制能力,同时保持 UIKit 的线程安全原则。