iOS Drawing Performance: Why Core Graphics Slows to a Crawl and How to Fix It
A drawing app that feels smooth for three strokes and janky at thirty is a memory and threading problem, not a rendering problem. The techniques here—hardware layers, dirty-rect clipping, and asynchronous drawing—apply to any interactive iOS view where content accumulates, from signature panels to annotation tools.
A full-screen software redraw on a Retina iPad allocates 12 MB of memory (2048×1536×4 bytes) and blocks the main thread. That cost repeats every time a view calls `drawRect:`, making interactive drawing applications degrade linearly as content accumulates. The core fix is to avoid Core Graphics entirely when possible: `CAShapeLayer`, `CATextLayer`, and `CAGradientLayer` offload vector drawing to the GPU and eliminate the backing store overhead.
When Core Graphics is unavoidable—such as stamping a chalk-texture brush along a touch path—the bottleneck shifts to overdraw. Marking only the affected rectangle with `setNeedsDisplayInRect:` and intersecting it against the dirty rect inside `drawRect:` prevents the entire stroke history from being re-rendered on every frame. The technique turns a linear slowdown into near-constant performance.
For cases where drawing still stalls the UI, iOS offers two asynchronous paths. `CATiledLayer` splits work across background threads per tile, while the `drawsAsynchronously` property (iOS 6+) defers Core Graphics commands to a background queue even though the delegate method runs on the main thread. Both keep the app responsive during heavy redraws, though `drawsAsynchronously` benefits most from frequently-updated views like table cells.
The 12 MB figure for a single full-screen software redraw explains why UIKit aggressively caches backing stores and why Apple steers developers toward GPU-backed layers—the memory pressure alone makes `drawRect:` a last resort.
Switching from `UIBezierPath` to `CAShapeLayer` is not just a performance win; it changes the programming model from imperative redraw-on-dirty to declarative path assignment, which sidesteps the overdraw problem entirely.
Dirty-rectangle clipping is a manual optimization that Core Animation cannot infer from custom drawing code, making it a rare case where the developer must explicitly tell the framework what changed.
The distinction between `CATiledLayer`'s true multi-threaded drawing and `drawsAsynchronously`'s deferred command queue matters: the former parallelizes work, while the latter only moves execution off the main thread without splitting the workload.
The discussion questions the article's relevance, dismissing its content as outdated material copied from an older source.
This screenshot of the simulator and Xcode version looks like it was copied from somewhere. A relic of a bygone era.
Why does this feel like an archaeological article?