Hello!
I’m experiencing a crash in my iOS/iPadOS app related to a CALayer
rendering process. The crash occurs when attempting to render a UIImage
on a background thread. The crashes are occurring in our production app, and while we can monitor them through Crashlytics, we are unable to reproduce the issue in our development environment.
Relevant Code
I have a custom view controller that handles rendering CALayer
s onto images. This method creates a CALayer
on the main thread and then starts a detached task to render this CALayer
into a UIImage
. The whole idea is learnt from this StackOverflow post: https://stackoverflow.com/a/77834613/9202699
Here are key parts of my implementation:
class MyViewController: UIViewController {
@MainActor
func renderToUIImage(size: CGSize, itemsToDraw: [MyDrawingItem], transform: CGAffineTransform) async -> UIImage? {
// Create CALayer and add it to the view.
CATransaction.begin()
let customLayer = MyDrawingLayer()
customLayer.setupContent(itemsToDraw: itemsToDraw)
// Position the frame off-screen to it hidden.
customLayer.frame = CGRect(
origin: CGPoint(x: -100 - size.width, y: -100 - size.height),
size: size)
customLayer.masksToBounds = true
customLayer.drawsAsynchronously = true
view.layer.addSublayer(customLayer)
CATransaction.commit()
// Render CALayer to UIImage in background thread.
let image = await Task.detached {
customLayer.setNeedsDisplay()
let renderer = UIGraphicsImageRenderer(size: size)
let image = renderer.image { // CRASH happens on this line
let cgContext = $0.cgContext
cgContext.saveGState()
cgContext.concatenate(transform)
customLayer.render(in: cgContext)
cgContext.restoreGState()
}
return image
}.value
// Remove the CALayer from the view.
CATransaction.begin()
customLayer.removeFromSuperlayer()
CATransaction.commit()
return image
}
}
class MyDrawingLayer: CALayer {
var itemsToDraw: [MyDrawingItem] = []
func setupContent(itemsToDraw: [MyDrawingItem]) {
self.itemsToDraw = itemsToDraw
}
override func draw(in ctx: CGContext) {
for item in itemsToDraw {
// Render the item to the context (example pseudo-code).
// All items are thread-safe to use.
// Things to draw may include CGPath, CGImages, UIImages, NSAttributedString, etc.
item.draw(in: ctx)
}
}
}
Crash Log
The crash occurs at the following location:
Crashed: com.apple.root.default-qos.cooperative
0 MyApp 0x5cb300 closure #1 in closure #1 in MyViewController.renderToUIImage(size: CGSize, itemsToDraw: [MyDrawingItem], transform: CGAffineTransform) + 4313002752 (<compiler-generated>:4313002752)
1 MyApp 0x5cb300 closure #1 in closure #1 in MyViewController.renderToUIImage(size: CGSize, itemsToDraw: [MyDrawingItem], transform: CGAffineTransform) + 4313002752 (<compiler-generated>:4313002752)
2 MyApp 0x1a4578 AnyModifier.modified(for:) + 4308649336 (<compiler-generated>:4308649336)
3 MyApp 0x7b4e64 thunk for @escaping @callee_guaranteed (@guaranteed UIGraphicsPDFRendererContext) -> () + 4315008612 (<compiler-generated>:4315008612)
4 UIKitCore 0x1489c0 -[UIGraphicsRenderer runDrawingActions:completionActions:format:error:] + 324
5 UIKitCore 0x14884c -[UIGraphicsRenderer runDrawingActions:completionActions:error:] + 92
6 UIKitCore 0x148778 -[UIGraphicsImageRenderer imageWithActions:] + 184
7 MyApp 0x5cb1c0 closure #1 in MyViewController.renderToUIImage(size: CGSize, itemsToDraw: [MyDrawingItem], transform: CGAffineTransform) + 100 (FileName.swift:100)
8 libswift_Concurrency.dylib 0x60f5c swift::runJobInEstablishedExecutorContext(swift::Job*) + 252
9 libswift_Concurrency.dylib 0x62514 swift_job_runImpl(swift::Job*, swift::SerialExecutorRef) + 144
10 libdispatch.dylib 0x15ec0 _dispatch_root_queue_drain + 392
11 libdispatch.dylib 0x166c4 _dispatch_worker_thread2 + 156
12 libsystem_pthread.dylib 0x3644 _pthread_wqthread + 228
13 libsystem_pthread.dylib 0x1474 start_wqthread + 8
Questions
-
Is it safe to run
UIGraphicsImageRenderer.image
on the background thread? -
Given that I want to leverage GPU rendering, what are some best practices for rendering images off the main thread while ensuring stability?
-
Are there alternatives to using
UIGraphicsImageRenderer
for background rendering that can still take advantage of GPU rendering? -
It is particularly interesting that the crash logs indicate the error may be related to
UIGraphicsPDFRendererContext
(crash log line number 3). It would be very helpful if someone could explain the connection between starting and drawing on aUIGraphicsImageRenderer
andUIGraphicsPDFRendererContext
.
Any insights or guidance on this issue would be greatly appreciated. Thanks!!!