Skip to content

Commit 7b169e8

Browse files
authored
fix(notification): add support for multiple clients (#408)
Fix NotificationCenter to support multiple clients (sdkKeys). - full thread-safety for additional concurrency requirements - no resource conflicts for multiple sdkKeys support
1 parent 5034522 commit 7b169e8

File tree

6 files changed

+332
-86
lines changed

6 files changed

+332
-86
lines changed

DemoSwiftApp/AppDelegate.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
7070
print("Optimizely SDK initiliazation failed: \(error)")
7171
case .success:
7272
print("Optimizely SDK initialized successfully!")
73-
#if !os(iOS)
7473
@unknown default:
7574
print("Optimizely SDK initiliazation failed with unknown result")
76-
#endif
7775
}
7876

7977
self.startWithRootViewController()
@@ -129,10 +127,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
129127
print("Optimizely SDK initiliazation failed: \(error)")
130128
case .success:
131129
print("Optimizely SDK initialized successfully!")
132-
#if !os(iOS)
133130
@unknown default:
134131
print("Optimizely SDK initiliazation failed with unknown result")
135-
#endif
136132
}
137133
self.startWithRootViewController()
138134

OptimizelySwiftSDK.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,7 @@
574574
6E623F0F253F9045000617D0 /* DecisionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E623F01253F9045000617D0 /* DecisionInfo.swift */; };
575575
6E636B912236C91F00AF3CEF /* Optimizely.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EBAEB6C21E3FEF800D13AA9 /* Optimizely.framework */; };
576576
6E636BA02236C96700AF3CEF /* Optimizely.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EBAEB6C21E3FEF800D13AA9 /* Optimizely.framework */; };
577+
6E6419DA2657059700C49555 /* NotificationCenterTests_MultiClients.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6419D92657059700C49555 /* NotificationCenterTests_MultiClients.swift */; };
577578
6E6BE00B237F547200FE8274 /* optimizely_config_datafile.json in Resources */ = {isa = PBXBuildFile; fileRef = 6E6BE009237F547200FE8274 /* optimizely_config_datafile.json */; };
578579
6E6BE00C237F547200FE8274 /* optimizely_config_expected.json in Resources */ = {isa = PBXBuildFile; fileRef = 6E6BE00A237F547200FE8274 /* optimizely_config_expected.json */; };
579580
6E7516A622C520D400B2B157 /* DefaultLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E75165F22C520D400B2B157 /* DefaultLogger.swift */; };
@@ -1850,6 +1851,7 @@
18501851
6E623F01253F9045000617D0 /* DecisionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecisionInfo.swift; sourceTree = "<group>"; };
18511852
6E636B8C2236C91F00AF3CEF /* OptimizelyTests-APIs-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "OptimizelyTests-APIs-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
18521853
6E636B9B2236C96700AF3CEF /* OptimizelyTests-Legacy-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "OptimizelyTests-Legacy-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
1854+
6E6419D92657059700C49555 /* NotificationCenterTests_MultiClients.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationCenterTests_MultiClients.swift; sourceTree = "<group>"; };
18531855
6E6BE009237F547200FE8274 /* optimizely_config_datafile.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = optimizely_config_datafile.json; sourceTree = "<group>"; };
18541856
6E6BE00A237F547200FE8274 /* optimizely_config_expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = optimizely_config_expected.json; sourceTree = "<group>"; };
18551857
6E75165F22C520D400B2B157 /* DefaultLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultLogger.swift; sourceTree = "<group>"; };
@@ -2236,6 +2238,7 @@
22362238
6E474C8C263C889E00ABDFF8 /* UserProfileServiceTests_MultiClients.swift */,
22372239
6EE591192649CF640013AD66 /* LoggerTests_MultiClients.swift */,
22382240
6EE5918D264AF44B0013AD66 /* HandlerRegistryServiceTests_MultiClients.swift */,
2241+
6E6419D92657059700C49555 /* NotificationCenterTests_MultiClients.swift */,
22392242
);
22402243
path = "OptimizelyTests-MultiClients";
22412244
sourceTree = "<group>";
@@ -3718,6 +3721,7 @@
37183721
6E424D0726324B620081004A /* Event.swift in Sources */,
37193722
6E424D0826324B620081004A /* ProjectConfig.swift in Sources */,
37203723
6E424D0926324B620081004A /* FeatureVariable.swift in Sources */,
3724+
6E6419DA2657059700C49555 /* NotificationCenterTests_MultiClients.swift in Sources */,
37213725
6E424D0A26324B620081004A /* Rollout.swift in Sources */,
37223726
6E2D5DAE26338CA00002077F /* AtomicDictionaryTests.swift in Sources */,
37233727
6E424D0B26324B620081004A /* Variation.swift in Sources */,

