Skip to content

Commit 03a6358

Browse files
authored
Merge pull request #1554 from OneSignal/fix/various_user_module_crashes
Synchronize to fix crashes in the user module
2 parents 456be5c + 735e755 commit 03a6358

File tree

7 files changed

+165
-129
lines changed

7 files changed

+165
-129
lines changed

iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSModel.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ open class OSModel: NSObject, NSCoding {
5252
}
5353

5454
// We can add operation name to this... , such as enum of "updated", "deleted", "added"
55-
public func set<T>(property: String, newValue: T) {
55+
public func set<T>(property: String, newValue: T, preventServerUpdate: Bool = false) {
5656
let changeArgs = OSModelChangedArgs(model: self, property: property, newValue: newValue)
5757

5858
changeNotifier.fire { modelChangeHandler in
59-
modelChangeHandler.onModelUpdated(args: changeArgs, hydrating: self.hydrating)
59+
modelChangeHandler.onModelUpdated(args: changeArgs, hydrating: self.hydrating || preventServerUpdate)
6060
}
6161
}
6262

iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSModelStore.swift

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ open class OSModelStore<TModel: OSModel>: NSObject {
3131
let storeKey: String
3232
let changeSubscription: OSEventProducer<OSModelStoreChangedHandler>
3333
var models: [String: TModel]
34+
let lock = NSLock()
3435

3536
public init(changeSubscription: OSEventProducer<OSModelStoreChangedHandler>, storeKey: String) {
3637
self.storeKey = storeKey
@@ -67,67 +68,76 @@ open class OSModelStore<TModel: OSModel>: NSObject {
6768
Examples: "[email protected]" for a subscription model or `OS_IDENTITY_MODEL_KEY` for an identity model.
6869
*/
6970
public func getModel(key: String) -> TModel? {
70-
return self.models[key]
71+
lock.withLock {
72+
return self.models[key]
73+
}
7174
}
7275

7376
/**
7477
Uses the `modelId` to get the corresponding model in the store's models dictionary.
7578
*/
7679
public func getModel(modelId: String) -> TModel? {
77-
for model in models.values {
78-
if model.modelId == modelId {
79-
return model
80+
lock.withLock {
81+
for model in models.values {
82+
if model.modelId == modelId {
83+
return model
84+
}
8085
}
86+
return nil
8187
}
82-
return nil
8388
}
8489

8590
public func getModels() -> [String: TModel] {
86-
return self.models
91+
lock.withLock {
92+
return self.models
93+
}
8794
}
8895

8996
public func add(id: String, model: TModel, hydrating: Bool) {
9097
// TODO: Check if we are adding the same model? Do we replace?
9198
// For example, calling addEmail multiple times with the same email
9299
// Check API endpoint for behavior
93-
models[id] = model
100+
lock.withLock {
101+
models[id] = model
94102

95-
// persist the models (including new model) to storage
96-
OneSignalUserDefaults.initShared().saveCodeableData(forKey: self.storeKey, withValue: self.models)
103+
// persist the models (including new model) to storage
104+
OneSignalUserDefaults.initShared().saveCodeableData(forKey: self.storeKey, withValue: self.models)
97105

98-
// listen for changes to this model
99-
model.changeNotifier.subscribe(self)
106+
// listen for changes to this model
107+
model.changeNotifier.subscribe(self)
100108

101-
guard !hydrating else {
102-
return
103-
}
109+
guard !hydrating else {
110+
return
111+
}
104112

105-
self.changeSubscription.fire { modelStoreListener in
106-
modelStoreListener.onAdded(model)
113+
self.changeSubscription.fire { modelStoreListener in
114+
modelStoreListener.onAdded(model)
115+
}
107116
}
108117
}
109118

110119
/**
111-
Returns false if this model does not exist in the store.
120+
Nothing will happen if this model does not exist in the store.
112121
This can happen if remove email or SMS is called and it doesn't exist in the store.
113122
*/
114123
public func remove(_ id: String) {
115-
OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSModelStore remove() called with model \(id)")
116-
// TODO: Nothing will happen if model doesn't exist in the store
117-
if let model = models[id] {
118-
models.removeValue(forKey: id)
119-
120-
// persist the models (with removed model) to storage
121-
OneSignalUserDefaults.initShared().saveCodeableData(forKey: self.storeKey, withValue: self.models)
122-
123-
// no longer listen for changes to this model
124-
model.changeNotifier.unsubscribe(self)
125-
126-
self.changeSubscription.fire { modelStoreListener in
127-
modelStoreListener.onRemoved(model)
124+
lock.withLock {
125+
OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSModelStore remove() called with model \(id)")
126+
if let model = models[id] {
127+
models.removeValue(forKey: id)
128+
129+
// persist the models (with removed model) to storage
130+
OneSignalUserDefaults.initShared().saveCodeableData(forKey: self.storeKey, withValue: self.models)
131+
132+
// no longer listen for changes to this model
133+
model.changeNotifier.unsubscribe(self)
134+
135+
self.changeSubscription.fire { modelStoreListener in
136+
modelStoreListener.onRemoved(model)
137+
}
138+
} else {
139+
OneSignalLog.onesignalLog(.LL_ERROR, message: "OSModelStore cannot remove \(id) because it doesn't exist in the store.")
128140
}
129-
} else {
130-
OneSignalLog.onesignalLog(.LL_ERROR, message: "OSModelStore cannot remove \(id) because it doesn't exist in the store.")
131141
}
132142
}
133143

@@ -146,20 +156,24 @@ open class OSModelStore<TModel: OSModel>: NSObject {
146156
In contrast, it is not necessary for the Identity or Properties Model Stores to do so.
147157
*/
148158
public func clearModelsFromStore() {
149-
self.models = [:]
159+
lock.withLock {
160+
self.models = [:]
161+
}
150162
}
151163
}
152164

153165
extension OSModelStore: OSModelChangedHandler {
154166
public func onModelUpdated(args: OSModelChangedArgs, hydrating: Bool) {
155167
// persist the changed models to storage
156-
OneSignalUserDefaults.initShared().saveCodeableData(forKey: self.storeKey, withValue: self.models)
168+
lock.withLock {
169+
OneSignalUserDefaults.initShared().saveCodeableData(forKey: self.storeKey, withValue: self.models)
157170

158-
guard !hydrating else {
159-
return
160-
}
161-
self.changeSubscription.fire { modelStoreListener in
162-
modelStoreListener.onUpdated(args)
171+
guard !hydrating else {
172+
return
173+
}
174+
self.changeSubscription.fire { modelStoreListener in
175+
modelStoreListener.onUpdated(args)
176+
}
163177
}
164178
}
165179
}

iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionModel.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,19 @@ class OSSubscriptionModel: OSModel {
117117
}
118118
}
119119

120-
// Set via server response
120+
/**
121+
Typically, the subscription ID is set via server response, so don't trigger a server update call when it changes.
122+
It can also be set to null by the SDK when the user or subscription is detected as missing.
123+
Setting the subscription ID to null will serve as a "reset" and will later hydrate a value from a user create rquest.
124+
*/
121125
var subscriptionId: String? {
122126
didSet {
123127
guard subscriptionId != oldValue else {
124128
return
125129
}
126-
self.set(property: "subscriptionId", newValue: subscriptionId)
130+
131+
// If the ID has changed, don't trigger a server call, since it can be set to null
132+
self.set(property: "subscriptionId", newValue: subscriptionId, preventServerUpdate: true)
127133

128134
guard self.type == .push else {
129135
return

0 commit comments

Comments
 (0)