A view is updated from a background queue and crashes intermittently — why, and how do you catch it in CI?
This image loader crashes now and then in production but rarely in the debugger. Find the cause, fix the code, and say how CI would catch the class of bug automatically.
Constraints:
- The image must still be decoded off the main thread.
- Only the UI update belongs on the main thread.
URLSession.shared.dataTask(with: url) { data, _, _ in
let image = UIImage(data: data!)
self.imageView.image = image // running on a background queue
}.resume()
Find and fix the bug.
UIKit isn't thread-safe — its views assume the main thread. Touching imageView off-main corrupts state, so it crashes only sometimes. Fix by hopping to DispatchQueue.main.async. In CI, the Main Thread Checker fails the run.
- ✗Believing UIKit is thread-safe and any queue may touch views
- ✗Blaming a force-unwrap instead of the off-main UI access
- ✗Using
main.syncwheremain.asyncis the correct hop
- →Why does off-main UIKit access crash only intermittently, not always?
- →What does the Main Thread Checker actually instrument to detect this?
The dataTask closure runs on URLSession's background queue, and imageView.image = … touches UIKit off the main thread — hence the rare crashes.
URLSession.shared.dataTask(with: url) { data, _, _ in
guard let data, let image = UIImage(data: data) else { return }
DispatchQueue.main.async { // hop back to the main thread
self.imageView.image = image
}
}.resume()
Decoding with UIImage(data:) stays on the background; only the assignment hops to main. In CI, enable the Main Thread Checker (on by default in Xcode) — it instruments UIKit calls and fails the run on any off-main access.