ios-performance-battery-patterns
Battery & Performance Patterns
Section titled “Battery & Performance Patterns”Apply these whenever touching widgets, animations, networking, or background work.
Widgets
Section titled “Widgets”- Limit
Timelineentries to ≤ 2 (current + one next-day refresh). More entries run the provider repeatedly and drain battery. - Use
.atEndreload policy — let WidgetKit decide when to refresh.
Animations
Section titled “Animations”- Always stop animations in
.onDisappear. Animations left running off-screen still consume CPU/GPU. - Bind repeating animations to a
@State var isAnimating = false: settruein.onAppear,falsein.onDisappear, and passvalue: isAnimatingtowithAnimation. - Use
.repeatCount(N)instead of.repeatForeverfor attention animations.
Low Power Mode
Section titled “Low Power Mode”Guard expensive operations before they start:
guard !ProcessInfo.processInfo.isLowPowerModeEnabled else { return }Apply to: image preloading, background downloads, video prefetch, heavy sync.
Network
Section titled “Network”let config = URLSessionConfiguration.defaultconfig.allowsConstrainedNetworkAccess = false // respect Low Data Modeconfig.allowsExpensiveNetworkAccess = false // avoid cellular when Wi-Fi preferredconfig.waitsForConnectivity = true // queue rather than fail when offlineObserver & Task Cleanup
Section titled “Observer & Task Cleanup”@Observable macro-generated storage prevents nonisolated deinit from removing NotificationCenter observers. Use reference-type boxes instead:
final class NotificationObserverBox { private var tokens: [NSObjectProtocol] = [] func add(_ token: NSObjectProtocol) { tokens.append(token) } deinit { tokens.forEach { NotificationCenter.default.removeObserver($0) } }}
final class TaskBox { private var cancel: (() -> Void)? func store<Success, Failure>(_ task: Task<Success, Failure>) { cancel = { task.cancel() } } deinit { cancel?() }}For MPRemoteCommandCenter: store addTarget return values; call removeTarget(nil) on each in deinit.