@@ -36,17 +36,20 @@ open class OSModelStore<TModel: OSModel>: NSObject {
3636 public init ( changeSubscription: OSEventProducer < OSModelStoreChangedHandler > , storeKey: String ) {
3737 self . storeKey = storeKey
3838 self . changeSubscription = changeSubscription
39-
40- // read models from cache, if any
41- if let models = OneSignalUserDefaults . initShared ( ) . getSavedCodeableData ( forKey: self . storeKey, defaultValue: [ : ] ) as? [ String : TModel ] {
42- self . models = models
43- } else {
44- // log error
45- self . models = [ : ]
46- }
39+ self . models = OSModelStore . loadModelsFromUserDefaults ( storeKey: storeKey)
4740 super. init ( )
41+ subscribeToOwnedModels ( )
42+ }
43+
44+ /// Reads the archived `[String: TModel]` dict for `storeKey` from shared UserDefaults.
45+ /// Returns an empty dict if the entry is missing or the unarchive fails.
46+ private static func loadModelsFromUserDefaults( storeKey: String ) -> [ String : TModel ] {
47+ return ( OneSignalUserDefaults . initShared ( ) . getSavedCodeableData ( forKey: storeKey, defaultValue: [ : ] ) as? [ String : TModel ] ) ?? [ : ]
48+ }
4849
49- // listen for changes to the models
50+ /// Subscribes this store as a change observer on every model currently in `models`.
51+ /// Callers must hold `lock` if invoking after init.
52+ private func subscribeToOwnedModels( ) {
5053 for model in self . models. values {
5154 model. changeNotifier. subscribe ( self )
5255 }
@@ -93,6 +96,25 @@ open class OSModelStore<TModel: OSModel>: NSObject {
9396 }
9497 }
9598
99+ /// Re-read this store's backing UserDefaults entry and hydrate `models` from disk.
100+ /// No-op when `models` is already non-empty — we never clobber in-memory state.
101+ ///
102+ /// Motivation: model stores load their `models` dict once in `init()` from shared UserDefaults.
103+ /// If `init()` runs while protected data is unavailable (iOS app prewarm, NSE before first
104+ /// unlock), that read returns nil and the dict stays empty for the lifetime of the singleton —
105+ /// it is never re-read. After protected data becomes available, callers can call `refresh()`
106+ /// so the store reflects what's actually on disk. Does not fire listener events.
107+ public func refresh( ) {
108+ lock. withLock {
109+ guard models. isEmpty else { return }
110+ let stored = OSModelStore . loadModelsFromUserDefaults ( storeKey: self . storeKey)
111+ guard !stored. isEmpty else { return }
112+ OneSignalLog . onesignalLog ( . LL_DEBUG, message: " OSModelStore[ \( self . storeKey) ] refresh hydrated \( stored. count) model(s) from UserDefaults " )
113+ self . models = stored
114+ subscribeToOwnedModels ( )
115+ }
116+ }
117+
96118 public func add( id: String , model: TModel , hydrating: Bool ) {
97119 // TODO: Check if we are adding the same model? Do we replace?
98120 // For example, calling addEmail multiple times with the same email
0 commit comments