Overview › Lifecycle & State Management
Lifecycle & State Management
Retain cycles, timer leaks, NotificationCenter unbounded observers — major risk.
60
SCORE
Summary
Lifecycle issues are the iOS codebase's biggest concentrated risk: 6 timer-not-invalidated patterns, 21 NotificationCenter observers without removal, retain cycles in completion handlers, and asyncAfter without cancellable WorkItems. The Cookie polling timer fires every 5 seconds for the app lifetime.
Common iOS lifecycle pitfalls
| Pattern | Risk | Fix |
|---|---|---|
Timer.scheduledTimer not invalidated | Strong ref; controller never released | timer?.invalidate() in deinit / viewWillDisappear |
NotificationCenter observer not removed | Observer retained; app accumulates | Capture token, call removeObserver; or Combine publisher with cancellable |
| Strong self in escaping closure | Retain cycle | [weak self] + guard let self else { return } |
Non-weak delegate | Retain cycle (delegate ↔ owner) | weak var delegate: SomeDelegate? |
asyncAfter without WorkItem | Stale callback after dismissal | DispatchWorkItem; cancel on disappear. Or Task + cancel. |
| KVO observer not removed | Crash on dealloc with active observer | Pair every addObserver(_:forKeyPath:) with removeObserver |
SwiftUI onAppear work without onDisappear cleanup | Stale state; leaks | Mirror onAppear with onDisappear; tie Tasks to view identity |
Findings
CRITICAL
Cookie polling timer fires every 5s for app lifetime
Costco/Costco/Application/CookiesManager/CookiesManager.swift:691 — repeating timer not paired with invalidate.
Recommendation: Replace polling with WKHTTPCookieStore observer; or throttle + invalidate on background.
HIGH
21 NotificationCenter observers without removal
Distributed across feature ViewModels.
Recommendation: Capture observer token in init; removeObserver in deinit. Consider
NotificationCenter.default.publisher(for: …) + cancellable Set.HIGH
40+ asyncAfter without cancellation
Closures capture self strongly with no path to cancel.
Recommendation: Standardize on
DispatchWorkItem stored on the view controller; or Task + cancel in dismiss path.Findings in this category
Costco iOS · Code Review Report · Generated 2026-05-07 · 88 machine-curated findings