Irbisa · Шпаргалка
Производительность iOS и память
ARC, retain cycles, Instruments, главный поток, оптимизация изображений и таблиц
Как работает ARC в Swift и чем он отличается от сборщика мусора?
final class Session { let id: String init(id: String) { self.id = id } deinit { print("\(id) freed") }}…Чем стек отличается от кучи и какие значения Swift где хранятся?
Что такое retain cycle, откуда он берётся в iOS-коде и как его разорвать?
@MainActorprotocol ProfileViewDelegate: AnyObject { // AnyObject is what makes weak legal func profileDidSave()} final class ProfileView: UIView {…Какие из четырёх замыканий в этом view controller создают retain cycle, а какие только откладывают освобождение?
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() {}}Найдите баг: Memory Graph показывает на один живой
OTPViewControllerбольше после каждого открытия и закрытия экрана ввода кода.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}Что такое утечка памяти в iOS-приложении и почему инструмент Leaks пропускает большинство утечек?
Как iOS-приложение должно реагировать на memory warning и почему
NSCache, а неDictionary?// 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() {…Пока работает этот цикл экспорта, память растёт до 1,5 ГБ и падает сразу после его завершения. Почему и как это исправить?
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)) }}Почему большинство проблем с памятью в iOS-приложениях связано с изображениями и как их даунсемплить?
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…Пользователь закрывает этот экран, запросы продолжают уходить, а
deinitтак и не печатается. Что делает этотTask?@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}Как найти проблему производительности с помощью Instruments, а не угадывать?
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] {…Чем hang отличается от hitch и как сохранить отзывчивость главного потока?
// 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())) ?? []}…Как добиться плавного скролла в
UITableViewилиUICollectionView?final class PhotoCell: UICollectionViewCell { let imageView = UIImageView() private var loadTask: Task<Void, Never>? func configure(url: URL, loader: ImageLoader) { loadTask = Task { [weak self] in…Что такое offscreen rendering, какие свойства слоя его вызывают и как его найти?
// 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…Пользователи жалуются на крэши, которые у вас не воспроизводятся. Как дойти от крэш-репорта до строки кода?
# 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 \…Что означает
EXC_BAD_ACCESSи какие инструменты Xcode помогают найти причину?В крэш-репорте код исключения
0x8badf00dи ни одного кадра вашего кода. Что убило приложение и что обычно к этому приводит?// 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() }…Как отслеживать производительность на устройствах реальных пользователей и не выпустить регрессию в релиз?
import XCTest final class LaunchPerformanceTests: XCTestCase { func testColdLaunch() { measure(metrics: [XCTApplicationLaunchMetric(), XCTMemoryMetric()]) { XCUIApplication().launch()…Как Core Animation превращает изменения UI в кадр и почему спиннер продолжает крутиться, когда главный поток завис?
Индикатор памяти Xcode показывает 480 МБ, Allocations показывает 90 МБ, а приложение всё равно убивают из-за памяти. Какая цифра настоящая?
// 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) {…Как измерить и сократить время запуска приложения?
import OSLog let launchSignposter = OSSignposter(subsystem: "com.example.app", category: "launch") @mainstruct MyApp: App {…Тестировщики говорят, что приложение сажает батарею. Как найти причину и что на самом деле менять?
Размер загрузки приложения растёт с каждым релизом. Как найти, что занимает место, и сократить его?
Что такое thread explosion и как Swift Concurrency его избегает, при условии, что вы не блокируете задачу?
// 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) }…