在 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)
区别与联系
-
目的和应用场景:
- 异步渲染主要用于将数据处理、图片加载等操作放在后台线程执行,然后在主线程更新 UI,以提高界面的响应速度。
- 异步绘制主要用于在后台线程执行实际的图形绘制操作,以提高图形绘制的效率和性能。
-
实现方式:
- 异步渲染通常使用 GCD 或 OperationQueue 进行后台线程的任务分发。
- 异步绘制 通常使用 Core Graphics 或 Core Animation 等图形框架,并利用专门的图层(如
CATiledLayer
)来进行后台绘制。
总结来说,异步渲染和异步绘制在概念和实现上有一些交叉,但它们的侧重点和应用场景有所不同。异步渲染更多地涉及数据处理和界面更新,而异步绘制则专注于高效的图形绘制操作。
拓展
facebook Texture 的实现原理是基于异步渲染还是异步绘制 ?还是两个都有用到 ?
Facebook 的 Texture(以前称为 AsyncDisplayKit)框架主要基于异步绘制(Asynchronous Drawing)来提高用户界面的性能和响应速度,同时也涉及异步渲染(Asynchronous Rendering)的一些方面。Texture 通过将耗时的界面绘制和布局操作放在后台线程中执行,来避免阻塞主线程,从而提供流畅的用户体验。
Texture 实现原理
异步绘制(Asynchronous Drawing)
Texture 的核心功能之一是异步绘制。它通过在后台线程进行实际的图形绘制操作,然后将绘制结果合成到主线程上显示。这一过程主要通过以下机制实现:
-
ASDisplayNode:
ASDisplayNode
是 Texture 的基本单元,类似于UIView
,但它支持异步绘制。ASDisplayNode
在后台线程执行drawRect:
方法,通过 Core Graphics 进行绘制,并生成一个包含绘制内容的图像。
-
异步绘制队列:
- Texture 使用专门的绘制队列来处理
ASDisplayNode
的绘制任务。绘制任务被分配到后台线程中执行,确保不会阻塞主线程。
- Texture 使用专门的绘制队列来处理
-
绘制合成:
- 绘制完成后,生成的图像被合成到主线程的视图层次结构中,确保界面更新的流畅性。
异步渲染(Asynchronous Rendering)
除了异步绘制,Texture 也利用了异步渲染的概念,将一些耗时的布局和数据处理操作放在后台线程中执行,然后在主线程更新 UI。
-
异步布局:
ASLayoutSpec
允许节点在后台线程进行布局计算。布局计算完成后,结果会被应用到主线程的界面中。
-
异步数据加载:
- 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 如何实现异步渲染和布局
-
异步计算布局 :
Texture 将布局计算移到后台线程。这些计算包括尺寸和位置的计算,而不是直接操作 UI。这意味着实际的 UI 更新还是在主线程上进行的,但计算工作已经在后台完成,从而减轻了主线程的负担。
objective-c// Example of layout calculation in background thread [node measureWithSizeRange:ASSizeRangeMake(minSize, maxSize)];
-
异步绘制内容 :
Texture 将绘制操作也移到了后台线程。具体来说,绘制操作会生成一个图像或其他内容,然后这些内容会在主线程上应用到 UI 组件中。这样,虽然绘制在后台完成,但实际的 UI 更新还是在主线程上进行的。
objective-cself.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 操作在主线程上进行,同时最大限度地利用了后台线程的计算能力。
objective-c[ASDisplayNode performBlockOnMainThread:^{ // Apply the result of the background rendering to the UI [self.layer setContents:(__bridge id _Nullable)(image.CGImage)]; }];
-
节点(Node)系统 :
Texture 的核心概念是节点(Node)系统,每个节点都是一个独立的实体,负责其自己的布局和渲染。节点的布局计算和绘制操作可以在后台线程中进行,而最终的 UI 更新还是在主线程上完成。
objective-cASDisplayNode *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 的异步绘制流程
-
定义节点和绘制块 :
每个
ASDisplayNode
可以定义一个displayBlock
,该块在后台线程中执行,使用 Core Graphics 进行绘制操作。 -
后台线程绘制 :
Texture 使用
ASDisplayQueue
将绘制任务分配到后台线程执行。在这些任务中,使用 Core Graphics 进行实际的绘制操作。 -
主线程应用绘制结果 :
在后台线程完成绘制操作后,生成的
UIImage
或其他内容会被传递到主线程,并应用到节点的CALayer
中。 -
使用
CATiledLayer
优化大规模绘制 :对于需要处理大规模图形的场景,Texture 使用
CATiledLayer
将绘制任务分成小块,并在后台线程中并行绘制这些小块。
小结
Texture 框架在实现异步绘制时,主要使用 Core Graphics 来处理后台线程中的绘制操作,并结合 Core Animation(特别是 CATiledLayer
)来优化图层显示和管理。这种结合使得 Texture 能够提供高性能的异步渲染和绘制能力,同时保持 UIKit 的线程安全原则。