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

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

异步渲染(Asynchronous Rendering)

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

示例:

swift 复制代码
DispatchQueue.global(qos: .background).async {
    let processedImage = processImage(image)
    DispatchQueue.main.async {
        imageView.image = processedImage
    }
}

异步绘制(Asynchronous Drawing)

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

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

示例:

swift 复制代码
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 的示例:

swift 复制代码
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 更新还是在主线程上进行的,但计算工作已经在后台完成,从而减轻了主线程的负担。

    objective-c 复制代码
    // Example of layout calculation in background thread
    [node measureWithSizeRange:ASSizeRangeMake(minSize, maxSize)];
  2. 异步绘制内容 :

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

    objective-c 复制代码
    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 操作在主线程上进行,同时最大限度地利用了后台线程的计算能力。

    objective-c 复制代码
    [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 更新还是在主线程上完成。

    objective-c 复制代码
    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 会在后台线程中执行这个方法。

objective-c 复制代码
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,它负责管理所有节点的异步绘制任务。每个绘制任务都会被放到这个队列中执行。

objective-c 复制代码
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。

objective-c 复制代码
- (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 会将图形分成若干小块,每块都可以在后台线程中独立绘制。这种方式不仅提高了绘制效率,还减少了内存占用。

objective-c 复制代码
CATiledLayer *tiledLayer = [CATiledLayer layer];
tiledLayer.tileSize = CGSizeMake(256, 256);
tiledLayer.levelsOfDetail = 4;
tiledLayer.levelsOfDetailBias = 3;

5. 异步图像加载

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

objective-c 复制代码
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 上。

示例:异步绘制图像
objective-c 复制代码
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 实现异步绘制
objective-c 复制代码
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 的线程安全原则。

相关推荐
传奇开心果编程1 天前
【SwiftUI入门练中学】第7课 动画与手势:让界面更灵动
学习·macos·ui·ios·swiftui·swift
知潮网1 天前
发售刚十天,9999 元的 iPhone 18 Pro 解锁就重启
ios·iphone
2501_916007472 天前
苹果应用商店App Store上架费用标准及原因解析
android·ios·小程序·https·uni-app·iphone·webview
传奇开心果编程2 天前
【SwiftUI娓娓道来】第3课:让界面活起来——交互与动画指南
学习·macos·ui·ios·swiftui·swift
可乐鸡翅yeah_2 天前
新手梳理:M3U8 线上问题,哪些是前端锅,哪些是后端锅
前端·ios·音视频·实时音视频·m3u8·音视频在线播放
00后程序员张2 天前
苹果App Store上架指南:费用、原理与步骤详解
android·ios·小程序·https·uni-app·iphone·webview
黑科技iOS上架2 天前
Unity3D游戏过审App Store4.3机审实战
ios·审核·ios混淆·执行程序差异化
用户38034165882972 天前
控制中心一键后台录音:我被自己的 Spike 骗了两次
ios·swift
Crazy_MT2 天前
App 可能不再是入口:从 iOS 27 快捷指令看苹果的 AI Agent 野心
人工智能·ios·apple
Maple9962 天前
Firebase iOS SDK 重大事故:一个 nil key 如何让全球 App 集体崩溃
ios