РозробкаБудь-який рівень

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 об’єкт — екземпляр класу — живе рівно доти, доки щось тримає на нього сильне посилання. Щойно зникає останнє, об’єкт звільняється на місці. Ніщо не приходить потім шукати об’єкти, до яких уже ніхто не може дістатися, як це робить збирач сміття () на 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 живим, доки не виконається, потім відпускає — затримка, а не витікНічого, якщо затримка не має значення
5Task { 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
9Combine sink, збережений у self.cancellables, який використовує selfself → множина → підписка → замикання → selfЦикл: підписка живе, доки живе self, і тримає self живим[weak self] або assign(to: &$property), що не утримує
10Блоковий спостерігач NotificationCenter, чий блок використовує selfЦентр тримає токен, токен — блок, блок — selfself живе, доки спостерігача не прибрано[weak self] у блоці — і прибрати токен
11Збережена Task, що крутиться вічно — for await по сповіщеннях — і використовує selfself → задача → замикання → selfЦикл, що сам не закінчиться; скасувати його в deinit неможливо, бо deinit ніколи не виконаєтьсяСкасовуйте, коли екран зникає; у SwiftUI беріть .task, яку скасовують разом із в’ю
12Timer.scheduledTimer(timeInterval: 1, target: self, …)Цикл подій (run loop) тримає таймер; таймер тримає цільself живе до invalidate() — так і сказано в документації Appleinvalidate(), коли екран зникає; блоковий таймер із [weak self]
13CADisplayLink(target: self, …) для покадрового малюванняDisplay link утримує ціль; цикл подій тримає display linkЯк у рядку 12invalidate() або невеликий проміжний об’єкт зі слабким посиланням як ціль
14URLSession(configuration:delegate:delegateQueue:)Сесія тримає сильне посилання на свого делегатаТече до завершення застосунку, якщо сесію не анулюватиfinishTasksAndInvalidate() або одна довгоживуча сесія на весь застосунок
15WKUserContentController.add(self, name:) — обробник повідомлень із JavaScriptКонтролер вмісту вебв’ю тримає обробник сильноЕкран, якому належить вебв’ю, ніколи не звільняєтьсяremoveScriptMessageHandler(forName:), коли екран зникає, або проміжний об’єкт зі слабким посиланням
16Синглтон чи статичний кеш, що тримає контролер в’ю, в’ю або замикання, яке їх захопилоКеш живе, доки живе застосунокНе цикл — об’єкт просто ніколи не стає недосяжнимНе тримайте екрани в довгоживучих об’єктах; зберігайте ідентифікатори й дані, а не в’ю
Swift звільняє об’єкт, коли ним більше ніхто не володіє, і компілятор вставляє цей підрахунок за вас — збирача сміття немає, пауз теж. Збій тут не в забутому звільненні, а в парі об’єктів, які тримають одне одного живими: лічильник ніколи не доходить до нуля, і жоден не звільняється. Розірвати цикл означає, що одне з двох посилань має відмовитися від володіння, — саме це роблять `weak` і `unowned`, і саме це робить `[weak self]` у замиканні, яке тримає сам об’єкт.

Підрахунок посилань: що 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 — і кому належить стан

На платформі два -фреймворки, і iOS-розробник, що працює, читає обидва. UIKit, присутній від першого iPhone 2008 року, імперативний: ви створюєте в’ю, тримаєте на них посилання й змінюєте їх, коли приходять події. SwiftUI, що з’явився з iOS 13 у 2019 році, декларативний: ви описуєте, який вигляд має екран для заданого стану, а фреймворк сам вираховує, що змінити, — та сама модель, що й у React, до якої дійшли незалежно. Більшість нових екранів пишуть на SwiftUI. Більшість наявних застосунків — це UIKit, усередині якого росте SwiftUI, і обидва взаємодіють в обидва боки: UIHostingController кладе SwiftUI всередину UIKit, а UIViewRepresentable — UIKit-в’ю всередину SwiftUI.

Один факт пояснює майже кожну несподіванку SwiftUI: в’ю у SwiftUI — це структура. Це опис, дешевий у створенні й викинутий після використання, і SwiftUI створює нові його значення щоразу, коли знову виконується body батька, — десятки разів на завантаженому екрані. Тож стан не може жити в структурі. Обгортки властивостей на кшталт @State кажуть SwiftUI тримати сховище поза в’ю, прив’язаним до її ідентичності — місця в ієрархії, — і віддавати його кожному новому значенню структури. Коли ідентичність змінюється, стан іде разом із нею: в’ю, перенесена між гілками if або отримала новий .id, починає знову з початкового значення. Це те саме правило, що й ключі в React, і той самий клас баґів.

В’ю у SwiftUI — це структура: опис, який SwiftUI створює заново щоразу, коли виконується body батька, і викидає після використання. Тож стан не може жити в структурі. Обгортки властивостей на кшталт `@State` кажуть SwiftUI тримати сховище зовні, прив’язаним до ідентичності в’ю, і віддавати те саме сховище кожному новому значенню. Об’єктом, створеним в `@ObservedObject`, ніхто не володіє, тож кожне нове значення в’ю створює власний, і стан екрана скидається — володійте ним через `@StateObject` або через `@State` для `@Observable`-класу. У збірці з Xcode 27 `@State` — макрос, що створює початкове значення лише вперше.
ОбгорткаВолодіє даними?З якої версіїДля чого
@StateТак — SwiftUI тримає, доки триває ідентичність в’юiOS 13Значення, локальне для в’ю; з iOS 17 — також @Observable-модель, яку створює в’ю
@BindingНі — читає й пише стан батькаiOS 13Дочірня в’ю, що редагує значення, яким не володіє
@Observable-класДаними володіє клас; в’ю перерендерюється лише через властивості, які прочитав її bodyiOS 17Моделі, спільні для кількох екранів
@BindableНіiOS 17Прив’язки до @Observable-моделі, переданої ззовні
@EnvironmentНі — значення, покладене вище в ієрархіїiOS 13Залежності й налаштування, які читає багато в’ю
@StateObjectТак — створюється раз на ідентичністьiOS 14ObservableObject, яким володіє в’ю, у коді до Observation
@ObservedObjectНі — лише стежить за об’єктом, який тримає хтось іншийiOS 13ObservableObject, переданий ззовні; ніколи не створений тут же

Що дає ваша мінімальна версія iOS

SwiftUI постачається всередині операційної системи, а не всередині вашого застосунку, тож найстаріша підтримувана iOS вирішує, який SwiftUI у вас є. Можливості, доданої в iOS 17, на телефоні з iOS 16 просто немає, і коду, що нею користується, потрібна гілка if #available із запасним варіантом. Тому мінімальна версія — продуктове рішення з ціною з обох боків: занизька — і команда пише запасні варіанти для того, що фреймворк уже вміє; зависока — і частина користувачів не зможе встановити наступне оновлення. Нові версії iOS приймають швидко, тож більшість команд підтримують поточну версію й одну-дві попередні.

Мінімальна iOSРікЩо отримав SwiftUI
iOS 132019Сам SwiftUI: в’ю, @State, @Binding, @ObservedObject
iOS 142020@StateObject, протокол App, ліниві стеки й сітки
iOS 152021.task — робота, прив’язана до часу життя в’ю, — AsyncImage, .refreshable
iOS 162022NavigationStack, Swift Charts, протокол Layout
iOS 172023Observation (@Observable, @Bindable), SwiftData
iOS 182024Власні контейнери, переходи з масштабуванням, сітчасті градієнти, тип Tab
iOS 262025Liquid Glass, WebView для SwiftUI, форматований текст у TextEditor
iOS 272026Перетягування для впорядкування в будь-якому контейнері, свайп-дії поза 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 перевіряє паралельність через ізоляцію: кожна частина змінного стану належить одному домену, і торкатися її може лише код із цього домену. Головний актор означає головний потік, і з Swift 6.2 модуль може зробити його типовим — Xcode 26 так і робить для нових застосунків, — тож код застосунку однопотоковий, доки свідомо не піде з нього. Актор на кшталт кешу зображень охороняє власний стан, пускаючи по одній задачі. Функція з `@concurrent` виконується у фоновому пулі. Кожен перехід — це `await`, і переходити можуть лише значення `Sendable`. У мовному режимі Swift 6 код, що міг би дати двом потокам торкнутися того самого стану без координації, не компілюється.

Swift 6 у вересні 2024 року зробив перевірки помилками, а не попередженнями, у своєму новому мовному режимі: код, що міг дати двом потокам торкнутися того самого змінного стану без координації, перестає компілюватися. Гонка за даними () стала помилкою компіляції — той самий хід, що Rust зробив із володінням. Перші міграції були важкими, бо більшість коду застосунків ніколи не казала, якому актору належить, і компілятор позначав це всюди. Swift 6.2 у вересні 2025 року змінив типові налаштування, а не правила. Модуль тепер може зробити все @MainActor, якщо не позначено інакше; async-функція, не прив’язана до актора, виконується на акторі того, хто її викликав, а не стрибає у фоновий потік; а @concurrent позначає функції, які мають піти. Xcode 26 увімкнув обидва налаштування для нових проєктів застосунків. Тож сучасна точка відліку — однопотокова: код виконується на головному акторі, а робота йде з нього свідомо.

ВерсіяВихідЩо змінилося для розробника застосунків
Swift 5.0берез. 2019Стабільний бінарний інтерфейс: середовище Swift постачається в iOS 12.2 і новіших, тож застосунки перестають його вкладати
Swift 5.5верес. 2021async/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верес. 2026await усередині 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 надсилати оновлення «по повітрю» (), а сторінка про React Native цитує цей пункт. Без релізу нативний застосунок може змінити лише те, що його збудовано читати: вміст, яким керує сервер, віддалену конфігурацію, , що вмикає код, уже наявний у бінарнику. Усе інше чекає наступної збірки, і саме тому важить порядок кроків нижче.

Кожна збірка для iOS доходить до користувачів через ті самі чотири кроки. TestFlight спершу кладе її на справжні пристрої — до 100 людей із команди, щойно збірку оброблено, до 10 000 зовнішніх тестувальників після Beta App Review, — і кожну збірку можна встановлювати 90 днів. Потім App Review дивиться на реліз; Apple каже, що 90% заявок переглядають менш ніж за 24 години. Поетапний реліз розтягує оновлення на сім днів за сталим розкладом, і діаграма в масштабі: на третій день оновлення має лише 5% користувачів. Він стримує лише автоматичні оновлення, його можна поставити на паузу сумарно на 30 днів, і його не скасувати — хто вже отримав оновлення, той його зберігає, тож шлях назад — нова збірка.

TestFlight — крок перед магазином. До 100 внутрішніх тестувальників із доступом до App Store Connect команди отримують збірку, щойно вона пройде обробку. До 10 000 зовнішніх тестувальників приєднуються за запрошенням чи публічним посиланням; перша збірка для них проходить Beta App Review. Кожну збірку можна встановлювати 90 днів. Далі — сам реліз і найкорисніший перемикач в App Store Connect: поетапний реліз (). Оновлення отримує 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 SDKLiquid 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 з мінімальною перевіркою паралельностіКомпілятор про гонки ще ніхто не питав; посилюйте перевірку по одному модулю
PodfileCocoaPods, чий центральний репозиторій перестає приймати нові версії 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, квіт. 2021iOS 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, що утримують, і життєвий цикл

Дослідження з Claude. Пошук і звірку першоджерел зроблено з Claude (Anthropic). Відбір, текст і перевірку фактів — редакція Anatomy of IT Product.

Було корисно?

Поділіться з тим, хто працює над тією ж задачею.