Sources/Implementation/DefaultNotificationCenter.swift

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,52 +19,46 @@ import Foundation
1919
public class DefaultNotificationCenter: OPTNotificationCenter {
2020
public var notificationId: Int {
2121
get {
22-
return notifications.notificationId
22+
var id = 0
23+
atomicListeners.performAtomic { listners in
24+
id = listners.notificationId
25+
}
26+
return id
2327
}
2428
set {
2529
_ = newValue // no setter (for suppressing SwiftLint warning)
2630
}
2731
}
2832

29-
class Notifications {
33+
class Listeners {
3034
var notificationId: Int = 1
3135
var notificationListeners = [Int: (Int, GenericListener)]()
32-
func incrementNotificationId() -> Int {
36+
37+
func add(type: Int, listener: @escaping GenericListener) -> Int {
38+
notificationListeners[notificationId] = (type, listener)
39+
3340
let returnValue = notificationId
3441
notificationId += 1
3542
return returnValue
3643
}
3744
}
3845

39-
private var _listeners: AtomicProperty<Notifications> = AtomicProperty<Notifications>()
40-
41-
var notifications: Notifications {
42-
get {
43-
return _listeners.property!
44-
}
45-
set {
46-
_listeners.property = newValue
47-
}
48-
}
46+
private var atomicListeners = AtomicProperty(property: Listeners())
4947

5048
var observerLogEvent: NSObjectProtocol?
5149

5250
required public init() {
5351
addInternalNotificationListners()
54-
notifications = Notifications()
5552
}
5653

5754
deinit {
5855
removeInternalNotificationListners()
5956
}
6057

6158
public func addGenericNotificationListener(notificationType: Int, listener: @escaping GenericListener) -> Int? {
62-
6359
var id = 0
64-
_listeners.performAtomic { (notifications) in
65-
notifications.notificationListeners[notifications.notificationId] = (notificationType, listener)
66-
67-
id = notifications.incrementNotificationId()
60+
atomicListeners.performAtomic { listners in
61+
id = listners.add(type: notificationType, listener: listener)
6862
}
6963
return id
7064
}
@@ -167,26 +161,26 @@ public class DefaultNotificationCenter: OPTNotificationCenter {
167161
}
168162

169163
public func removeNotificationListener(notificationId: Int) {
170-
_listeners.performAtomic { (listeners) in
164+
atomicListeners.performAtomic { listeners in
171165
listeners.notificationListeners.removeValue(forKey: notificationId)
172166
}
173167
}
174168

175169
public func clearNotificationListeners(type: NotificationType) {
176-
_listeners.performAtomic { (listeners) in
170+
atomicListeners.performAtomic { listeners in
177171
listeners.notificationListeners = listeners.notificationListeners.filter({$1.0 != type.rawValue})
178172
}
179173
}
180174

181175
public func clearAllNotificationListeners() {
182-
_listeners.performAtomic { (listeners) in
176+
atomicListeners.performAtomic { listeners in
183177
listeners.notificationListeners.removeAll()
184178
}
185179
}
186180

187181
public func sendNotifications(type: Int, args: [Any?]) {
188182
var selected = [GenericListener]()
189-
_listeners.performAtomic { (listeners) in
183+
atomicListeners.performAtomic { listeners in
190184
selected = listeners.notificationListeners.values.filter { $0.0 == type }.map { $0.1 }
191185
}
192186

Sources/Utils/HandlerRegistryService.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class HandlerRegistryService {
3030

3131
func registerBinding(binder: BinderProtocol) {
3232
let sk = ServiceKey(service: "\(type(of: binder.service))", sdkKey: binder.sdkKey)
33-
binders.performAtomic{ prop in
33+
binders.performAtomic { prop in
3434
if prop[sk] == nil {
3535
prop[sk] = binder
3636
}
@@ -46,7 +46,7 @@ class HandlerRegistryService {
4646
let binderToUse = binders.property?[sk]
4747

4848
func updateBinder(b: BinderProtocol) {
49-
binders.performAtomic{ prop in
49+
binders.performAtomic { prop in
5050
prop[sk] = b
5151
}
5252
}

Tests/OptimizelyTests-Common/NotificationCenterTests.swift

Lines changed: 57 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -56,63 +56,6 @@ class NotificationCenterTests: XCTestCase {
5656
super.tearDown()
5757
}
5858

59-
func sendActivate() {
60-
notificationCenter.sendNotifications(type: NotificationType.activate.rawValue, args: [experiment!, "userId", nil, variation!, ["url": "https://url.com/", "body": Data()]])
61-
}
62-
63-
func sendTrack() {
64-
notificationCenter.sendNotifications(type: NotificationType.track.rawValue, args: ["eventKey", "userId", nil, nil, ["url": "https://url.com/", "body": Data()]])
65-
}
66-
67-
func sendDecision() {
68-
notificationCenter.sendNotifications(type: NotificationType.decision.rawValue, args: [Constants.DecisionType.featureVariable.rawValue, "userId", nil, ["url": "https://url.com/", "body": Data()]])
69-
}
70-
71-
func sendDatafileChange() {
72-
notificationCenter.sendNotifications(type: NotificationType.datafileChange.rawValue, args: [Data()])
73-
}
74-
75-
func sendLogEvent() {
76-
notificationCenter.sendNotifications(type: NotificationType.logEvent.rawValue, args: ["https://url.com/", [:]])
77-
}
78-
79-
func addActivateListener() -> Int? {
80-
let id = notificationCenter.addActivateNotificationListener { (_, _, _, _, _) in
81-
self.called = true
82-
}
83-
return id
84-
}
85-
86-
func addTrackListener() -> Int? {
87-
let id = notificationCenter.addTrackNotificationListener { (_, _, _, _, _) in
88-
self.called = true
89-
}
90-
return id
91-
}
92-
93-
func addDecisionListener() -> Int? {
94-
let id = notificationCenter.addDecisionNotificationListener { (_, _, _, _) in
95-
self.called = true
96-
}
97-
return id
98-
}
99-
100-
func addDatafileChangeListener() -> Int? {
101-
let id = notificationCenter.addDatafileChangeNotificationListener { (_) in
102-
self.called = true
103-
}
104-
return id
105-
}
106-
107-
func addLogEventListener() -> Int? {
108-
let id = notificationCenter.addLogEventNotificationListener { (_, _) in
109-
self.called = true
110-
}
111-
return id
112-
}
113-
114-
115-
11659
func testNotificationCenterAddRemoveActivate() {
11760
called = false
11861

@@ -365,4 +308,61 @@ class NotificationCenterTests: XCTestCase {
365308
XCTAssertFalse(called)
366309
}
367310

311+
// MARK: - Utils
312+
313+
func sendActivate() {
314+
notificationCenter.sendNotifications(type: NotificationType.activate.rawValue, args: [experiment!, "userId", nil, variation!, ["url": "https://url.com/", "body": Data()]])
315+
}
316+
317+
func sendTrack() {
318+
notificationCenter.sendNotifications(type: NotificationType.track.rawValue, args: ["eventKey", "userId", nil, nil, ["url": "https://url.com/", "body": Data()]])
319+
}
320+
321+
func sendDecision() {
322+
notificationCenter.sendNotifications(type: NotificationType.decision.rawValue, args: [Constants.DecisionType.featureVariable.rawValue, "userId", nil, ["url": "https://url.com/", "body": Data()]])
323+
}
324+
325+
func sendDatafileChange() {
326+
notificationCenter.sendNotifications(type: NotificationType.datafileChange.rawValue, args: [Data()])
327+
}
328+
329+
func sendLogEvent() {
330+
notificationCenter.sendNotifications(type: NotificationType.logEvent.rawValue, args: ["https://url.com/", [:]])
331+
}
332+
333+
func addActivateListener() -> Int? {
334+
let id = notificationCenter.addActivateNotificationListener { (_, _, _, _, _) in
335+
self.called = true
336+
}
337+
return id
338+
}
339+
340+
func addTrackListener() -> Int? {
341+
let id = notificationCenter.addTrackNotificationListener { (_, _, _, _, _) in
342+
self.called = true
343+
}
344+
return id
345+
}
346+
347+
func addDecisionListener() -> Int? {
348+
let id = notificationCenter.addDecisionNotificationListener { (_, _, _, _) in
349+
self.called = true
350+
}
351+
return id
352+
}
353+
354+
func addDatafileChangeListener() -> Int? {
355+
let id = notificationCenter.addDatafileChangeNotificationListener { (_) in
356+
self.called = true
357+
}
358+
return id
359+
}
360+
361+
func addLogEventListener() -> Int? {
362+
let id = notificationCenter.addLogEventNotificationListener { (_, _) in
363+
self.called = true
364+
}
365+
return id
366+
}
367+
368368
}

0 commit comments

Comments
 (0)