iOS 开发中,异步渲染和异步绘制

发布于:2024-07-11 ⋅ 阅读:(21) ⋅ 点赞:(0)

在 iOS 开发中,异步渲染(Asynchronous Rendering)和异步绘制(Asynchronous Drawing)虽然有相似之处,但它们并不是完全相同的概念。

异步渲染(Asynchronous Rendering)

异步渲染主要指在后台线程进行与界面显示相关的耗时操作,比如图片加载、文本排版、数据处理等,然后在主线程更新 UI。这种做法的主要目的是避免阻塞主线程,以确保用户界面的流畅性和响应速度。

示例:

DispatchQueue.global(qos: .background).async {
    let processedImage = processImage(image)
    DispatchQueue.main.async {
        imageView.image = processedImage
    }
}

异步绘制(Asynchronous Drawing)

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

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

示例:

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

    override func draw(_ rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()
        // 在此处执行绘制操作
    }
}

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

区别与联系

  1. 目的和应用场景

    • 异步渲染主要用于将数据处理、图片加载等操作放在后台线程执行,然后在主线程更新 UI,以提高界面的响应速度。
    • 异步绘制主要用于在后台线程执行实际的图形绘制操作,以提高图形绘制的效率和性能。
  2. 实现方式

    • 异步渲染通常使用 GCD 或 OperationQueue 进行后台线程的任务分发。
    • 异步绘制通常使用 Core Graphics 或 Core Animation 等图形框架,并利用专门的图层(如 CATiledLayer)来进行后台绘制。

总结来说,异步渲染和异步绘制在概念和实现上有一些交叉,但它们的侧重点和应用场景有所不同。异步渲染更多地涉及数据处理和界面更新,而异步绘制则专注于高效的图形绘制操作。


拓展

facebook Texture 的实现原理是基于异步渲染还是异步绘制 ?还是两个都有用到 ?

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

Texture 实现原理

异步绘制(Asynchronous Drawing)

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

  1. ASDisplayNode

    • ASDisplayNode 是 Texture 的基本单元,类似于 UIView,但它支持异步绘制。
    • ASDisplayNode 在后台线程执行 drawRect: 方法,通过 Core Graphics 进行绘制,并生成一个包含绘制内容的图像。
  2. 异步绘制队列

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

    • 绘制完成后,生成的图像被合成到主线程的视图层次结构中,确保界面更新的流畅性。
异步渲染(Asynchronous Rendering)

除了异步绘制,Texture 也利用了异步渲染的概念,将一些耗时的布局和数据处理操作放在后台线程中执行,然后在主线程更新 UI。

  1. 异步布局

    • ASLayoutSpec 允许节点在后台线程进行布局计算。布局计算完成后,结果会被应用到主线程的界面中。
  2. 异步数据加载

    • Texture 支持在后台线程加载数据,如图片和文本内容,然后将结果异步应用到 UI 组件上。

结合使用

Texture 的强大之处在于它同时结合了异步绘制和异步渲染,通过将界面绘制、布局和数据加载等操作放在后台线程中执行,大大提高了界面的响应速度和流畅性。

以下是一个使用 Texture 的示例:

import AsyncDisplayKit

class AsyncImageNode: ASDisplayNode {
    let imageNode = ASImageNode()

    override init() {
        super.init()
        addSubnode(imageNode)
    }

    override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
        return ASInsetLayoutSpec(insets: .zero, child: imageNode)
    }

    func setImage(_ image: UIImage?) {
        imageNode.image = image
    }
}

let asyncImageNode = AsyncImageNode()
DispatchQueue.global().async {
    let image = loadImageFromNetwork()
    DispatchQueue.main.async {
        asyncImageNode.setImage(image)
    }
}

总结来说,Facebook 的 Texture 框架通过结合异步绘制和异步渲染,提供了强大的性能优化能力,使得复杂的界面操作可以在后台线程中高效执行,从而保证主线程的流畅性和用户体验。


拓展 - 主线程操作UI的疑问

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

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

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

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

    // Example of layout calculation in background thread
    [node measureWithSizeRange:ASSizeRangeMake(minSize, maxSize)];
    
  2. 异步绘制内容
    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;
    };
    
  3. 双缓冲技术(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)];
    }];
    
  4. 节点(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 的异步绘制操作

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 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 的异步绘制流程

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

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

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

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

小结

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