Skip to content

Commit 70fcfb0

Browse files
authored
Merge pull request #1454 from OneSignal/fix/add_dispatch_queues_to_all_executors
[Bug] Add Dispatch Queues to all executors
2 parents f1489cc + 8248762 commit 70fcfb0

File tree

9 files changed

+532
-312
lines changed

9 files changed

+532
-312
lines changed

iOS_SDK/OneSignalSDK/OneSignalCoreMocks/MockOSDispatchQueue.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import OneSignalOSCore
3030
public class MockDispatchQueue: OSDispatchQueue {
3131
let requestDispatch = DispatchQueue(label: "MockDispatchQueue")
3232
var numDispatches = 0
33-
33+
3434
public init() {}
3535

3636
public func async(execute work: @escaping @convention(block) () -> Void) {
@@ -46,8 +46,8 @@ public class MockDispatchQueue: OSDispatchQueue {
4646
self.numDispatches += 1
4747
}
4848
}
49-
50-
public func waitForDispatches(_ numDispatches: Int) -> Void {
49+
50+
public func waitForDispatches(_ numDispatches: Int) {
5151
while self.numDispatches < numDispatches {
5252
requestDispatch.sync {
5353
Thread.sleep(forTimeInterval: TimeInterval(1))

iOS_SDK/OneSignalSDK/OneSignalCoreMocks/MockOneSignalClient.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,17 @@ import OneSignalCore
2828
*/
2929
@objc
3030
public class MockOneSignalClient: NSObject, IOneSignalClient {
31-
public let executionQueue: DispatchQueue = DispatchQueue(label: "com.onesignal.execution")
31+
public let executionQueue: DispatchQueue = DispatchQueue(label: "com.onesignal.execution", attributes: .concurrent)
32+
let lock = NSLock()
3233

3334
var mockResponses: [String: [String: Any]] = [:]
3435
var mockFailureResponses: [String: NSError] = [:]
3536
public var lastHTTPRequest: OneSignalRequest?
3637
public var networkRequestCount = 0
3738
public var executedRequests: [OneSignalRequest] = []
3839
public var executeInstantaneously = false
40+
/// Set to true to make it unnecessary to setup mock responses for every request possible
41+
public var fireSuccessForAllRequests = false
3942

4043
var remoteParamsResponse: [String: Any]?
4144
var shouldUseProvisionalAuthorization = false // new in iOS 12 (aka Direct to History)
@@ -90,7 +93,9 @@ public class MockOneSignalClient: NSObject, IOneSignalClient {
9093
public func execute(_ request: OneSignalRequest, onSuccess successBlock: @escaping OSResultSuccessBlock, onFailure failureBlock: @escaping OSFailureBlock) {
9194
print("🧪 MockOneSignalClient execute called")
9295

93-
executedRequests.append(request)
96+
lock.withLock {
97+
executedRequests.append(request)
98+
}
9499

95100
if executeInstantaneously {
96101
finishExecutingRequest(request, onSuccess: successBlock, onFailure: failureBlock)
@@ -135,6 +140,9 @@ public class MockOneSignalClient: NSObject, IOneSignalClient {
135140
successBlock(mockResponses[stringifiedRequest])
136141
} else if (mockFailureResponses[stringifiedRequest]) != nil {
137142
failureBlock(mockFailureResponses[stringifiedRequest])
143+
} else if fireSuccessForAllRequests {
144+
allRequestsHandled = false
145+
successBlock([:])
138146
} else {
139147
allRequestsHandled = false
140148
print("🧪 cannot find a mock response for request: \(stringifiedRequest)")

iOS_SDK/OneSignalSDK/OneSignalCoreMocks/OneSignalCoreMocks.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ public class OneSignalCoreMocks: NSObject {
5151
let expectation = XCTestExpectation(description: "Wait for \(seconds) seconds")
5252
_ = XCTWaiter.wait(for: [expectation], timeout: seconds)
5353
}
54-
54+
5555
@objc public static func backgroundApp() {
56-
if (OSBundleUtils.isAppUsingUIScene()) {
56+
if OSBundleUtils.isAppUsingUIScene() {
5757
if #available(iOS 13.0, *) {
5858
NotificationCenter.default.post(name: UIScene.willDeactivateNotification, object: nil)
5959
NotificationCenter.default.post(name: UIScene.didEnterBackgroundNotification, object: nil)
@@ -63,9 +63,9 @@ public class OneSignalCoreMocks: NSObject {
6363
NotificationCenter.default.post(name: UIApplication.didEnterBackgroundNotification, object: nil)
6464
}
6565
}
66-
66+
6767
@objc public static func foregroundApp() {
68-
if (OSBundleUtils.isAppUsingUIScene()) {
68+
if OSBundleUtils.isAppUsingUIScene() {
6969
if #available(iOS 13.0, *) {
7070
NotificationCenter.default.post(name: UIScene.willEnterForegroundNotification, object: nil)
7171
NotificationCenter.default.post(name: UIScene.didActivateNotification, object: nil)
@@ -75,19 +75,19 @@ public class OneSignalCoreMocks: NSObject {
7575
NotificationCenter.default.post(name: UIApplication.didBecomeActiveNotification, object: nil)
7676
}
7777
}
78-
78+
7979
@objc public static func resignActive() {
80-
if (OSBundleUtils.isAppUsingUIScene()) {
80+
if OSBundleUtils.isAppUsingUIScene() {
8181
if #available(iOS 13.0, *) {
8282
NotificationCenter.default.post(name: UIScene.willDeactivateNotification, object: nil)
8383
}
8484
} else {
8585
NotificationCenter.default.post(name: UIApplication.willResignActiveNotification, object: nil)
8686
}
8787
}
88-
88+
8989
@objc public static func becomeActive() {
90-
if (OSBundleUtils.isAppUsingUIScene()) {
90+
if OSBundleUtils.isAppUsingUIScene() {
9191
if #available(iOS 13.0, *) {
9292
NotificationCenter.default.post(name: UIScene.didActivateNotification, object: nil)
9393
}

iOS_SDK/OneSignalSDK/OneSignalNotificationsTests/OneSignalNotificationsTests.swift

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import OneSignalCoreMocks
2626
import UIKit
2727

2828
final class OneSignalNotificationsTests: XCTestCase {
29-
29+
3030
var notifTypes: Int32 = 0
3131
var token: String = ""
3232

@@ -52,7 +52,7 @@ final class OneSignalNotificationsTests: XCTestCase {
5252
// Ensure that badge count == 0
5353
XCTAssertEqual(UIApplication.shared.applicationIconBadgeNumber, 0)
5454
}
55-
55+
5656
func testDontclearBadgesWhenAppBecomesActive() throws {
5757
// NotificationManager Start to register lifecycle listener
5858
OSNotificationsManager.start()
@@ -65,25 +65,24 @@ final class OneSignalNotificationsTests: XCTestCase {
6565
// Ensure that badge count == 1
6666
XCTAssertEqual(UIApplication.shared.applicationIconBadgeNumber, 1)
6767
}
68-
68+
6969
func testUpdateNotificationTypesOnAppEntersForeground() throws {
7070
// NotificationManager Start to register lifecycle listener
7171
OSNotificationsManager.start()
72-
72+
7373
OSNotificationsManager.delegate = self
74-
74+
7575
XCTAssertEqual(self.notifTypes, 0)
76-
76+
7777
// Then background the app
7878
OneSignalCoreMocks.backgroundApp()
79-
79+
8080
// Foreground the app for within 30 seconds
8181
OneSignalCoreMocks.foregroundApp()
82-
82+
8383
// Ensure that the delegate is updated with the new notification type
8484
XCTAssertEqual(self.notifTypes, ERROR_PUSH_NEVER_PROMPTED)
8585
}
86-
8786

8887
}
8988

iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSIdentityOperationExecutor.swift

Lines changed: 88 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ class OSIdentityOperationExecutor: OSOperationExecutor {
3535
var addRequestQueue: [OSRequestAddAliases] = []
3636
var removeRequestQueue: [OSRequestRemoveAlias] = []
3737

38+
// The Identity executor dispatch queue, serial. This synchronizes access to the delta and request queues.
39+
private let dispatchQueue = DispatchQueue(label: "OneSignal.OSIdentityOperationExecutor", target: .global())
40+
3841
init() {
3942
// Read unfinished deltas from cache, if any...
4043
if var deltaQueue = OneSignalUserDefaults.initShared().getSavedCodeableData(forKey: OS_IDENTITY_EXECUTOR_DELTA_QUEUE_KEY, defaultValue: []) as? [OSDelta] {
@@ -101,53 +104,60 @@ class OSIdentityOperationExecutor: OSOperationExecutor {
101104
}
102105

103106
func enqueueDelta(_ delta: OSDelta) {
104-
OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSIdentityOperationExecutor enqueueDelta: \(delta)")
105-
deltaQueue.append(delta)
107+
self.dispatchQueue.async {
108+
OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSIdentityOperationExecutor enqueueDelta: \(delta)")
109+
self.deltaQueue.append(delta)
110+
}
106111
}
107112

108113
func cacheDeltaQueue() {
109-
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_DELTA_QUEUE_KEY, withValue: self.deltaQueue)
114+
self.dispatchQueue.async {
115+
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_DELTA_QUEUE_KEY, withValue: self.deltaQueue)
116+
}
110117
}
111118

112119
func processDeltaQueue(inBackground: Bool) {
113-
if !deltaQueue.isEmpty {
114-
OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSIdentityOperationExecutor processDeltaQueue with queue: \(deltaQueue)")
115-
}
116-
for delta in deltaQueue {
117-
guard let model = delta.model as? OSIdentityModel,
118-
let aliases = delta.value as? [String: String]
119-
else {
120-
// Log error
121-
continue
120+
self.dispatchQueue.async {
121+
if !self.deltaQueue.isEmpty {
122+
OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSIdentityOperationExecutor processDeltaQueue with queue: \(self.deltaQueue)")
122123
}
124+
for delta in self.deltaQueue {
125+
guard let model = delta.model as? OSIdentityModel,
126+
let aliases = delta.value as? [String: String]
127+
else {
128+
// Log error
129+
continue
130+
}
123131

124-
switch delta.name {
125-
case OS_ADD_ALIAS_DELTA:
126-
let request = OSRequestAddAliases(aliases: aliases, identityModel: model)
127-
addRequestQueue.append(request)
132+
switch delta.name {
133+
case OS_ADD_ALIAS_DELTA:
134+
let request = OSRequestAddAliases(aliases: aliases, identityModel: model)
135+
self.addRequestQueue.append(request)
128136

129-
case OS_REMOVE_ALIAS_DELTA:
130-
for (label, _) in aliases {
131-
let request = OSRequestRemoveAlias(labelToRemove: label, identityModel: model)
132-
removeRequestQueue.append(request)
133-
}
137+
case OS_REMOVE_ALIAS_DELTA:
138+
for (label, _) in aliases {
139+
let request = OSRequestRemoveAlias(labelToRemove: label, identityModel: model)
140+
self.removeRequestQueue.append(request)
141+
}
134142

135-
default:
136-
OneSignalLog.onesignalLog(.LL_DEBUG, message: "OSIdentityOperationExecutor met incompatible OSDelta type: \(delta)")
143+
default:
144+
OneSignalLog.onesignalLog(.LL_DEBUG, message: "OSIdentityOperationExecutor met incompatible OSDelta type: \(delta)")
145+
}
137146
}
138-
}
139147

140-
self.deltaQueue = [] // TODO: Check that we can simply clear all the deltas in the deltaQueue
148+
self.deltaQueue = [] // TODO: Check that we can simply clear all the deltas in the deltaQueue
141149

142-
// persist executor's requests (including new request) to storage
143-
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_ADD_REQUEST_QUEUE_KEY, withValue: self.addRequestQueue)
144-
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY, withValue: self.removeRequestQueue)
150+
// persist executor's requests (including new request) to storage
151+
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_ADD_REQUEST_QUEUE_KEY, withValue: self.addRequestQueue)
152+
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY, withValue: self.removeRequestQueue)
145153

146-
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_DELTA_QUEUE_KEY, withValue: self.deltaQueue) // This should be empty, can remove instead?
154+
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_DELTA_QUEUE_KEY, withValue: self.deltaQueue) // This should be empty, can remove instead?
147155

148-
processRequestQueue(inBackground: inBackground)
156+
self.processRequestQueue(inBackground: inBackground)
157+
}
149158
}
150159

160+
/// This method is called by `processDeltaQueue` only and does not need to be added to the dispatchQueue.
151161
func processRequestQueue(inBackground: Bool) {
152162
let requestQueue: [OneSignalRequest] = addRequestQueue + removeRequestQueue
153163

@@ -188,38 +198,42 @@ class OSIdentityOperationExecutor: OSOperationExecutor {
188198
OneSignalCoreImpl.sharedClient().execute(request) { _ in
189199
// No hydration from response
190200
// On success, remove request from cache
191-
self.addRequestQueue.removeAll(where: { $0 == request})
192-
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_ADD_REQUEST_QUEUE_KEY, withValue: self.addRequestQueue)
193-
if inBackground {
194-
OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier)
201+
self.dispatchQueue.async {
202+
self.addRequestQueue.removeAll(where: { $0 == request})
203+
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_ADD_REQUEST_QUEUE_KEY, withValue: self.addRequestQueue)
204+
if inBackground {
205+
OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier)
206+
}
195207
}
196208
} onFailure: { error in
197209
OneSignalLog.onesignalLog(.LL_ERROR, message: "OSIdentityOperationExecutor add aliases request failed with error: \(error.debugDescription)")
198-
if let nsError = error as? NSError {
199-
let responseType = OSNetworkingUtils.getResponseStatusType(nsError.code)
200-
if responseType == .missing {
201-
// Remove from cache and queue
202-
self.addRequestQueue.removeAll(where: { $0 == request})
203-
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_ADD_REQUEST_QUEUE_KEY, withValue: self.addRequestQueue)
204-
// Logout if the user in the SDK is the same
205-
guard OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModel)
206-
else {
207-
if inBackground {
208-
OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier)
210+
self.dispatchQueue.async {
211+
if let nsError = error as? NSError {
212+
let responseType = OSNetworkingUtils.getResponseStatusType(nsError.code)
213+
if responseType == .missing {
214+
// Remove from cache and queue
215+
self.addRequestQueue.removeAll(where: { $0 == request})
216+
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_ADD_REQUEST_QUEUE_KEY, withValue: self.addRequestQueue)
217+
// Logout if the user in the SDK is the same
218+
guard OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModel)
219+
else {
220+
if inBackground {
221+
OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier)
222+
}
223+
return
209224
}
210-
return
225+
// The subscription has been deleted along with the user, so remove the subscription_id but keep the same push subscription model
226+
OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModel?.subscriptionId = nil
227+
OneSignalUserManagerImpl.sharedInstance._logout()
228+
} else if responseType != .retryable {
229+
// Fail, no retry, remove from cache and queue
230+
self.addRequestQueue.removeAll(where: { $0 == request})
231+
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_ADD_REQUEST_QUEUE_KEY, withValue: self.addRequestQueue)
211232
}
212-
// The subscription has been deleted along with the user, so remove the subscription_id but keep the same push subscription model
213-
OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModel?.subscriptionId = nil
214-
OneSignalUserManagerImpl.sharedInstance._logout()
215-
} else if responseType != .retryable {
216-
// Fail, no retry, remove from cache and queue
217-
self.addRequestQueue.removeAll(where: { $0 == request})
218-
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_ADD_REQUEST_QUEUE_KEY, withValue: self.addRequestQueue)
219233
}
220-
}
221-
if inBackground {
222-
OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier)
234+
if inBackground {
235+
OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier)
236+
}
223237
}
224238
}
225239
}
@@ -243,25 +257,28 @@ class OSIdentityOperationExecutor: OSOperationExecutor {
243257
OneSignalCoreImpl.sharedClient().execute(request) { _ in
244258
// There is nothing to hydrate
245259
// On success, remove request from cache
246-
self.removeRequestQueue.removeAll(where: { $0 == request})
247-
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY, withValue: self.removeRequestQueue)
248-
if inBackground {
249-
OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier)
260+
self.dispatchQueue.async {
261+
self.removeRequestQueue.removeAll(where: { $0 == request})
262+
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY, withValue: self.removeRequestQueue)
263+
if inBackground {
264+
OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier)
265+
}
250266
}
251267
} onFailure: { error in
252268
OneSignalLog.onesignalLog(.LL_ERROR, message: "OSIdentityOperationExecutor remove alias request failed with error: \(error.debugDescription)")
253-
254-
if let nsError = error as? NSError {
255-
let responseType = OSNetworkingUtils.getResponseStatusType(nsError.code)
256-
if responseType != .retryable {
257-
// Fail, no retry, remove from cache and queue
258-
// A response of .missing could mean the alias doesn't exist on this user OR this user has been deleted
259-
self.removeRequestQueue.removeAll(where: { $0 == request})
260-
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY, withValue: self.removeRequestQueue)
269+
self.dispatchQueue.async {
270+
if let nsError = error as? NSError {
271+
let responseType = OSNetworkingUtils.getResponseStatusType(nsError.code)
272+
if responseType != .retryable {
273+
// Fail, no retry, remove from cache and queue
274+
// A response of .missing could mean the alias doesn't exist on this user OR this user has been deleted
275+
self.removeRequestQueue.removeAll(where: { $0 == request})
276+
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY, withValue: self.removeRequestQueue)
277+
}
278+
}
279+
if inBackground {
280+
OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier)
261281
}
262-
}
263-
if inBackground {
264-
OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier)
265282
}
266283
}
267284
}

0 commit comments

Comments
 (0)