iOS / Swift
Розробка під iOS — це створення застосунків для iPhone та iPad мовою Swift, компільованою мовою, що звільняє пам’ять підрахунком посилань, а не збирачем сміття. Цей один вибір пояснює найчастіший тихий баґ платформи — два об’єкти, які назавжди тримають один одного живими, — і більшість того, що нативна iOS-команда проєктує свідомо. Сторінка починається з шістнадцяти посилань, які тримає iOS-застосунок, і того, що звільняє кожне; далі — підрахунок посилань, SwiftUI проти UIKit і кому належить стан, паралельність у Swift до версії 6.4, рев’ю, TestFlight і семиденний поетапний реліз, дедлайни Apple на 2026 рік, п’ять задокументованих кейсів і те, де все це вироджується.
- Поточна версія
- Swift 6.4 · Xcode 27 · iOS 27, верес. 2026
- Пам’ять
- Підрахунок, а не збирання — без пауз і без пошуку циклів
- Реліз
- Кожна збірка — через рев’ю; 90% — за 24 години
Хто тримає цей об’єкт живим — шістнадцять посилань, які тримає iOS-застосунок
У Swift об’єкт — екземпляр класу — живе рівно доти, доки щось тримає на нього сильне посилання. Щойно зникає останнє, об’єкт звільняється на місці. Ніщо не приходить потім шукати об’єкти, до яких уже ніхто не може дістатися, як це робить збирач сміття (garbage collectorзвільняє пам’ять, яку ніхто не використовуєЧастина середовища виконання, що знаходить об’єкти, на які вже ніщо не посилається, і звільняє їхню пам’ять, тож програма ніколи не звільняє її вручну. Ціна — додаткова пам’ять і робота, яку програма не планує, — паузи, короткі чи довгі залежно від збирача.Докладніше в статті →Усі терміни) на Android чи в Java. Тож екран, який мав звільнитися й не звільнився, ніколи не є падінням. Це пам’ять, що росте; таймер, що далі спрацьовує для екрана, якого ніхто не бачить; мережевий колбек, що оновлює в’ю, якої вже немає. Отже, корисне питання до будь-якого посилання — не «чи це витік?». Воно таке: «хто кого тримає і що змушує їх відпустити?». Ось шістнадцять посилань, які iOS-застосунок тримає щодня. Наступні розділи посилаються на рядки за номерами.
| # | Посилання | Хто кого тримає | Що відбувається | Що робити |
|---|---|---|---|---|
| 1 | Локальна константа — let parser = Parser() усередині функції | Область видимості функції тримає об’єкт | Звільняється, коли функція повертає результат | Нічого |
| 2 | Структура, перелік, String чи Array | Значення копіюються, а не спільні; у значення немає власного лічильника посилань | Живе, доки живе змінна, що його тримає; copy-on-write робить копії дешевими | Нічого — саме тому більшість типів у Swift є значеннями |
| 3 | Замикання, що не виходить за межі виклику (non-escaping), — map, filter, sorted(by:) | Замикання існує лише під час виклику | Не може пережити виклик, тож не може утворити цикл | Нічого; [weak self] тут — шум |
| 4 | Замикання, що виходить за межі виклику, але його не зберігає self, — DispatchQueue.main.async, UIView.animate | Черга чи UIKit тримає замикання; замикання тримає self | Тримає self живим, доки не виконається, потім відпускає — затримка, а не витік | Нічого, якщо затримка не має значення |
| 5 | Task { await self.load() }, що завершується | Задача тримає self, доки не завершиться | Як у рядку 4: звільняється, коли робота закінчується | Скасовуйте її, коли екран зникає, щоб зупинилася й робота |
| 6 | Делегат, оголошений як var delegate: Delegate? | Дочірній об’єкт тримає власника, а власник — дочірній | Цикл: жоден ніколи не звільниться | weak var delegate і протокол лише для класів (AnyObject) |
| 7 | Замикання, збережене в self, яке використовує self, — onTap = { self.close() } | self → замикання → self | Цикл | [weak self] у списку захоплення |
| 8 | Дочірній об’єкт із сильною властивістю parent | батько → дитина → батько | Цикл | weak var parent |
| 9 | Combine sink, збережений у self.cancellables, який використовує self | self → множина → підписка → замикання → self | Цикл: підписка живе, доки живе self, і тримає self живим | [weak self] або assign(to: &$property), що не утримує |
| 10 | Блоковий спостерігач NotificationCenter, чий блок використовує self | Центр тримає токен, токен — блок, блок — self | self живе, доки спостерігача не прибрано | [weak self] у блоці — і прибрати токен |
| 11 | Збережена Task, що крутиться вічно — for await по сповіщеннях — і використовує self | self → задача → замикання → self | Цикл, що сам не закінчиться; скасувати його в deinit неможливо, бо deinit ніколи не виконається | Скасовуйте, коли екран зникає; у SwiftUI беріть .task, яку скасовують разом із в’ю |
| 12 | Timer.scheduledTimer(timeInterval: 1, target: self, …) | Цикл подій (run loop) тримає таймер; таймер тримає ціль | self живе до invalidate() — так і сказано в документації Apple | invalidate(), коли екран зникає; блоковий таймер із [weak self] |
| 13 | CADisplayLink(target: self, …) для покадрового малювання | Display link утримує ціль; цикл подій тримає display link | Як у рядку 12 | invalidate() або невеликий проміжний об’єкт зі слабким посиланням як ціль |
| 14 | URLSession(configuration:delegate:delegateQueue:) | Сесія тримає сильне посилання на свого делегата | Тече до завершення застосунку, якщо сесію не анулювати | finishTasksAndInvalidate() або одна довгоживуча сесія на весь застосунок |
| 15 | WKUserContentController.add(self, name:) — обробник повідомлень із JavaScript | Контролер вмісту вебв’ю тримає обробник сильно | Екран, якому належить вебв’ю, ніколи не звільняється | removeScriptMessageHandler(forName:), коли екран зникає, або проміжний об’єкт зі слабким посиланням |
| 16 | Синглтон чи статичний кеш, що тримає контролер в’ю, в’ю або замикання, яке їх захопило | Кеш живе, доки живе застосунок | Не цикл — об’єкт просто ніколи не стає недосяжним | Не тримайте екрани в довгоживучих об’єктах; зберігайте ідентифікатори й дані, а не в’ю |
Підрахунок посилань: що Swift робить замість збирача сміття
Automatic Reference Counting, ARC, легко сформулювати. Кожен екземпляр класу несе лічильник сильних посилань, що на нього вказують. Компілятор вставляє збільшення там, де посилання зберігають, і зменшення там, де воно зникає, тож самі ви їх ніколи не пишете. Коли лічильник доходить до нуля, виконується deinit і пам’ять повертається — негайно, у тому самому потоці виконання. Ця детермінованість — справжня перевага. Немає пауз на збирання; відпущене зображення звільняє пам’ять тієї миті, коли його відпускає останній екран; файл, закритий у deinit, закривається в передбачуваній точці. Ціна — саме те, про що таблиця: підрахунок не бачить циклу. Два об’єкти, що тримають один одного, назавжди тримають лічильники одне одного на одиниці.
Друга половина моделі — значення. Структура, перелік, кортеж — а також String, Array, Dictionary і Set, які в Swift є структурами, — копіюються під час присвоєння чи передачі, тож дві змінні ніколи не ділять одного значення й рахувати нічого. Стандартні колекції роблять це дешевим через copy-on-write: дві копії ділять один буфер, доки одну з них не змінять, і лише тоді буфер дублюється. Тому ідіоматичний Swift починається зі структури й тягнеться до класу лише тоді, коли важить ідентичність: один спільний об’єкт, за яким стежать кілька екранів, або ресурс із власним часом життя, як-от з’єднання чи плеєр. Рядки 1–3 таблиці прості саме тому, що більша частина програми на Swift — значення.
| Вид посилання | Володіє об’єктом? | Коли об’єкт зникає | Для чого |
|---|---|---|---|
| strong (типово) | Так | Він не може зникнути, доки існує це посилання | Майже для всього |
| weak | Ні | Посилання стає nil, тож воно завжди опціональна var | Делегати, зворотні посилання, замикання, збережені в self, — рядки 6–11 |
| unowned | Ні | Посилання лишається неопціональним; використання після цього зупиняє застосунок аварійно | Посилання, яке за побудовою не може пережити свою ціль |
| unowned(unsafe) | Ні | Нічого не перевіряється; читання після цього — undefined behaviour | Виміряні гарячі ділянки; у коді застосунку майже ніколи |
Знайти цикл — справа секунд, щойно почнеш шукати. Поставте print чи точку зупину в deinit екранів, які вас турбують: якщо він не спрацьовує, коли ви йдете з екрана, — щось досі цей екран тримає. Memory Graph Debugger у Xcode тоді намалює об’єкти, що його тримають, зі стрілками й усім іншим, і позначить витоки, які може довести. Шаблон Allocations в Instruments відповідає на питання обсягу: відкрийте й закрийте той самий екран десять разів, і кількість живих екземплярів його контролера в’ю має повернутися до початкової. Лічильник, що росте на одиницю за кожен візит, — це рядки 6–16 одним числом.
На iOS витоки важать більше, ніж підказує їхній розмір, бо система не вивантажує пам’ять застосунку на диск. Коли пам’яті бракує, вона завершує застосунки — спершу фонові, — і застосунок на передньому плані, що виріс понад власну межу, теж завершують. Це називається jetsam-подією. Для користувача це виглядає як падіння; розробникові ж воно не приходить звичайним звітом про падіння, а з’являється в лічильниках завершень через пам’ять в Organizer у Xcode і в MetricKit. Звичайна причина — зображення, з тієї самої причини, що на сторінці про React Native: декодоване зображення займає ширина × висота × 4 байти, тож фото на 12 мегапікселів займає 48 МБ, хоч яким маленьким був JPEG.
SwiftUI чи UIKit — і кому належить стан
На платформі два UIUser Interface — те, що бачить користувачУсе, що користувач бачить і чим керує: екрани, кнопки, поля, стани. UI відповідає за те, ЯК виглядає взаємодія; чи має вона сенс узагалі — це вже питання UX, і ці два поняття плутають постійно.Усі терміни-фреймворки, і iOS-розробник, що працює, читає обидва. UIKit, присутній від першого iPhone SDKSoftware Development Kit — готовий набір для платформиЗібраний набір бібліотек, інструментів і прикладів для роботи з чужою платформою. Відмінність від API в тому, що API лише описує правила, а SDK дає код, який уже вміє за цими правилами говорити.Усі терміни 2008 року, імперативний: ви створюєте в’ю, тримаєте на них посилання й змінюєте їх, коли приходять події. SwiftUI, що з’явився з iOS 13 у 2019 році, декларативний: ви описуєте, який вигляд має екран для заданого стану, а фреймворк сам вираховує, що змінити, — та сама модель, що й у React, до якої дійшли незалежно. Більшість нових екранів пишуть на SwiftUI. Більшість наявних застосунків — це UIKit, усередині якого росте SwiftUI, і обидва взаємодіють в обидва боки: UIHostingController кладе SwiftUI всередину UIKit, а UIViewRepresentable — UIKit-в’ю всередину SwiftUI.
Один факт пояснює майже кожну несподіванку SwiftUI: в’ю у SwiftUI — це структура. Це опис, дешевий у створенні й викинутий після використання, і SwiftUI створює нові його значення щоразу, коли знову виконується body батька, — десятки разів на завантаженому екрані. Тож стан не може жити в структурі. Обгортки властивостей на кшталт @State кажуть SwiftUI тримати сховище поза в’ю, прив’язаним до її ідентичності — місця в ієрархії, — і віддавати його кожному новому значенню структури. Коли ідентичність змінюється, стан іде разом із нею: в’ю, перенесена між гілками if або отримала новий .id, починає знову з початкового значення. Це те саме правило, що й ключі в React, і той самий клас баґів.
| Обгортка | Володіє даними? | З якої версії | Для чого |
|---|---|---|---|
@State | Так — SwiftUI тримає, доки триває ідентичність в’ю | iOS 13 | Значення, локальне для в’ю; з iOS 17 — також @Observable-модель, яку створює в’ю |
@Binding | Ні — читає й пише стан батька | iOS 13 | Дочірня в’ю, що редагує значення, яким не володіє |
@Observable-клас | Даними володіє клас; в’ю перерендерюється лише через властивості, які прочитав її body | iOS 17 | Моделі, спільні для кількох екранів |
@Bindable | Ні | iOS 17 | Прив’язки до @Observable-моделі, переданої ззовні |
@Environment | Ні — значення, покладене вище в ієрархії | iOS 13 | Залежності й налаштування, які читає багато в’ю |
@StateObject | Так — створюється раз на ідентичність | iOS 14 | ObservableObject, яким володіє в’ю, у коді до Observation |
@ObservedObject | Ні — лише стежить за об’єктом, який тримає хтось інший | iOS 13 | ObservableObject, переданий ззовні; ніколи не створений тут же |
Що дає ваша мінімальна версія iOS
SwiftUI постачається всередині операційної системи, а не всередині вашого застосунку, тож найстаріша підтримувана iOS вирішує, який SwiftUI у вас є. Можливості, доданої в iOS 17, на телефоні з iOS 16 просто немає, і коду, що нею користується, потрібна гілка if #available із запасним варіантом. Тому мінімальна версія — продуктове рішення з ціною з обох боків: занизька — і команда пише запасні варіанти для того, що фреймворк уже вміє; зависока — і частина користувачів не зможе встановити наступне оновлення. Нові версії iOS приймають швидко, тож більшість команд підтримують поточну версію й одну-дві попередні.
| Мінімальна iOS | Рік | Що отримав SwiftUI |
|---|---|---|
| iOS 13 | 2019 | Сам SwiftUI: в’ю, @State, @Binding, @ObservedObject |
| iOS 14 | 2020 | @StateObject, протокол App, ліниві стеки й сітки |
| iOS 15 | 2021 | .task — робота, прив’язана до часу життя в’ю, — AsyncImage, .refreshable |
| iOS 16 | 2022 | NavigationStack, Swift Charts, протокол Layout |
| iOS 17 | 2023 | Observation (@Observable, @Bindable), SwiftData |
| iOS 18 | 2024 | Власні контейнери, переходи з масштабуванням, сітчасті градієнти, тип Tab |
| iOS 26 | 2025 | Liquid Glass, WebView для SwiftUI, форматований текст у TextEditor |
| iOS 27 | 2026 | Перетягування для впорядкування в будь-якому контейнері, свайп-дії поза List, новий API документів |
SwiftUI
- Значно менше коду на той самий екран і живі прев’ю просто під час набору.
- Один фреймворк для iPhone, iPad, Mac, Watch, TV і Vision Pro.
- Стан має одного власника, а екран іде за ним — жодної в’ю, яку треба синхронізувати руками.
- Ваша мінімальна iOS вирішує, які можливості є; крайні випадки досі спускаються в UIKit.
UIKit
- Вісімнадцять років відповідей, бібліотек і поведінки, яку нікому не треба вгадувати.
- Повний контроль над розкладкою, переходами, введенням тексту й кожним крайнім випадком.
- Те, на чому написано більшість наявних застосунків, — тож читати його ви будете так чи інакше.
- Більше коду, більше стану, який синхронізують руками, і патерни делегатів та цілей, що стоять за рядками 6 і 12.
Ще одна різниця між ними проявилася 2025 року. Обидва малює операційна система, тож коли iOS 26 перемалювала свої елементи керування в стилі Liquid Glass, кожен застосунок на SwiftUI та UIKit, перезібраний з iOS 26 SDK, отримав новий вигляд — панелі вкладок, навігаційні панелі, перемикачі, alert-діалоги — без жодного рядка коду. Apple дозволила відмовитися на рік через ключ UIDesignRequiresCompatibility, і цей ключ ігнорується, щойно застосунок збирають з iOS 27 SDK. Сторінка про Flutter показує, що цей редизайн зробив із застосунками на SwiftUI, React Native і Flutter поруч; для нативного застосунку робота — це власні намальовані в’ю, які змінюються, лише коли їх змінюєте ви.
Паралельність у Swift: хто має право торкатися цього стану
Одне правило на цій платформі не змінюється ніколи: інтерфейсу торкаються лише з головного потоку. Головний потік до того ж має наглядача. Watchdog завершує застосунок, що не встиг вчасно намалювати перший кадр або перестав відповідати, і звіт про падіння несе код 0x8badf00d — «ate bad food». Власна документація Apple показує класичну причину: Data(contentsOf:) з адресою https виглядає як читання файлу, а тихо робить синхронний мережевий запит у тому потоці, який його викликав. В офісній мережі це мілісекунди. У потязі — достатньо довго для watchdog.
Відповідь Swift, додана у Swift 5.5 2021 року, має три частини. async і await дозволяють функції чекати на мережеву відповідь чи файл, не блокуючи свого потоку: на кожному await функція поступається, і потік робить іншу роботу. Актор — це тип, що захищає власний стан, пускаючи всередину по одній задачі, тож дві задачі не можуть одночасно змінювати той самий словник. @MainActor — актор, що означає головний потік, тож позначка ним на моделі в’ю дає компілятору, а не код-рев’ю, перевірити, що її методи виконуються саме там. Третя частина — Sendable: тип, який безпечно переходить від одного актора до іншого, бо він значення або сам охороняє свій стан.
Swift 6 у вересні 2024 року зробив перевірки помилками, а не попередженнями, у своєму новому мовному режимі: код, що міг дати двом потокам торкнутися того самого змінного стану без координації, перестає компілюватися. Гонка за даними (data raceдва потоки, одна змінна, без блокуванняДва потоки чи горутини одночасно звертаються до тієї самої памʼяті, принаймні один із них пише, і впорядкувати ці звернення нічим. Результат невизначений — хибне значення, «розірване» читання чи падіння набагато пізніше, — і, на відміну від стану гонки в логіці, його можна виключити механічно: детектором гонок або, у Rust, компілятором.Докладніше в статті →Усі терміни) стала помилкою компіляції — той самий хід, що Rust зробив із володінням. Перші міграції були важкими, бо більшість коду застосунків ніколи не казала, якому актору належить, і компілятор позначав це всюди. Swift 6.2 у вересні 2025 року змінив типові налаштування, а не правила. Модуль тепер може зробити все @MainActor, якщо не позначено інакше; async-функція, не прив’язана до актора, виконується на акторі того, хто її викликав, а не стрибає у фоновий потік; а @concurrent позначає функції, які мають піти. Xcode 26 увімкнув обидва налаштування для нових проєктів застосунків. Тож сучасна точка відліку — однопотокова: код виконується на головному акторі, а робота йде з нього свідомо.
| Версія | Вихід | Що змінилося для розробника застосунків |
|---|---|---|
| Swift 5.0 | берез. 2019 | Стабільний бінарний інтерфейс: середовище Swift постачається в iOS 12.2 і новіших, тож застосунки перестають його вкладати |
| Swift 5.5 | верес. 2021 | async/await, актори, структурована паралельність, Sendable |
| Swift 5.9 | верес. 2023 | Макроси; фреймворк Observation, що стоїть за @Observable |
| Swift 6.0 | верес. 2024 | Мовний режим Swift 6: гонки за даними — помилки компіляції; типізований throws |
| Swift 6.2 | верес. 2025 | Головний актор типово (за вибором, увімкнено в нових проєктах Xcode 26), @concurrent; InlineArray, Span |
| Swift 6.3 | берез. 2026 | Вийшов Swift SDK для Android; @c для взаємодії із C; селектори модулів (Module::name) |
| Swift 6.4 | верес. 2026 | await усередині defer; щити від скасування задач; Swift Build — типовий у менеджері пакетів; постачається з Xcode 27 |
Випуск: рев’ю, TestFlight і семиденне розгортання
Те, що дивує команди з вебу, — випуск тут не є деплоєм. Кожен бінарник проходить App Review, перш ніж хтось поза командою зможе його встановити. Apple каже, що в середньому 90% заявок переглядають менш ніж за 24 години і що понад 40% проблем, які зупиняють заявку, підпадають під пункт 2.1 правил, App Completeness: падіння, заглушки замість вмісту, демо-акаунт, яким рецензент не зміг увійти. Для критичного виправлення є прискорене рев’ю, яке Apple надає в кожному випадку окремо. А з червня 2020 року виправлення помилок для застосунку, що вже в магазині, не затримують через порушення правил, якщо питання не юридичне; розділ кейсів розповідає, звідки взялося це правило.
Обійти бінарник неможливо. Swift компілюється заздалегідь у машинний код, а угода Developer Program License Agreement дозволяє завантажуваний код, лише якщо він інтерпретований і не змінює основного призначення застосунку, — саме це дозволяє React Native надсилати оновлення «по повітрю» (over-the-airоновлення встановленого ПЗ віддаленоОновлення, яке доставляють мережею до ПЗ, уже встановленого на пристрої, без перевстановлення користувачем, — JavaScript-бандл для застосунку на React Native, прошивка для телефона чи дрона. На iOS і в Google Play так може приходити лише інтерпретований код; нативний потребує нової збірки й ревʼю магазину.Докладніше в статті →Усі терміни), а сторінка про React Native цитує цей пункт. Без релізу нативний застосунок може змінити лише те, що його збудовано читати: вміст, яким керує сервер, віддалену конфігурацію, feature flagперемикач навколо нового кодуПеремикач, що під час роботи вирішує, чи активний шматок коду, — тож код може потрапити в продакшен вимкненим і бути ввімкненим для обраної аудиторії пізніше. Він відділяє деплой від релізу й має ціну: кожен живий прапорець — це розгалуження в коді, яке має працювати в обох станах.Докладніше в статті →Усі терміни, що вмикає код, уже наявний у бінарнику. Усе інше чекає наступної збірки, і саме тому важить порядок кроків нижче.
TestFlight — крок перед магазином. До 100 внутрішніх тестувальників із доступом до App Store Connect команди отримують збірку, щойно вона пройде обробку. До 10 000 зовнішніх тестувальників приєднуються за запрошенням чи публічним посиланням; перша збірка для них проходить Beta App Review. Кожну збірку можна встановлювати 90 днів. Далі — сам реліз і найкорисніший перемикач в App Store Connect: поетапний реліз (staged rolloutоновлення для дедалі більшої частки користувачівВипуск оновлення спершу для невеликої частки користувачів — 1%, потім 5%, 20%, усі — з перевіркою частоти падінь і помилок перед кожним кроком, щоб поганий реліз дійшов до небагатьох, а не до всіх. Воно обмежує, до скількох людей дійде поганий реліз, а не те, чи дійде він узагалі: у магазинах застосунків зупинене розгортання не забирає оновлення в тих, хто його вже має.Усі терміни). Оновлення отримує 1% користувачів першого дня, далі 2%, 5%, 10%, 20%, 50% — і всі на сьомий день. Його можна поставити на паузу сумарно до 30 днів або відкрити всім одразу.
Календар Apple — вимоги, що діють близько до дати цієї сторінки
| З якої дати | Що вимагає | Що це означає в коді |
|---|---|---|
| 30 черв. 2022 | Застосунок, що дозволяє створити акаунт, дозволяє й видалити його в самому застосунку (пункт 5.1.1(v)) | Справжній шлях видалення — деактивації недостатньо — і відкликання токенів Sign in with Apple |
| 1 трав. 2024 | Схвалені причини для API з обов’язковою причиною, зокрема тих, що їх викликають сторонні SDK | Маніфест приватності, що пояснює, навіщо застосунок читає user defaults, позначки часу файлів, час завантаження системи, вільне місце чи активні клавіатури |
| 31 січ. 2026 | Відповіді на нові питання вікового рейтингу для кожного застосунку | Без них оновлення зупиняються на етапі подання |
| 28 квіт. 2026 | Завантаження, зібрані в Xcode 26 з iOS 26 SDK | Liquid Glass на кожному системному елементі; ключ відмови на рік ще діє |
| 9 верес. 2026 | Завантаження мають ціль iOS 13 або новішу | Мінімальна версія нижче iOS 13 відхиляється вже під час завантаження |
| Збірка в Xcode 27 (вийшов 14 верес. 2026) | Життєвий цикл на сценах (UIScene), інакше застосунок не запуститься; UIDesignRequiresCompatibility ігнорується | App delegate, що володіє вікном, має переїхати в scene delegate до того, як iOS 27 SDK стане обов’язковим, — Apple піднімала мінімальний SDK щоквітня |
| 2 груд. 2026 (заплановано) | CocoaPods trunk стає лише для читання | Наявні збірки працюють, але нових версій жодного pod не з’явиться; переводьте залежності на Swift Package Manager |
Яка iOS-кодова база перед вами
| Що ви бачите в коді | Що це означає |
|---|---|
AppDelegate із властивістю window і без SceneDelegate | Життєвий цикл до iOS 13; у збірці з iOS 27 SDK застосунок не запуститься |
ObservableObject, @Published і @StateObject усюди | Написано до Observation (iOS 17); працює, але кожна зміна перерендерює кожну в’ю, що стежить за об’єктом |
NavigationView | Застаріле з iOS 16; його замінює NavigationStack |
Обробники завершення й DispatchQueue.main.async у кожному мережевому виклику | Паралельність із часів до 2021 року, усередину якої перевірки компілятора на гонки не бачать |
SWIFT_VERSION = 5 з мінімальною перевіркою паралельності | Компілятор про гонки ще ніхто не питав; посилюйте перевірку по одному модулю |
Podfile | CocoaPods, чий центральний репозиторій перестає приймати нові версії 2 груд. 2026 |
UIDesignRequiresCompatibility зі значенням YES в Info.plist | Команда відклала Liquid Glass; з iOS 27 SDK ключ ігнорується |
Timer.scheduledTimer(… target: self …) без invalidate() поруч | Рядок 12 таблиці, що чекає, поки його знайдуть |
П’ять кейсів і те, чого в кожному бракувало
Нативна iOS-команда успадковує дві речі, яких не контролює: код кожного SDK, який вона постачає, і правила компанії, що тримає магазин. Усі п’ять кейсів — про одне чи друге, і кожен задокументували ті, кого він стосувався: issue на GitHub, заява компанії, власні оголошення Apple.
| Кейс | Що сталося | Чого бракувало |
|---|---|---|
| Facebook SDK, 6 трав. і 10 лип. 2020 | Зміна на серверах Facebook двічі валила на старті Spotify, Pinterest, TikTok, Venmo та інші застосунки | Спосіб вимкнути сторонній SDK і розбір даних, що не довіряє серверу |
| HEY, черв. 2020 | Оновлення з виправленням відхилили, бо застосунок продавав підписку лише у вебі; схвалили 22 червня після додавання безкоштовної адреси на 14 днів | Бізнес-модель, звірена з пунктом 3.1 правил до написання коду |
| Банер вставлення в iOS 14, черв. 2020 | Перша бета показувала банер щоразу, коли застосунок читав буфер обміну; TikTok читав його що кілька натискань, LinkedIn — на кожне | Перелік того, що читають застосунок і його SDK, — з травня 2024 року обов’язковий як маніфест приватності |
| App Tracking Transparency, квіт. 2021 | iOS 14.5 вимагала запиту перед відстеженням у застосунках інших компаній; Meta оцінила ціну приблизно в $10 млрд виручки 2022 року | Продукт, що не залежав би від типової поведінки, яку може змінити операційна система |
| Видалення акаунта, 30 черв. 2022 | Застосунки з реєстрацією мусили пропонувати видалення в самому застосунку; дедлайн уже раз переносили | Шлях видалення, спроєктований разом із реєстрацією, а не після того, як рев’ю помітило його відсутність |
Перший кейс показує, що може зробити SDK на старті. 6 травня 2020 року, з 14:44 за тихоокеанським часом, застосунки з вбудованим iOS SDK від Facebook почали падати тієї ж миті, коли їх відкривали, — Spotify, Pinterest, TikTok, Venmo, Tinder та інші, зокрема в користувачів, які ніколи не входили через Facebook. Issue, відкритий на GitHub того ж дня, вказав на один рядок: SDK на старті завантажував конфігурацію із серверів Facebook і читав одне зі значень як словник, а сервер почав надсилати щось інше. Facebook виправив це на сервері за кілька годин. 10 липня 2020 року це сталося знову — через той самий компонент. Постраждалі застосунки нічого не зробили неправильно у власному коді. Їм бракувало дистанції: SDK, ініціалізований на шляху старту, ще до першого екрана, без перемикача, щоб вимкнути його віддалено, і без стійкості до сервера, що відповідає в неочікуваній формі.
Третій кейс — операційна система робить видимим те, що код робив завжди. У червні 2020 року перша бета iOS 14 почала показувати невеликий банер щоразу, коли застосунок читав буфер обміну. За кілька днів користувачі виклали записи: TikTok читав його що кілька натискань, LinkedIn — на кожне. TikTok назвав це функцією проти спаму й прибрав її; LinkedIn назвав це баґом. Банерові було байдуже, чий код читав буфер обміну — власний код застосунку чи SDK усередині нього, — і саме в цьому суть: команда відповідає за все, що робить її бінарник. У травні 2024 року Apple перетворила це на вимогу: завантаження мусить декларувати схвалену причину для кожного API з обов’язковою причиною, який воно викликає, зокрема через сторонні SDK. Четвертий кейс — той самий патерн у більшому масштабі. З iOS 14.5, 26 квітня 2021 року, застосунок мусив спитати, перш ніж відстежувати людину в застосунках інших компаній, і система показує цей запит один раз. У лютому 2022 року Meta сказала інвесторам, що ця зміна коштуватиме їй близько $10 млрд того року, — оцінка, як сказав фінансовий директор, і єдине опубліковане число такого масштабу. Типову поведінку, якою володіє платформа, платформа може змінити.
Де це вироджується
Нативна iOS щедра там, де команда дисциплінована, і мовчить там, де ні. Компілятор щороку ловить більше — з Swift 6 і гонки за даними, — але він досі не скаже, що екран так і не звільнився, що SDK читає буфер обміну чи що наступна вимога до SDK — за пів року. Майже кожен збій — одне з цього:
- Збережені замикання й делегати, що сильно захоплюють свого власника, течуть тихо й ніколи не падають (рядки 6–9).
- Таймери, display link, сесії й обробники вебв’ю, які ніхто не анулює (рядки 12–15).
- Задача, що крутиться вічно й тримає з собою свій екран (рядок 11).
unownedтам, де мався на увазіweak, — витік перетворюється на падіння на повільній мережі.- Модель, створена всередині
@ObservedObject, що скидається щоразу, коли оновлюється батько. - Синхронна робота в головному потоці — файл, декодування, прихований мережевий виклик, — доки watchdog це не завершить.
- Continuation, не відновлений на кожному шляху, — і екран, що вантажиться вічно.
- Сторонні SDK, запущені на старті, без способу їх вимкнути й без переліку того, що вони читають.
- Запити дозволів на старті, а не тієї миті, коли вони потрібні фічі, — система питає один раз.
- Вимоги Apple — видалення, маніфести приватності, життєвий цикл на сценах, мінімальний SDK, — виявлені в день, коли мав вийти реліз.
Чесна межа — сама платформа. Нативний iOS-застосунок — найкраща версія застосунку на iPhone: найновіші API з першого дня, власні елементи керування платформи та їхні редизайни задарма, детермінована пам’ять, компілятор, що виключає гонки за даними. І це одна платформа. Продукт, якому потрібен ще й Android, платить за другу нативну команду, за кросплатформний шар або — як вирішив Shopify у вересні 2026 року, про що розповідає сторінка про React Native, — за агентів для програмування, що перекладають між двома; безкоштовного варіанта немає. І кожен реліз досі проходить через рев’ю однієї компанії та її календар. Команда, що планує з урахуванням обох, вважає їх частиною графіка, а не погодою.
Практична перевірка займає десять хвилин. Прочитайте мінімальну версію iOS і версію Xcode й знайдіть їх у таблиці календаря. Пошукайте в коді var delegate: без weak, Timer.scheduledTimer(, addObserver(forName:, .sink { без списку захоплення, URLSession(configuration: із делегатом і add(self, name:. Відкрийте й закрийте три найнавантаженіші екрани по десять разів із відкритим Memory Graph Debugger. Перевірте, що Info.plist декларує маніфест сцен. Подивіться на мовний режим Swift і рівень перевірки паралельності кожної цілі збірки. І переконайтеся, що наступний реліз налаштовано на поетапний.
Коли це застосовувати
Застосовуйте, коли
- Продукти, де досвід на iPhone і є продуктом: продуктивність, вилизаність і конвенції платформи.
- Усе, що спирається на можливості, які Apple випускає першими: віджети, Live Activities, моделі на пристрої, нове залізо.
- Застосунки, де справді важать детермінована пам’ять, відсутність пауз збирання й перевірка гонок під час компіляції.
- Команди, здатні добре підтримувати одну платформу, а не приблизно дві.
Уникайте, коли
- Продукти, яким потрібні обидві платформи з однією командою й одним бюджетом, — для цього й є кросплатформні інструменти.
- Усе, що мусить змінити нативний код того ж дня: кожен бінарник іде через рев’ю, а відкату немає.
- Бізнес-моделі, що конфліктують із правилами платежів магазину, — вирішуйте це до написання коду.
- Команда без техніки Mac і без ресурсу стежити за щорічним календарем SDK від Apple: Xcode обійти неможливо.
Джерела й метод
На чому стоїть ця стаття
- 18 джерел
- 2 задокументовані збої
- Перевірено 13 жовтня 2026 р.
Практики й походження термінів
хто кого тримає: ARC, API, що утримують, і життєвий цикл- ГайдThe Swift Programming Language — Automatic Reference Counting (відкривається в новій вкладці)swift.org
- ДокументаціяTimer (відкривається в новій вкладці)Apple Developer Documentation
- ДокументаціяURLSession (відкривається в новій вкладці)Apple Developer Documentation
- ДокументаціяState (відкривається в новій вкладці)Apple Developer Documentation
- ДокументаціяAddressing watchdog terminations (відкривається в новій вкладці)Apple Developer Documentation
- ДокументаціяTN3187: Migrating to the UIKit scene-based life cycle (відкривається в новій вкладці)Apple Developer Documentation
- ДокументаціяWebKit — WKUserContentController.mm (відкривається в новій вкладці)GitHub · WebKit
- СтаттяSwift 6.4 Released (відкривається в новій вкладці)Swift.org · 15.09.2026
- ДокументаціяApp Review (відкривається в новій вкладці)Apple Developer
- ДокументаціяUpcoming Requirements (відкривається в новій вкладці)Apple Developer
- СтаттяCocoaPods Trunk Read-only Plan (відкривається в новій вкладці)CocoaPods Blog · 05.2025
Збої, на яких вчиться стаття
збій Facebook SDK і HEY у «П’ять кейсів»- ПостмортемSuddenly FBSDKRestrictiveDataFilterManager.m - Line 71 started to crash — facebook-ios-sdk #1373 (відкривається в новій вкладці)GitHub · 06.05.2020
- ПостмортемApp suddenly crashing on startup in production due to FBSDKRestrictiveDataFilterManager — #1427 (відкривається в новій вкладці)GitHub · 10.07.2020
- СтаттяSeveral major iOS apps crashing at launch due to Facebook SDK issues (відкривається в новій вкладці)TechCrunch · 06.05.2020
- СтаттяApple approves Hey email app update as company adds time-limited free accounts (відкривається в новій вкладці)9to5Mac · 22.06.2020
Правила приватності, що змінили застосунки
банер вставки, App Tracking Transparency і видалення акаунта- СтаттяTikTok to stop reading user clipboards after being exposed by iOS 14 privacy feature (відкривається в новій вкладці)9to5Mac · 25.06.2020
- СтаттяFacebook says Apple iOS privacy change will result in $10 billion revenue hit this year (відкривається в новій вкладці)CNBC · 02.02.2022
- ДокументаціяAccount deletion requirement starts June 30 (відкривається в новій вкладці)Apple Developer News · 24.05.2022
Дослідження з Claude. Пошук і звірку першоджерел зроблено з Claude (Anthropic). Відбір, текст і перевірку фактів — редакція Anatomy of IT Product.
Було корисно?
Поділіться з тим, хто працює над тією ж задачею.