Irbisa · Cheatsheet
iOS Performance & Memory
ARC, retain cycles, Instruments, main thread, image and table optimization
How does ARC work in Swift, and how is it different from a garbage collector?
final class Session { let id: String init(id: String) { self.id = id } deinit { print("\(id) freed") }}…What is the difference between the stack and the heap, and which Swift values live where?
What is a retain cycle, where does it come from in iOS code, and how do you break it?
@MainActorprotocol ProfileViewDelegate: AnyObject { // AnyObject is what makes weak legal func profileDidSave()} final class ProfileView: UIView {…Which of the four closures in this view controller create a retain cycle, and which ones only delay deallocation?
final class ProfileViewController: UIViewController { var onSave: (() -> Void)? private let saveButton = UIButton(type: .system) override func viewDidLoad() { super.viewDidLoad() view.addSubview(saveButton) UIView.animate(withDuration: 0.3) { self.view.alpha = 1 } // A URLSession.shared.dataTask(with: URL(string: "https://example.com/feed")!) { _, _, _ in self.reload() }.resume() // B onSave = { self.reload() } // C saveButton.addAction(UIAction { _ in self.reload() }, for: .touchUpInside) // D } func reload() {}}Find the bug: the Memory Graph shows one more
OTPViewControlleralive every time the user opens and closes the code screen.final class OTPViewController: UIViewController { private var timer: Timer? private var secondsLeft = 60 override func viewDidLoad() { super.viewDidLoad() timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(tick), userInfo: nil, repeats: true) } @objc private func tick() { secondsLeft -= 1 title = "Resend the code in \(secondsLeft) s" } deinit { timer?.invalidate() } // the timer must not outlive the screen}What is a memory leak in an iOS app, and why does the Leaks instrument miss most of them?
How should an iOS app react to a memory warning, and why use
NSCacheinstead of aDictionary?// NSCache is thread-safe, so sharing this wrapper across threads is finefinal class ThumbnailCache: @unchecked Sendable { private let cache = NSCache<NSURL, UIImage>() private var observer: NSObjectProtocol? init() {…Memory climbs to 1.5 GB while this export loop runs and drops the moment it returns. Why, and how do you fix it?
func exportThumbnails(_ urls: [URL], to folder: URL) throws { for url in urls { // 3 000 photos guard let image = UIImage(contentsOfFile: url.path), let thumb = image.preparingThumbnail(of: CGSize(width: 512, height: 512)), let data = thumb.jpegData(compressionQuality: 0.8) else { continue } try data.write(to: folder.appendingPathComponent(url.lastPathComponent)) }}Why do images cause most memory problems in iOS apps, and how do you downsample them?
func thumbnail(at url: URL, pointSize: CGSize, scale: CGFloat) -> UIImage? { // Do not cache the full-size image while reading the file let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions) else { return nil } let maxPixels = max(pointSize.width, pointSize.height) * scale…The user pops this screen, requests keep firing and
deinitnever prints. What is thisTaskdoing?@MainActorfinal class FeedModel { var posts: [Post] = [] init(feed: FeedService) { Task { for await update in feed.updates { // a stream that never finishes self.posts = update } } } deinit { print("FeedModel freed") } // never prints}How do you find a performance problem with Instruments instead of guessing?
import OSLog let signposter = OSSignposter(subsystem: "com.example.app", category: "feed")let log = Logger(subsystem: "com.example.app", category: "feed") func loadFeed() async throws -> [Post] {…What is the difference between a hang and a hitch, and how do you keep the main thread responsive?
// wrong: synchronous I/O and decoding on the main thread@MainActorfunc loadFeedBadly() { let data = try? Data(contentsOf: feedURL) feed = (try? JSONDecoder().decode([Post].self, from: data ?? Data())) ?? []}…How do you make a
UITableVieworUICollectionViewscroll smoothly?final class PhotoCell: UICollectionViewCell { let imageView = UIImageView() private var loadTask: Task<Void, Never>? func configure(url: URL, loader: ImageLoader) { loadTask = Task { [weak self] in…What is offscreen rendering, which layer properties trigger it, and how do you find it?
// Expensive: an offscreen pass for the shadow and another for the clipped subtreefinal class BadCardCell: UICollectionViewCell { func style() { layer.shadowOpacity = 0.2 layer.shadowRadius = 12 // no shadowPath -> GPU renders first to find the shape contentView.layer.cornerRadius = 16…Users report crashes you cannot reproduce. How do you get from a crash report to the line of code?
# Which build is this? Must match the UUID in the report's Binary Imagesdwarfdump --uuid MyApp.app.dSYM # Resolve one frame by hand: -l is the app's load address from Binary Imagesxcrun atos -arch arm64 \ -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp \…What does
EXC_BAD_ACCESSmean, and which Xcode tools help you find its cause?A crash report shows exception code
0x8badf00dand no frame of your code. What killed the app, and what usually causes it?// wrong: launch parks the main thread until the server answersfunc applicationWrong(_ app: UIApplication, didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let semaphore = DispatchSemaphore(value: 0) let box = ConfigBox() // a reference box, so the closure can fill it RemoteConfig.fetch { box.value = $0; semaphore.signal() }…How do you track performance on real users' devices and stop a regression before it ships?
import XCTest final class LaunchPerformanceTests: XCTestCase { func testColdLaunch() { measure(metrics: [XCTApplicationLaunchMetric(), XCTMemoryMetric()]) { XCUIApplication().launch()…How does Core Animation turn your UI changes into a frame, and why does a spinner keep spinning while the main thread is frozen?
Xcode's memory gauge shows 480 MB, Allocations shows 90 MB, and the app still gets killed for memory. Which number is real?
// The number the memory limit is checked against, read from inside the appfunc physFootprint() -> UInt64 { var info = task_vm_info_data_t() var count = mach_msg_type_number_t( MemoryLayout<task_vm_info_data_t>.size / MemoryLayout<natural_t>.size) let status = withUnsafeMutablePointer(to: &info) {…How do you measure and reduce app launch time?
import OSLog let launchSignposter = OSSignposter(subsystem: "com.example.app", category: "launch") @mainstruct MyApp: App {…QA says the app drains the battery. How do you find the cause, and what do you actually change?
The app's download size grows with every release. How do you find what takes the space and cut it?
What is thread explosion, and how does Swift Concurrency avoid it as long as you never block a task?
// Thread explosion: hundreds of threads, most of them blockedfor url in urls { DispatchQueue.global().async { let data = try? Data(contentsOf: url) // blocks a thread for the whole transfer handle(data) }…