Irbisa · Шпаргалка
Жизненный цикл, фон и push
Холодный старт, scene phase, фоновые режимы, BGTaskScheduler, APNs, silent push, universal links
В каких состояниях может быть iOS-приложение (not running, inactive, active, background, suspended), и что переводит его из одного в другое?
final class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? // background -> inactive: about to return to the screen func sceneWillEnterForeground(_ scene: UIScene) { feed.refreshIfStale() }…Чем
AppDelegateотличается отSceneDelegate, и зачем в iOS 13 жизненный цикл UI перенесли в сцены?@mainfinal class AppDelegate: UIResponder, UIApplicationDelegate { // Once per process, with or without UI func application(_ application: UIApplication, didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { Analytics.configure()…Как отслеживать жизненный цикл приложения в SwiftUI через
scenePhaseи когда всё ещё нужен@UIApplicationDelegateAdaptor?@mainstruct ShopApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate @Environment(\.scenePhase) private var scenePhase @State private var store = CartStore()…Гарантированно ли вызывается
applicationWillTerminateи где тогда сохранять состояние?Чем локальные уведомления отличаются от удалённых и как запросить у пользователя разрешение?
import UIKitimport UserNotifications @MainActorfunc enableReminders() async throws { let center = UNUserNotificationCenter.current()…После выхода этого кода число сессий удвоилось. Почему и в каком колбэке должна начинаться сессия?
final class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func sceneDidBecomeActive(_ scene: UIScene) { Analytics.log("session_start") } func sceneDidEnterBackground(_ scene: UIScene) { Analytics.log("session_end") }}Что происходит между тапом пользователя по иконке и вызовом
application(_:didFinishLaunchingWithOptions:)?Как iOS ограничивает фоновую работу и какими легальными способами можно выполнить код после того, как пользователь ушёл из приложения?
<!-- Info.plist: declare only what the app genuinely does --><key>UIBackgroundModes</key><array> <!-- lets the system wake you for BGAppRefreshTask --> <string>fetch</string> <!-- lets the system wake you for BGProcessingTask -->…Этот код должен дать загрузке завершиться после ухода пользователя из приложения. Что с ним не так?
func sceneDidEnterBackground(_ scene: UIScene) { let app = UIApplication.shared let id = app.beginBackgroundTask(withName: "upload") uploader.upload(draft) { result in if case .success = result { app.endBackgroundTask(id) } }}Найдите баг: посреди тренировки пользователь на несколько минут переключается в другое приложение, а по возвращении этот таймер отстаёт на эти минуты.
struct WorkoutTimerView: View { @State private var elapsed = 0 private let ticker = Timer.publish(every: 1, on: .main, in: .common).autoconnect() var body: some View { Text(Duration.seconds(elapsed).formatted(.time(pattern: .minuteSecond))) .monospacedDigit() .onReceive(ticker) { _ in elapsed += 1 } }}Как запланировать работу через
BGTaskSchedulerи почему зарегистрированная задача может так и не запуститься?// Register before didFinishLaunching returnsfunc application(_ application: UIApplication, didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.app.nightly-sync", using: nil) { task in handleSync(task as! BGProcessingTask)…Как протестировать задачу
BGTaskScheduler, не дожидаясь, пока её запустит система?# 1. Run on a device from Xcode, let the app call submit(),# then send it to the background.# 2. Press Pause in Xcode and enter in the LLDB console: e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.example.app.nightly-sync"]…Как push-уведомления работают от начала до конца: APNs, device token и payload?
func application(_ application: UIApplication, didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Every launch: the token may have changed since last time. application.registerForRemoteNotifications() return true}…Что нужно silent push, чтобы разбудить приложение, и почему он всё равно может не дойти?
// What the server sends:// apns-push-type: background <- alert here means no background wake// apns-priority: 5 <- 10 is not allowed for background pushes// apns-topic: com.example.app// { "aps": { "content-available": 1 }, "reason": "feed" }…Найдите баг в этом обработчике silent push: пуш приходит, но когда пользователь открывает приложение, входящие всё ещё устаревшие.
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { guard userInfo["reason"] as? String == "inbox" else { completionHandler(.noData) return } Task { try await inbox.sync() } completionHandler(.newData)}Как показать или скрыть уведомление, пришедшее при открытом приложении, и как тапы и кнопки действий попадают в
userNotificationCenter(_:didReceive:)?func application(_ application: UIApplication, didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let reply = UNTextInputNotificationAction(identifier: "REPLY", title: "Reply", options: [], textInputButtonTitle: "Send", textInputPlaceholder: "Message") let message = UNNotificationCategory(identifier: "MESSAGE", // "category": "MESSAGE" in aps actions: [reply], intentIdentifiers: [])…Что даёт provisional-разрешение на уведомления и почему пользователь со статусом
.authorizedможет ничего не видеть?let center = UNUserNotificationCenter.current() func requestProvisional() async throws { // Day one: no prompt. Delivered quietly to Notification Center. let granted = try await center.requestAuthorization(options: [.alert, .sound, .badge, .provisional]) // granted is true immediately; the user saw nothing.…Чем universal links отличаются от кастомных URL-схем и что нужно universal link, чтобы открыть приложение?
{ "applinks": { "details": [ { "appIDs": ["ABCDE12345.com.example.app"], "components": […Universal links открывают нужный экран, пока приложение запущено, но после холодного старта приводят на главный экран. Чего здесь не хватает?
final class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = scene as? UIWindowScene else { return } window = UIWindow(windowScene: windowScene) window?.rootViewController = RootViewController() window?.makeKeyAndVisible() } func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { guard let url = userActivity.webpageURL else { return } router.open(Route(url) ?? .home) }}Как iOS может запустить приложение сразу в фоне и чего
didFinishLaunchingдолжен избегать на этом пути?func application(_ application: UIApplication, didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Needed on every launch, visible or not. registerBackgroundTasks() UNUserNotificationCenter.current().delegate = self return true…Какие фоновые пробуждения ещё доходят до приложения после того, как пользователь смахнул его в переключателе приложений?
Что такое prewarming приложения и как он искажает метрики времени запуска?
import UIKitimport os @MainActorenum LaunchClock { // Set by the system only when this process was started by prewarming.…Что такое
BGContinuedProcessingTaskв iOS 26 и когда его выбирать вместоBGProcessingTask?import BackgroundTasks func startExport(_ video: Video) throws { // Info.plist lists com.example.app.export.* in BGTaskSchedulerPermittedIdentifiers let id = "com.example.app.export.\(video.id)"…