Quick Wins (iOS)
Generated 2026-05-07 · Costco iOS
Top 10 quick wins (ios)
Each row is a sub-1-day fix that reduces immediate risk. Copy-paste the After block; verify locally; ship.
spread across the team
HIGH+ findings closed
QW-i-01 · Remove NSAllowsArbitraryLoads from Notification Service Extension
30 minProduction NSE has cleartext traffic enabled — bypasses HTTPS-only policy of the main app.
<!-- NotificationServiceExtension-Info.plist:25 -->
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<!-- NotificationServiceExtension-Info.plist -->
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>cdn.costco.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<false/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
QW-i-02 · Generate App Privacy Manifest
1 hourApple requires PrivacyInfo.xcprivacy for apps and SDKs accessing Required Reason APIs since 2024.
# No PrivacyInfo.xcprivacy
<!-- Costco/Costco/PrivacyInfo.xcprivacy -->
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array><string>CA92.1</string></array>
</dict>
<!-- Add entries for FileTimestamp, DiskSpace, etc. -->
</array>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>
QW-i-03 · Replace try! NSRegularExpression with try?
30 minForce-try on regex compilation crashes on syntax error. Use safe variant + fallback.
// MyWarehouseAndDeliveryStateManager.swift:863
let regex = try! NSRegularExpression(
pattern: "...",
options: []
)
// MyWarehouseAndDeliveryStateManager.swift:863
guard let regex = try? NSRegularExpression(
pattern: "...",
options: []
) else {
Logger.warehouse.error("Regex compile failed; falling back to substring match")
return false // or use a simpler matcher
}
QW-i-04 · Replace force-unwrap on URL(string:) with optional pattern
1 hour18+ instances in BazaarVoiceClient force-unwrap a hardcoded URL string. One typo crashes the entire reviews module.
// BazaarVoiceClient.swift
static let baseURL = URL(string: "https://api.bazaarvoice.com/...")!
// BazaarVoiceClient.swift
static let baseURL: URL = {
guard let u = URL(string: "https://api.bazaarvoice.com/...") else {
Logger.bazaar.fault("Invalid base URL — Reviews disabled")
return URL(string: "about:blank")! // safe fallback; downstream still no-ops
}
return u
}()
QW-i-05 · Invalidate cookieListenerTimer in deinit
30 minPolling timer fires every 5 seconds for the app lifetime; doesn't get cancelled. Drains battery + memory.
// CookiesManager.swift:691
self.cookieListenerTimer = Timer.scheduledTimer(
withTimeInterval: 5.0, repeats: true
) { _ in
self.checkCookies()
}
// CookiesManager.swift
self.cookieListenerTimer = Timer.scheduledTimer(
withTimeInterval: 5.0, repeats: true
) { [weak self] _ in
self?.checkCookies()
}
deinit {
cookieListenerTimer?.invalidate()
cookieListenerTimer = nil
}
QW-i-06 · Replace print() with os.Logger
1 hour30+ print() calls leak to console regardless of build config; Logger respects privacy + filtering.
// DMCWidgetHelper.swift:84
print("Store is not Opend yet")
print("\(Date()): Get DMCWidget Timeline called")
// At top of file or in a Logger+ extension
import os
extension Logger {
static let widget = Logger(subsystem: "com.costco.costco", category: "widget")
}
// Replace print with categorized Logger:
Logger.widget.notice("Store is not open yet")
Logger.widget.debug("Get DMCWidget Timeline called")
QW-i-07 · Add SwiftLint custom rule banning hex outside design system
1 hourHex literals scattered across feature code defeat the design-token pipeline.
# .swiftlint.yml — no custom rules
# .swiftlint.yml
custom_rules:
no_hex_color_outside_design_system:
name: "Hardcoded hex color"
regex: 'Color\(hex:|UIColor\(hex:|init\(hex:'
excluded:
- ".*CostcoDesignSystem.*"
severity: warning
no_force_try_in_production:
name: "Forbid try!"
regex: 'try!'
excluded:
- ".*Tests.*"
- ".*UITests.*"
severity: error
QW-i-08 · Add weak self to escaping closures
30 minClosures stored on long-lived objects (NotificationCenter, Timer, dispatch queues) capture self strongly without [weak self].
// CartViewModel.swift
NotificationCenter.default.addObserver(
forName: .cartUpdated, object: nil, queue: .main
) { notification in
self.refresh() // strong capture
}
// CartViewModel.swift
NotificationCenter.default.addObserver(
forName: .cartUpdated, object: nil, queue: .main
) { [weak self] notification in
self?.refresh()
}
// Or migrate to Combine:
NotificationCenter.default.publisher(for: .cartUpdated)
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in self?.refresh() }
.store(in: &cancellables)
QW-i-09 · Add CODEOWNERS
15 min29 SPM packages, no review routing.
# No CODEOWNERS
# .github/CODEOWNERS
* @costco-ios/maintainers
/Costco-Digital/Features/Cart/ @costco-ios/cart-team
/Costco-Digital/Features/PDP/ @costco-ios/pdp-team
/Costco-Digital/Features/Authentication/ @costco-ios/security
/Costco-Digital/CostcoDesignSystem/ @costco-ios/design-system
/Costco-Digital/Core/ @costco-ios/platform
QW-i-10 · Bump Podfile fallback iOS to 16.0
30 minMain app is iOS 16; Podfile fallback at 14 lets some pods ship for older OS than the app supports.
# Costco/Podfile
platform :ios, '14.0'
# Costco/Podfile
platform :ios, '16.0'