From 1e559e80516ebc74f2bb068bdb7d4c787f01f56a Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Fri, 11 Aug 2023 12:02:41 -0400 Subject: [PATCH 1/7] feat: add event subscription interface to iOS SDK --- DevCycle.xcodeproj/project.pbxproj | 4 + DevCycle/DevCycleClient.swift | 27 ++++-- DevCycle/SSEConnection.swift | 4 +- DevCycle/Utils/EventEmitter.swift | 82 +++++++++++++++++++ .../DevCycleManager.swift | 8 ++ 5 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 DevCycle/Utils/EventEmitter.swift diff --git a/DevCycle.xcodeproj/project.pbxproj b/DevCycle.xcodeproj/project.pbxproj index 3696f281..5cd2e805 100644 --- a/DevCycle.xcodeproj/project.pbxproj +++ b/DevCycle.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 0B721685290B21CD004D0AB7 /* SSEMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B721684290B21CD004D0AB7 /* SSEMessage.swift */; }; 0F2DD75A279EFA6B00540C9D /* CustomData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F2DD759279EFA6B00540C9D /* CustomData.swift */; }; + 0FD643D52A8680E4004BB036 /* EventEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FD643D42A8680E4004BB036 /* EventEmitter.swift */; }; 52133B2028DDFB260007691D /* RequestConsolidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52133B1F28DDFB260007691D /* RequestConsolidatorTests.swift */; }; 52133B2228DE00BC0007691D /* ProcessConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52133B2128DE00BC0007691D /* ProcessConfig.swift */; }; 52133B2428DE0FEB0007691D /* GetTestConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52133B2328DE0FEB0007691D /* GetTestConfig.swift */; }; @@ -82,6 +83,7 @@ /* Begin PBXFileReference section */ 0B721684290B21CD004D0AB7 /* SSEMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSEMessage.swift; sourceTree = ""; }; 0F2DD759279EFA6B00540C9D /* CustomData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomData.swift; sourceTree = ""; }; + 0FD643D42A8680E4004BB036 /* EventEmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventEmitter.swift; sourceTree = ""; }; 52133B1F28DDFB260007691D /* RequestConsolidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestConsolidatorTests.swift; sourceTree = ""; }; 52133B2128DE00BC0007691D /* ProcessConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessConfig.swift; sourceTree = ""; }; 52133B2328DE0FEB0007691D /* GetTestConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetTestConfig.swift; sourceTree = ""; }; @@ -166,6 +168,7 @@ 529CE32228DCBEC2009AB137 /* RequestConsolidator.swift */, 52133B2128DE00BC0007691D /* ProcessConfig.swift */, 5226DF05290C588900630745 /* NotificationNames.swift */, + 0FD643D42A8680E4004BB036 /* EventEmitter.swift */, ); path = Utils; sourceTree = ""; @@ -444,6 +447,7 @@ 5276C9F0275E682B00B9A324 /* DevCycleOptions.swift in Sources */, 524D58242770F78B00D7CC56 /* ObjCDVCVariable.swift in Sources */, 52133B2228DE00BC0007691D /* ProcessConfig.swift in Sources */, + 0FD643D52A8680E4004BB036 /* EventEmitter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/DevCycle/DevCycleClient.swift b/DevCycle/DevCycleClient.swift index 854de052..6ca7a14f 100644 --- a/DevCycle/DevCycleClient.swift +++ b/DevCycle/DevCycleClient.swift @@ -28,6 +28,7 @@ public typealias IdentifyCompletedHandler = (Error?, [String: Variable]?) -> Voi public typealias FlushCompletedHandler = (Error?) -> Void public typealias CloseCompletedHandler = () -> Void + public class DevCycleClient { var sdkKey: String? var user: DevCycleUser? @@ -42,6 +43,7 @@ public class DevCycleClient { private var enableEdgeDB: Bool = false var inactivityDelayMS: Double = 120000 + let eventEmitter: EventEmitter = EventEmitter() private var service: DevCycleServiceProtocol? private var cacheService: CacheServiceProtocol = CacheService() private var cache: Cache? @@ -140,9 +142,14 @@ public class DevCycleClient { self.service?.getConfig(user: user, enableEdgeDB: self.enableEdgeDB, extraParams: nil, completion: { [weak self] config, error in guard let self = self else { return } + var requestErrored = false + if let error = error { Log.error("Error getting config: \(error)", tags: ["setup"]) self.cache = self.cacheService.load() + + self.eventEmitter.emitError(error) + requestErrored = true } else { if let config = config { Log.debug("Config: \(config)", tags: ["setup"]) @@ -165,14 +172,14 @@ public class DevCycleClient { } } - self.setupSSEConnection() + self.configCompletionHandlers.forEach { handler in handler(error) } + self.configCompletionHandlers = [] - for handler in self.configCompletionHandlers { - handler(error) - } - callback?(error) self.initialized = true - self.configCompletionHandlers = [] + callback?(error) + self.eventEmitter.emitInitialized(!requestErrored) + + self.setupSSEConnection() }) self.flushTimer = Timer.scheduledTimer( @@ -421,6 +428,14 @@ public class DevCycleClient { public func allVariables() -> [String: Variable] { return self.config?.userConfig?.variables ?? [:] } + + public func subscribe(_ handler: SubscribeHandlers) { + self.eventEmitter.subscribe(handler) + } + + public func unsubscribe(_ handler: SubscribeHandlers) { + self.eventEmitter.unsubscribe(handler) + } public func track(_ event: DevCycleEvent) { if (self.closed) { diff --git a/DevCycle/SSEConnection.swift b/DevCycle/SSEConnection.swift index cc2de5d7..ef0ae5df 100644 --- a/DevCycle/SSEConnection.swift +++ b/DevCycle/SSEConnection.swift @@ -9,6 +9,8 @@ import Foundation import LDSwiftEventSource +typealias LDEventHandler = LDSwiftEventSource.EventHandler + public typealias MessageHandler = (String) -> Void protocol SSEConnectionProtocol { @@ -63,7 +65,7 @@ class SSEConnection: SSEConnectionProtocol { } } -class Handler: EventHandler { +class Handler: LDEventHandler { private var handler: MessageHandler init(handler: @escaping MessageHandler) { diff --git a/DevCycle/Utils/EventEmitter.swift b/DevCycle/Utils/EventEmitter.swift new file mode 100644 index 00000000..3aad02f7 --- /dev/null +++ b/DevCycle/Utils/EventEmitter.swift @@ -0,0 +1,82 @@ +// +// EventEmitter.swift +// DevCycle +// + +public typealias EventHandler = (Any?) -> Void + +public typealias SubscribeErrorHandler = (Error) -> Void +public typealias SubscribeInitializedHandler = (Bool) -> Void + +public struct SubscribeError { + // Had to add this index field, because I can't make this struct Equitable to do a equality comparison + // because you can't compare anon function pointers... so it was either this or generate a UUID to compare. + fileprivate var index: Int? + var handler: SubscribeErrorHandler + let key = "error" + + + public init(_ handler: @escaping SubscribeErrorHandler) { + self.handler = handler + } +} + +public struct SubscribeInitialized { + fileprivate var index: Int? + var handler: SubscribeInitializedHandler + let key = "initialized" + + public init(_ handler: @escaping SubscribeInitializedHandler) { + self.handler = handler + } +} + +public enum SubscribeHandlers { + case error(SubscribeError) + case initialized(SubscribeInitialized) +} + +class EventEmitter { + var errorHandlers: [SubscribeError] = [] + var initHandlers: [SubscribeInitialized] = [] + + func subscribe(_ handler: SubscribeHandlers) { + switch handler { + case .error(var errorHandler): + self.errorHandlers.append(errorHandler) + errorHandler.index = self.errorHandlers.count-1 + break + case .initialized(var initHandler): + self.initHandlers.append(initHandler) + initHandler.index = self.initHandlers.count-1 + break + } + } + + func unsubscribe(_ handler: SubscribeHandlers) { + switch handler { + case .error(let errorHandler): + if let index = errorHandler.index { + self.errorHandlers.remove(at: index) + } + break + case .initialized(let initHandler): + if let index = initHandler.index { + self.initHandlers.remove(at: index) + } + break + } + } + + func emitError(_ err: Error) { + self.errorHandlers.forEach { errSub in + errSub.handler(err) + } + } + + func emitInitialized(_ success: Bool) { + self.initHandlers.forEach { initSub in + initSub.handler(success) + } + } +} diff --git a/Examples/DevCycle-iOS-Example-App-Swift/DevCycle-iOS-Example-App-Swift/DevCycleManager.swift b/Examples/DevCycle-iOS-Example-App-Swift/DevCycle-iOS-Example-App-Swift/DevCycleManager.swift index 13027a39..df7b078d 100644 --- a/Examples/DevCycle-iOS-Example-App-Swift/DevCycle-iOS-Example-App-Swift/DevCycleManager.swift +++ b/Examples/DevCycle-iOS-Example-App-Swift/DevCycle-iOS-Example-App-Swift/DevCycleManager.swift @@ -30,5 +30,13 @@ class DevCycleManager { return } self.client = client + + + client.subscribe(SubscribeHandlers.error(SubscribeError { error in + print("DevCycle Error: \(error.localizedDescription)") + })) + client.subscribe(SubscribeHandlers.initialized(SubscribeInitialized({ success in + print("DevCycle Initialized: \(success) from subscription") + }))) } } From f63040bd9047656b068be0fd7aec569c442bb6f8 Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Fri, 11 Aug 2023 12:18:44 -0400 Subject: [PATCH 2/7] fix: cleanup type names --- DevCycle/DevCycleClient.swift | 4 +-- DevCycle/Utils/EventEmitter.swift | 25 ++++++++----------- .../DevCycleManager.swift | 6 ++--- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/DevCycle/DevCycleClient.swift b/DevCycle/DevCycleClient.swift index 6ca7a14f..18692be5 100644 --- a/DevCycle/DevCycleClient.swift +++ b/DevCycle/DevCycleClient.swift @@ -429,11 +429,11 @@ public class DevCycleClient { return self.config?.userConfig?.variables ?? [:] } - public func subscribe(_ handler: SubscribeHandlers) { + public func subscribe(_ handler: DevCycleEventHandlers) { self.eventEmitter.subscribe(handler) } - public func unsubscribe(_ handler: SubscribeHandlers) { + public func unsubscribe(_ handler: DevCycleEventHandlers) { self.eventEmitter.unsubscribe(handler) } diff --git a/DevCycle/Utils/EventEmitter.swift b/DevCycle/Utils/EventEmitter.swift index 3aad02f7..bfb10353 100644 --- a/DevCycle/Utils/EventEmitter.swift +++ b/DevCycle/Utils/EventEmitter.swift @@ -8,39 +8,36 @@ public typealias EventHandler = (Any?) -> Void public typealias SubscribeErrorHandler = (Error) -> Void public typealias SubscribeInitializedHandler = (Bool) -> Void -public struct SubscribeError { +public struct ErrorHandler { // Had to add this index field, because I can't make this struct Equitable to do a equality comparison // because you can't compare anon function pointers... so it was either this or generate a UUID to compare. fileprivate var index: Int? var handler: SubscribeErrorHandler - let key = "error" - public init(_ handler: @escaping SubscribeErrorHandler) { self.handler = handler } } -public struct SubscribeInitialized { +public struct InitializedHandler { fileprivate var index: Int? var handler: SubscribeInitializedHandler - let key = "initialized" public init(_ handler: @escaping SubscribeInitializedHandler) { self.handler = handler } } -public enum SubscribeHandlers { - case error(SubscribeError) - case initialized(SubscribeInitialized) +public enum DevCycleEventHandlers { + case error(ErrorHandler) + case initialized(InitializedHandler) } class EventEmitter { - var errorHandlers: [SubscribeError] = [] - var initHandlers: [SubscribeInitialized] = [] + var errorHandlers: [ErrorHandler] = [] + var initHandlers: [InitializedHandler] = [] - func subscribe(_ handler: SubscribeHandlers) { + func subscribe(_ handler: DevCycleEventHandlers) { switch handler { case .error(var errorHandler): self.errorHandlers.append(errorHandler) @@ -53,7 +50,7 @@ class EventEmitter { } } - func unsubscribe(_ handler: SubscribeHandlers) { + func unsubscribe(_ handler: DevCycleEventHandlers) { switch handler { case .error(let errorHandler): if let index = errorHandler.index { @@ -69,9 +66,7 @@ class EventEmitter { } func emitError(_ err: Error) { - self.errorHandlers.forEach { errSub in - errSub.handler(err) - } + self.errorHandlers.forEach { errHandler in errHandler.handler(err) } } func emitInitialized(_ success: Bool) { diff --git a/Examples/DevCycle-iOS-Example-App-Swift/DevCycle-iOS-Example-App-Swift/DevCycleManager.swift b/Examples/DevCycle-iOS-Example-App-Swift/DevCycle-iOS-Example-App-Swift/DevCycleManager.swift index df7b078d..7617fec0 100644 --- a/Examples/DevCycle-iOS-Example-App-Swift/DevCycle-iOS-Example-App-Swift/DevCycleManager.swift +++ b/Examples/DevCycle-iOS-Example-App-Swift/DevCycle-iOS-Example-App-Swift/DevCycleManager.swift @@ -32,11 +32,11 @@ class DevCycleManager { self.client = client - client.subscribe(SubscribeHandlers.error(SubscribeError { error in + client.subscribe(DevCycleEventHandlers.error(ErrorHandler { error in print("DevCycle Error: \(error.localizedDescription)") })) - client.subscribe(SubscribeHandlers.initialized(SubscribeInitialized({ success in + client.subscribe(DevCycleEventHandlers.initialized(InitializedHandler { success in print("DevCycle Initialized: \(success) from subscription") - }))) + })) } } From 27162fa4623d383da3f0e1e0ecb83d14624652c2 Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Mon, 14 Aug 2023 10:38:52 -0400 Subject: [PATCH 3/7] feat: implement all EventEmitter handler types --- DevCycle/DevCycleClient.swift | 18 +- DevCycle/Utils/EventEmitter.swift | 186 +++++++++++++----- .../DevCycleManager.swift | 19 +- 3 files changed, 166 insertions(+), 57 deletions(-) diff --git a/DevCycle/DevCycleClient.swift b/DevCycle/DevCycleClient.swift index 18692be5..fbe16b66 100644 --- a/DevCycle/DevCycleClient.swift +++ b/DevCycle/DevCycleClient.swift @@ -148,7 +148,7 @@ public class DevCycleClient { Log.error("Error getting config: \(error)", tags: ["setup"]) self.cache = self.cacheService.load() - self.eventEmitter.emitError(error) + self.eventEmitter.emit(EventEmitValues.error(error)) requestErrored = true } else { if let config = config { @@ -177,7 +177,7 @@ public class DevCycleClient { self.initialized = true callback?(error) - self.eventEmitter.emitInitialized(!requestErrored) + self.eventEmitter.emit(EventEmitValues.initialized(!requestErrored)) self.setupSSEConnection() }) @@ -429,12 +429,18 @@ public class DevCycleClient { return self.config?.userConfig?.variables ?? [:] } - public func subscribe(_ handler: DevCycleEventHandlers) { - self.eventEmitter.subscribe(handler) + public func subscribe(_ handler: InitializedEventHandler) { + self.eventEmitter.subscribe(EventHandlers.initialized(handler)) + } + public func subscribe(_ handler: ErrorEventHandler) { + self.eventEmitter.subscribe(EventHandlers.error(handler)) } - public func unsubscribe(_ handler: DevCycleEventHandlers) { - self.eventEmitter.unsubscribe(handler) + public func unsubscribe(_ handler: InitializedEventHandler) { + self.eventEmitter.unsubscribe(EventHandlers.initialized(handler)) + } + public func unsubscribe(_ handler: ErrorEventHandler) { + self.eventEmitter.unsubscribe(EventHandlers.error(handler)) } public func track(_ event: DevCycleEvent) { diff --git a/DevCycle/Utils/EventEmitter.swift b/DevCycle/Utils/EventEmitter.swift index bfb10353..c8d878c6 100644 --- a/DevCycle/Utils/EventEmitter.swift +++ b/DevCycle/Utils/EventEmitter.swift @@ -3,75 +3,169 @@ // DevCycle // -public typealias EventHandler = (Any?) -> Void +public typealias VariableSet = [String: Variable] -public typealias SubscribeErrorHandler = (Error) -> Void -public typealias SubscribeInitializedHandler = (Bool) -> Void +public typealias ErrorHandlerCallback = (Error) -> Void +public typealias InitializedHandlerCallback = (Bool) -> Void +public typealias ConfigUpdatedHandlerCallback = (VariableSet) -> Void +public typealias VariableUpdatedHandlerCallback = (String, Variable) -> Void +public typealias VariableEvaluatedHandlerCallback = (String, DVCVariable) -> Void +public typealias FeatureUpdatedHandlerCallback = (String, Feature) -> Void -public struct ErrorHandler { - // Had to add this index field, because I can't make this struct Equitable to do a equality comparison - // because you can't compare anon function pointers... so it was either this or generate a UUID to compare. - fileprivate var index: Int? - var handler: SubscribeErrorHandler +public class BaseHandler: Equatable { + public static func == (lhs: BaseHandler, rhs: BaseHandler) -> Bool { + return lhs === rhs + } + + let callback: T - public init(_ handler: @escaping SubscribeErrorHandler) { - self.handler = handler + public init(_ handler: T) { + self.callback = handler } } -public struct InitializedHandler { - fileprivate var index: Int? - var handler: SubscribeInitializedHandler - - public init(_ handler: @escaping SubscribeInitializedHandler) { - self.handler = handler - } +public typealias ErrorEventHandler = BaseHandler +public typealias InitializedEventHandler = BaseHandler +public typealias ConfigUpdatedEventHandler = BaseHandler +public typealias VariableUpdatedHandler = BaseHandler +public typealias VariableEvaluatedHandler = BaseHandler +public typealias FeatureUpdatedHandler = BaseHandler + +enum EventHandlers { + case error(ErrorEventHandler) + case initialized(InitializedEventHandler) + case configUpdated(ConfigUpdatedEventHandler) + case variableUpdated(String?, VariableUpdatedHandler) + case variableEvaluated(String?, VariableEvaluatedHandler) + case featureUpdated(String?, FeatureUpdatedHandler) } -public enum DevCycleEventHandlers { - case error(ErrorHandler) - case initialized(InitializedHandler) +enum EventEmitValues { + case error(Error) + case initialized(Bool) + case configUpdated(VariableSet) + case variableUpdated(String, Variable) + case variableEvaluated(String, DVCVariable) + case featureUpdated(String, Feature) } class EventEmitter { - var errorHandlers: [ErrorHandler] = [] - var initHandlers: [InitializedHandler] = [] - - func subscribe(_ handler: DevCycleEventHandlers) { + var errorHandlers: [ErrorEventHandler] = [] + var initHandlers: [InitializedEventHandler] = [] + var configUpdatedHandlers: [ConfigUpdatedEventHandler] = [] + var variableUpdatedHandlers: [String : [VariableUpdatedHandler]] = [:] + var allVariableUpdatedHandlers: [VariableUpdatedHandler] = [] + var variableEvaluatedHandlers: [String : [VariableEvaluatedHandler]] = [:] + var allVariableEvaluatedHandlers: [VariableEvaluatedHandler] = [] + var featureUpdatedHandlers: [String : [FeatureUpdatedHandler]] = [:] + var allFeatureUpdatedHandlers: [FeatureUpdatedHandler] = [] + + func subscribe(_ handler: EventHandlers) { switch handler { - case .error(var errorHandler): - self.errorHandlers.append(errorHandler) - errorHandler.index = self.errorHandlers.count-1 - break - case .initialized(var initHandler): - self.initHandlers.append(initHandler) - initHandler.index = self.initHandlers.count-1 - break + case .error(let handler): + self.errorHandlers.append(handler) + case .initialized(let handler): + self.initHandlers.append(handler) + case .configUpdated(let handler): + self.configUpdatedHandlers.append(handler) + case .variableUpdated(let key, let handler): + if let key = key { + subscribeByKey(key, handler: handler, handlersByKey: &self.variableUpdatedHandlers) + } else { + self.allVariableUpdatedHandlers.append(handler) + } + case .variableEvaluated(let key, let handler): + if let key = key { + subscribeByKey(key, handler: handler, handlersByKey: &self.variableEvaluatedHandlers) + } else { + self.allVariableEvaluatedHandlers.append(handler) + } + case .featureUpdated(let key, let handler): + if let key = key { + subscribeByKey(key, handler: handler, handlersByKey: &self.featureUpdatedHandlers) + } else { + self.allFeatureUpdatedHandlers.append(handler) + } + } + } + + private func subscribeByKey(_ key: String, handler: T, handlersByKey: inout [String: [T]]) { + if var handlers = handlersByKey[key] { + handlers.append(handler) + } else { + handlersByKey[key] = [handler] } } - func unsubscribe(_ handler: DevCycleEventHandlers) { + func unsubscribe(_ handler: EventHandlers) { switch handler { - case .error(let errorHandler): - if let index = errorHandler.index { - self.errorHandlers.remove(at: index) + case .error(let handler): + unsubscribeHandler(handler, handlers: &self.errorHandlers) + case .initialized(let handler): + unsubscribeHandler(handler, handlers: &self.initHandlers) + case .configUpdated(let handler): + unsubscribeHandler(handler, handlers: &self.configUpdatedHandlers) + case .variableUpdated(let key, let handler): + if let key = key { + unsubscribeHandlerByKey(key, handler: handler, handlersByKey: &self.variableUpdatedHandlers) + } else { + unsubscribeHandler(handler, handlers: &self.allVariableUpdatedHandlers) + } + case .variableEvaluated(let key, let handler): + if let key = key { + unsubscribeHandlerByKey(key, handler: handler, handlersByKey: &self.variableEvaluatedHandlers) + } else { + unsubscribeHandler(handler, handlers: &self.allVariableEvaluatedHandlers) } - break - case .initialized(let initHandler): - if let index = initHandler.index { - self.initHandlers.remove(at: index) + case .featureUpdated(let key, let handler): + if let key = key { + unsubscribeHandlerByKey(key, handler: handler, handlersByKey: &self.featureUpdatedHandlers) + } else { + unsubscribeHandler(handler, handlers: &self.allVariableEvaluatedHandlers) } - break } } - func emitError(_ err: Error) { - self.errorHandlers.forEach { errHandler in errHandler.handler(err) } + private func unsubscribeHandlerByKey(_ key: String, handler: T, handlersByKey: inout [String: [T]]) { + if var handlers = handlersByKey[key] { + unsubscribeHandler(handler, handlers: &handlers) + } } - func emitInitialized(_ success: Bool) { - self.initHandlers.forEach { initSub in - initSub.handler(success) + private func unsubscribeHandler(_ handler: T, handlers: inout [T]) { + if let index = handlers.firstIndex(where: { $0 == handler }) { + handlers.remove(at: index) } } + + func emit(_ emitValues: EventEmitValues) { + switch emitValues { + case .error(let err): + self.errorHandlers.forEach { handler in handler.callback(err) } + case .initialized(let initialized): + self.initHandlers.forEach { handler in handler.callback(initialized) } + case .configUpdated(let variableSet): + self.configUpdatedHandlers.forEach { handler in handler.callback(variableSet) } + case .variableUpdated(let key, let variable): + if let handlers = self.variableUpdatedHandlers[key] { + handlers.forEach { handler in handler.callback(key, variable) } + } + case .variableEvaluated(let key, let variable): + if let handlers = self.variableEvaluatedHandlers[key] { + handlers.forEach { handler in handler.callback(key, variable) } + } + allVariableEvaluatedHandlers.forEach { handler in handler.callback(key, variable) } + case .featureUpdated(let key, let feature): + if let handlers = self.featureUpdatedHandlers[key] { + handlers.forEach { handler in handler.callback(key, feature) } + } + allFeatureUpdatedHandlers.forEach { handler in handler.callback(key, feature) } + } + } + +// private func emitValuesByKey, V>(_ key: String, variable: V, handlersByKey: [String : [T]]) { +// if let handlers = handlersByKey[key] { +// handlers.forEach { handler in handler.callback(key, variable) } +// } +// } } diff --git a/Examples/DevCycle-iOS-Example-App-Swift/DevCycle-iOS-Example-App-Swift/DevCycleManager.swift b/Examples/DevCycle-iOS-Example-App-Swift/DevCycle-iOS-Example-App-Swift/DevCycleManager.swift index 7617fec0..e35b60e9 100644 --- a/Examples/DevCycle-iOS-Example-App-Swift/DevCycle-iOS-Example-App-Swift/DevCycleManager.swift +++ b/Examples/DevCycle-iOS-Example-App-Swift/DevCycle-iOS-Example-App-Swift/DevCycleManager.swift @@ -31,12 +31,21 @@ class DevCycleManager { } self.client = client - - client.subscribe(DevCycleEventHandlers.error(ErrorHandler { error in + let errHandler = ErrorEventHandler { error in print("DevCycle Error: \(error.localizedDescription)") - })) - client.subscribe(DevCycleEventHandlers.initialized(InitializedHandler { success in + } + client.subscribe(errHandler) + + let initHandler = InitializedEventHandler { success in print("DevCycle Initialized: \(success) from subscription") - })) + } + client.subscribe(initHandler) + + + Timer.scheduledTimer(withTimeInterval: TimeInterval(15), repeats: false) { timer in + print("Cleanup handlers") + client.unsubscribe(errHandler) + client.unsubscribe(initHandler) + } } } From 809e546811735e3dbb8a087f4234301151b09964 Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Thu, 17 Aug 2023 16:18:34 -0400 Subject: [PATCH 4/7] feat: added all subscribe / unsubscribe overloads --- DevCycle/DevCycleClient.swift | 24 ++++++++++ DevCycle/Utils/EventEmitter.swift | 47 +++++++++++-------- .../DevCycleManager.swift | 25 ++++++++++ 3 files changed, 77 insertions(+), 19 deletions(-) diff --git a/DevCycle/DevCycleClient.swift b/DevCycle/DevCycleClient.swift index fbe16b66..b7efac99 100644 --- a/DevCycle/DevCycleClient.swift +++ b/DevCycle/DevCycleClient.swift @@ -435,6 +435,18 @@ public class DevCycleClient { public func subscribe(_ handler: ErrorEventHandler) { self.eventEmitter.subscribe(EventHandlers.error(handler)) } + public func subscribe(_ handler: ConfigUpdatedEventHandler) { + self.eventEmitter.subscribe(EventHandlers.configUpdated(handler)) + } + public func subscribe(_ handler: VariableUpdatedHandler) { + self.eventEmitter.subscribe(EventHandlers.variableUpdated(handler)) + } + public func subscribe(_ handler: VariableEvaluatedHandler) { + self.eventEmitter.subscribe(EventHandlers.variableEvaluated(handler)) + } + public func subscribe(_ handler: FeatureUpdatedHandler) { + self.eventEmitter.subscribe(EventHandlers.featureUpdated(handler)) + } public func unsubscribe(_ handler: InitializedEventHandler) { self.eventEmitter.unsubscribe(EventHandlers.initialized(handler)) @@ -442,6 +454,18 @@ public class DevCycleClient { public func unsubscribe(_ handler: ErrorEventHandler) { self.eventEmitter.unsubscribe(EventHandlers.error(handler)) } + public func unsubscribe(_ handler: ConfigUpdatedEventHandler) { + self.eventEmitter.unsubscribe(EventHandlers.configUpdated(handler)) + } + public func unsubscribe(_ handler: VariableUpdatedHandler) { + self.eventEmitter.unsubscribe(EventHandlers.variableUpdated(handler)) + } + public func unsubscribe(_ handler: VariableEvaluatedHandler) { + self.eventEmitter.unsubscribe(EventHandlers.variableEvaluated(handler)) + } + public func unsubscribe(_ handler: FeatureUpdatedHandler) { + self.eventEmitter.unsubscribe(EventHandlers.featureUpdated(handler)) + } public func track(_ event: DevCycleEvent) { if (self.closed) { diff --git a/DevCycle/Utils/EventEmitter.swift b/DevCycle/Utils/EventEmitter.swift index c8d878c6..b41f5236 100644 --- a/DevCycle/Utils/EventEmitter.swift +++ b/DevCycle/Utils/EventEmitter.swift @@ -24,20 +24,29 @@ public class BaseHandler: Equatable { } } +public class BaseHandlerWithKey: BaseHandler { + let key: String? + + public init(key: String?, handler: T) { + self.key = key + super.init(handler) + } +} + public typealias ErrorEventHandler = BaseHandler public typealias InitializedEventHandler = BaseHandler public typealias ConfigUpdatedEventHandler = BaseHandler -public typealias VariableUpdatedHandler = BaseHandler -public typealias VariableEvaluatedHandler = BaseHandler -public typealias FeatureUpdatedHandler = BaseHandler +public typealias VariableUpdatedHandler = BaseHandlerWithKey +public typealias VariableEvaluatedHandler = BaseHandlerWithKey +public typealias FeatureUpdatedHandler = BaseHandlerWithKey enum EventHandlers { case error(ErrorEventHandler) case initialized(InitializedEventHandler) case configUpdated(ConfigUpdatedEventHandler) - case variableUpdated(String?, VariableUpdatedHandler) - case variableEvaluated(String?, VariableEvaluatedHandler) - case featureUpdated(String?, FeatureUpdatedHandler) + case variableUpdated(VariableUpdatedHandler) + case variableEvaluated(VariableEvaluatedHandler) + case featureUpdated(FeatureUpdatedHandler) } enum EventEmitValues { @@ -68,20 +77,20 @@ class EventEmitter { self.initHandlers.append(handler) case .configUpdated(let handler): self.configUpdatedHandlers.append(handler) - case .variableUpdated(let key, let handler): - if let key = key { + case .variableUpdated(let handler): + if let key = handler.key { subscribeByKey(key, handler: handler, handlersByKey: &self.variableUpdatedHandlers) } else { self.allVariableUpdatedHandlers.append(handler) } - case .variableEvaluated(let key, let handler): - if let key = key { + case .variableEvaluated(let handler): + if let key = handler.key { subscribeByKey(key, handler: handler, handlersByKey: &self.variableEvaluatedHandlers) } else { self.allVariableEvaluatedHandlers.append(handler) } - case .featureUpdated(let key, let handler): - if let key = key { + case .featureUpdated(let handler): + if let key = handler.key { subscribeByKey(key, handler: handler, handlersByKey: &self.featureUpdatedHandlers) } else { self.allFeatureUpdatedHandlers.append(handler) @@ -105,23 +114,23 @@ class EventEmitter { unsubscribeHandler(handler, handlers: &self.initHandlers) case .configUpdated(let handler): unsubscribeHandler(handler, handlers: &self.configUpdatedHandlers) - case .variableUpdated(let key, let handler): - if let key = key { + case .variableUpdated(let handler): + if let key = handler.key { unsubscribeHandlerByKey(key, handler: handler, handlersByKey: &self.variableUpdatedHandlers) } else { unsubscribeHandler(handler, handlers: &self.allVariableUpdatedHandlers) } - case .variableEvaluated(let key, let handler): - if let key = key { + case .variableEvaluated(let handler): + if let key = handler.key { unsubscribeHandlerByKey(key, handler: handler, handlersByKey: &self.variableEvaluatedHandlers) } else { unsubscribeHandler(handler, handlers: &self.allVariableEvaluatedHandlers) } - case .featureUpdated(let key, let handler): - if let key = key { + case .featureUpdated(let handler): + if let key = handler.key { unsubscribeHandlerByKey(key, handler: handler, handlersByKey: &self.featureUpdatedHandlers) } else { - unsubscribeHandler(handler, handlers: &self.allVariableEvaluatedHandlers) + unsubscribeHandler(handler, handlers: &self.allFeatureUpdatedHandlers) } } } diff --git a/Examples/DevCycle-iOS-Example-App-Swift/DevCycle-iOS-Example-App-Swift/DevCycleManager.swift b/Examples/DevCycle-iOS-Example-App-Swift/DevCycle-iOS-Example-App-Swift/DevCycleManager.swift index e35b60e9..062520e4 100644 --- a/Examples/DevCycle-iOS-Example-App-Swift/DevCycle-iOS-Example-App-Swift/DevCycleManager.swift +++ b/Examples/DevCycle-iOS-Example-App-Swift/DevCycle-iOS-Example-App-Swift/DevCycleManager.swift @@ -41,11 +41,36 @@ class DevCycleManager { } client.subscribe(initHandler) + let configHandler = ConfigUpdatedEventHandler { variables in + print("DevCycle Config Updated: \(variables)") + } + client.subscribe(configHandler) + + let variableUpdatedHandler = VariableUpdatedHandler(key: nil) { key, variable in + print("DevCycle Variable \(key) Updated: \(variable)") + } + client.subscribe(variableUpdatedHandler) + + let variableEvalHandler = VariableEvaluatedHandler(key: nil) { key, variable in + print("DevCycle Variable \(key) Evaluated: \(variable)") + } + client.subscribe(variableEvalHandler) + + let featureUpdatedHandler = FeatureUpdatedHandler(key: nil) { key, feature in + print("DevCycle Feature \(key) Updated: \(feature)") + } + client.subscribe(featureUpdatedHandler) + + Timer.scheduledTimer(withTimeInterval: TimeInterval(15), repeats: false) { timer in print("Cleanup handlers") client.unsubscribe(errHandler) client.unsubscribe(initHandler) + client.unsubscribe(configHandler) + client.unsubscribe(variableUpdatedHandler) + client.unsubscribe(variableEvalHandler) + client.unsubscribe(featureUpdatedHandler) } } } From a2b8079bc74304c01478bb28a792979803b08ca3 Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Thu, 17 Aug 2023 16:18:13 -0400 Subject: [PATCH 5/7] fix: WIP update types --- DevCycle/DVCVariable.swift | 10 ++- DevCycle/DevCycleClient.swift | 79 ++++++++++++++------- DevCycle/Models/UserConfig.swift | 112 +++++++++++++++++++++++------- DevCycle/Utils/EventEmitter.swift | 68 ++++++++++++++---- 4 files changed, 202 insertions(+), 67 deletions(-) diff --git a/DevCycle/DVCVariable.swift b/DevCycle/DVCVariable.swift index d9cb2e2f..fe92e0a9 100644 --- a/DevCycle/DVCVariable.swift +++ b/DevCycle/DVCVariable.swift @@ -107,8 +107,8 @@ public class DVCVariable { let oldValue = self.value self.value = value self.isDefaulted = false - if let handler = self.handler, - !isEqual(oldValue, variable.value) { + + if let handler = self.handler, !isEqual(oldValue, variable.value) { handler(value) } } else { @@ -136,7 +136,11 @@ public class DVCVariable { } private func addNotificationObserver() { - NotificationCenter.default.addObserver(self, selector: #selector(propertyChange(notification:)), name: Notification.Name(NotificationNames.NewUserConfig), object: nil) + NotificationCenter.default.addObserver( + self, + selector: #selector(propertyChange(notification:)), + name: Notification.Name(NotificationNames.NewUserConfig), + object: nil) } public func onUpdate(handler: @escaping VariableValueHandler) -> DVCVariable { diff --git a/DevCycle/DevCycleClient.swift b/DevCycle/DevCycleClient.swift index b7efac99..337626ac 100644 --- a/DevCycle/DevCycleClient.swift +++ b/DevCycle/DevCycleClient.swift @@ -28,6 +28,8 @@ public typealias IdentifyCompletedHandler = (Error?, [String: Variable]?) -> Voi public typealias FlushCompletedHandler = (Error?) -> Void public typealias CloseCompletedHandler = () -> Void +internal typealias VariableInstanceDic = [String: NSMapTable] + public class DevCycleClient { var sdkKey: String? @@ -51,7 +53,7 @@ public class DevCycleClient { private var flushTimer: Timer? private var closed: Bool = false private var inactivityWorkItem: DispatchWorkItem? - private var variableInstanceDictonary = [String: NSMapTable]() + private var variableInstanceDictonary = VariableInstanceDic() private var isConfigCached: Bool = false private var disableAutomaticEventLogging: Bool = false private var disableCustomEventLogging: Bool = false @@ -154,21 +156,17 @@ public class DevCycleClient { if let config = config { Log.debug("Config: \(config)", tags: ["setup"]) } - self.config?.userConfig = config - self.isConfigCached = false - - self.cacheUser(user: user) + self.setUserConfig(config) + self.cacheUser(user) - if (self.checkIfEdgeDBEnabled(config: config!, enableEdgeDB: self.enableEdgeDB)) { - if (!(user.isAnonymous ?? false)) { - self.service?.saveEntity(user: user, completion: { data, response, error in - if error != nil { - Log.error("Error saving user entity for \(user). Error: \(String(describing: error))") - } else { - Log.info("Saved user entity") - } - }) - } + if (self.checkIfEdgeDBEnabled(config: config!, enableEdgeDB: self.enableEdgeDB) && !(user.isAnonymous ?? false)) { + self.service?.saveEntity(user: user, completion: { data, response, error in + if error != nil { + Log.error("Error saving user entity for \(user). Error: \(String(describing: error))") + } else { + Log.info("Saved user entity") + } + }) } } @@ -216,17 +214,40 @@ public class DevCycleClient { let extraParams = RequestParams(sse: sse, lastModified: lastModified, etag: etag) self.service?.getConfig(user: lastIdentifiedUser, enableEdgeDB: self.enableEdgeDB, extraParams: extraParams, completion: { [weak self] config, error in guard let self = self else { return } + if let error = error { Log.error("Error getting config: \(error)", tags: ["refetchConfig"]) + self.eventEmitter.emit(EventEmitValues.error(error)) } else { - self.config?.userConfig = config - self.isConfigCached = false + self.setUserConfig(config) } }) } } + + private func setUserConfig(_ config: UserConfig?) { + let oldConfig = self.config + self.config?.userConfig = config + self.isConfigCached = false + + if let config = config { + self.eventEmitter.emitFeatureUpdates( + oldFeatures: oldConfig?.userConfig?.features, + newFeatures: config.features + ) + self.eventEmitter.emitVariableUpdates( + oldVariables: oldConfig?.userConfig?.variables, + newVariables: config.variables, + variableInstanceDic: self.variableInstanceDictonary + ) + + if oldConfig == nil || config.etag != oldConfig?.userConfig?.etag { + self.eventEmitter.emit(EventEmitValues.configUpdated(config.variables)) + } + } + } - private func cacheUser(user: DevCycleUser) { + private func cacheUser(_ user: DevCycleUser) { self.cacheService.save(user: user) if user.isAnonymous == true, let userId = user.userId { self.cacheService.setAnonUserId(anonUserId: userId) @@ -314,8 +335,9 @@ public class DevCycleClient { return getVariable(key: key, defaultValue: defaultValue) } + private let regex = try? NSRegularExpression(pattern: ".*[^a-z0-9(\\-)(_)].*") + func getVariable(key: String, defaultValue: T) -> DVCVariable { - let regex = try? NSRegularExpression(pattern: ".*[^a-z0-9(\\-)(_)].*") if (regex?.firstMatch(in: key, range: NSMakeRange(0, key.count)) != nil) { Log.error("The variable key \(key) is invalid. It must contain only lowercase letters, numbers, hyphens and underscores. The default value will always be returned for this call.") return DVCVariable( @@ -354,6 +376,8 @@ public class DevCycleClient { self.eventQueue.updateAggregateEvents(variableKey: variable.key, variableIsDefaulted: variable.isDefaulted) } + + self.eventEmitter.emit(EventEmitValues.variableEvaluated(variable.key, variable)) return variable } } @@ -374,18 +398,19 @@ public class DevCycleClient { self.service?.getConfig(user: updateUser, enableEdgeDB: self.enableEdgeDB, extraParams: nil, completion: { [weak self] config, error in guard let self = self else { return } + if let error = error { Log.error("Error getting config: \(error)", tags: ["identify"]) self.cache = self.cacheService.load() + self.eventEmitter.emit(EventEmitValues.error(error)) } else { if let config = config { Log.debug("Config: \(config)", tags: ["identify"]) } - self.config?.userConfig = config - self.isConfigCached = false + self.setUserConfig(config) } self.user = user - self.cacheUser(user: user) + self.cacheUser(user) callback?(error, config?.variables) }) } @@ -402,21 +427,23 @@ public class DevCycleClient { self.service?.getConfig(user: anonUser, enableEdgeDB: self.enableEdgeDB, extraParams: nil, completion: { [weak self] config, error in guard let self = self else { return } - guard error == nil else { + + if let error = error { if let previousAnonUserId = cachedAnonUserId { self.cacheService.setAnonUserId(anonUserId: previousAnonUserId) } + self.eventEmitter.emit(EventEmitValues.error(error)) callback?(error, nil) return } if let config = config { Log.debug("Config: \(config)", tags: ["reset"]) + self.eventEmitter.emit(EventEmitValues.configUpdated(config.variables)) } - self.config?.userConfig = config - self.isConfigCached = false + self.setUserConfig(config) self.user = anonUser - self.cacheUser(user: anonUser) + self.cacheUser(anonUser) callback?(error, config?.variables) }) } diff --git a/DevCycle/Models/UserConfig.swift b/DevCycle/Models/UserConfig.swift index 3571bcab..af1df608 100644 --- a/DevCycle/Models/UserConfig.swift +++ b/DevCycle/Models/UserConfig.swift @@ -6,6 +6,9 @@ import Foundation +public typealias VariableSet = [String: Variable] +public typealias FeatureSet = [String: Feature] + enum UserConfigError: Error { case MissingInConfig(String) case MissingProperty(String) @@ -16,17 +19,27 @@ public struct UserConfig { var project: Project var environment: Environment var featureVariationMap: [String: String] - var features: [String: Feature] - var variables: [String: Variable] + var features: FeatureSet + var variables: VariableSet var sse: SSE? var etag: String? - init(from dictionary: [String:Any]) throws { - guard let environment = dictionary["environment"] as? [String: Any] else { throw UserConfigError.MissingInConfig("environment") } - guard let project = dictionary["project"] as? [String: Any] else { throw UserConfigError.MissingInConfig("project") } - guard let featureVariationMap = dictionary["featureVariationMap"] as? [String: String] else { throw UserConfigError.MissingInConfig("featureVariationMap") } - guard var featureMap = dictionary["features"] as? [String: Any] else { throw UserConfigError.MissingInConfig("features") } - guard var variablesMap = dictionary["variables"] as? [String: Any] else { throw UserConfigError.MissingInConfig("variables") } + init(from dictionary: [String: Any]) throws { + guard let environment = dictionary["environment"] as? [String: Any] else { + throw UserConfigError.MissingInConfig("environment") + } + guard let project = dictionary["project"] as? [String: Any] else { + throw UserConfigError.MissingInConfig("project") + } + guard let featureVariationMap = dictionary["featureVariationMap"] as? [String: String] else { + throw UserConfigError.MissingInConfig("featureVariationMap") + } + guard var featureMap = dictionary["features"] as? [String: Any] else { + throw UserConfigError.MissingInConfig("features") + } + guard var variablesMap = dictionary["variables"] as? [String: Any] else { + throw UserConfigError.MissingInConfig("variables") + } let sse = dictionary["sse"] as? [String: Any] let etag = dictionary["etag"] as? String @@ -47,23 +60,21 @@ public struct UserConfig { let variableKeys = Array(variablesMap.keys) for key in featureKeys { - if let featureDict = featureMap[key] as? [String:String] - { + if let featureDict = featureMap[key] as? [String: String] { let feature = try Feature(from: featureDict) featureMap[key] = feature } } for key in variableKeys { - if let variableDict = variablesMap[key] as? [String:Any] - { + if let variableDict = variablesMap[key] as? [String: Any] { let variable = try Variable(from: variableDict) variablesMap[key] = variable } } - self.features = featureMap as! [String: Feature] - self.variables = variablesMap as! [String: Variable] + self.features = featureMap as! FeatureSet + self.variables = variablesMap as! VariableSet } } @@ -73,8 +84,12 @@ public struct Project { var settings: Settings init (from dictionary: [String: Any]) throws { - guard let key = dictionary["key"] as? String else { throw UserConfigError.MissingProperty("key in Project") } - guard let id = dictionary["_id"] as? String else { throw UserConfigError.MissingProperty("_id in Project") } + guard let key = dictionary["key"] as? String else { + throw UserConfigError.MissingProperty("key in Project") + } + guard let id = dictionary["_id"] as? String else { + throw UserConfigError.MissingProperty("_id in Project") + } let settings = dictionary["settings"] as? [String:Any] self._id = id self.key = key @@ -117,8 +132,12 @@ public struct Environment { var key: String init (from dictionary: [String: Any]) throws { - guard let key = dictionary["key"] as? String else { throw UserConfigError.MissingProperty("key in Environment") } - guard let id = dictionary["_id"] as? String else { throw UserConfigError.MissingProperty("_id in Environment") } + guard let key = dictionary["key"] as? String else { + throw UserConfigError.MissingProperty("key in Environment") + } + guard let id = dictionary["_id"] as? String else { + throw UserConfigError.MissingProperty("_id in Environment") + } self._id = id self.key = key } @@ -134,12 +153,24 @@ public struct Feature { public var evalReason: String? init (from dictionary: [String: String]) throws { - guard let id = dictionary["_id"] else { throw UserConfigError.MissingProperty("_id in Feature object") } - guard let variation = dictionary["_variation"] else { throw UserConfigError.MissingProperty("_variation in Feature object") } - guard let key = dictionary["key"] else { throw UserConfigError.MissingProperty("key in Feature object") } - guard let type = dictionary["type"] else { throw UserConfigError.MissingProperty("type in Feature object") } - guard let variationKey = dictionary["variationKey"] else { throw UserConfigError.MissingProperty("variationKey in Feature object") } - guard let variationName = dictionary["variationName"] else { throw UserConfigError.MissingProperty("variationName in Feature object") } + guard let id = dictionary["_id"] else { + throw UserConfigError.MissingProperty("_id in Feature object") + } + guard let variation = dictionary["_variation"] else { + throw UserConfigError.MissingProperty("_variation in Feature object") + } + guard let key = dictionary["key"] else { + throw UserConfigError.MissingProperty("key in Feature object") + } + guard let type = dictionary["type"] else { + throw UserConfigError.MissingProperty("type in Feature object") + } + guard let variationKey = dictionary["variationKey"] else { + throw UserConfigError.MissingProperty("variationKey in Feature object") + } + guard let variationName = dictionary["variationName"] else { + throw UserConfigError.MissingProperty("variationName in Feature object") + } self._id = id self._variation = variation self.key = key @@ -150,7 +181,7 @@ public struct Feature { } } -public struct Variable { +public struct Variable: Equatable { public var _id: String public var key: String public var type: DVCVariableTypes @@ -170,7 +201,10 @@ public struct Variable { guard let varType = DVCVariableTypes(rawValue: type) else { throw UserConfigError.InvalidVariableType("invalid Variable type: \(type)") } - guard let value = dictionary["value"] else { throw UserConfigError.MissingProperty("value in Variable object") } + guard let value = dictionary["value"] else { + throw UserConfigError.MissingProperty("value in Variable object") + } + self._id = id self.key = key self.type = varType @@ -182,4 +216,30 @@ public struct Variable { self.value = value } } + + public static func == (lhs: Variable, rhs: Variable) -> Bool { + return lhs._id == rhs._id && + lhs.key == rhs.key && + compareValues(lhs: lhs, rhs: rhs) && + lhs.evalReason == rhs.evalReason + } + + private static func compareValues(lhs: Variable, rhs: Variable) -> Bool { + guard lhs.type == rhs.type else { + return false + } + + switch lhs.type { + case "Boolean": + return (lhs.value as! Bool) == (rhs.value as! Bool) + case "String": + return (lhs.value as! String) == (rhs.value as! String) + case "Number": + return (lhs.value as! NSNumber) == (rhs.value as! NSNumber) + case "JSON": + return (lhs.value as! NSDictionary) == (rhs.value as! NSDictionary) + default: + return false + } + } } diff --git a/DevCycle/Utils/EventEmitter.swift b/DevCycle/Utils/EventEmitter.swift index b41f5236..99250eaf 100644 --- a/DevCycle/Utils/EventEmitter.swift +++ b/DevCycle/Utils/EventEmitter.swift @@ -3,14 +3,12 @@ // DevCycle // -public typealias VariableSet = [String: Variable] - public typealias ErrorHandlerCallback = (Error) -> Void public typealias InitializedHandlerCallback = (Bool) -> Void public typealias ConfigUpdatedHandlerCallback = (VariableSet) -> Void -public typealias VariableUpdatedHandlerCallback = (String, Variable) -> Void +public typealias VariableUpdatedHandlerCallback = (String, Variable?) -> Void public typealias VariableEvaluatedHandlerCallback = (String, DVCVariable) -> Void -public typealias FeatureUpdatedHandlerCallback = (String, Feature) -> Void +public typealias FeatureUpdatedHandlerCallback = (String, Feature?) -> Void public class BaseHandler: Equatable { public static func == (lhs: BaseHandler, rhs: BaseHandler) -> Bool { @@ -53,9 +51,9 @@ enum EventEmitValues { case error(Error) case initialized(Bool) case configUpdated(VariableSet) - case variableUpdated(String, Variable) + case variableUpdated(String, Variable?) case variableEvaluated(String, DVCVariable) - case featureUpdated(String, Feature) + case featureUpdated(String, Feature?) } class EventEmitter { @@ -147,6 +145,58 @@ class EventEmitter { } } + func emitFeatureUpdates(oldFeatures: FeatureSet?, newFeatures: FeatureSet) { + if self.featureUpdatedHandlers.count == 0 + && self.allFeatureUpdatedHandlers.count == 0 { + return + } + guard let oldFeatures = oldFeatures else { + newFeatures.forEach { (key: String, variable: Feature) in + self.emit(EventEmitValues.featureUpdated(key, variable)) + } + return + } + + let keys = Set(Array(oldFeatures.keys) + Array(newFeatures.keys)) + keys.forEach { key in + let oldFeatureVar = oldFeatures[key]?._variation + let newFeature = newFeatures[key] + let newFeatureVar = newFeature?._variation + + if oldFeatureVar != newFeatureVar { + self.emit(EventEmitValues.featureUpdated(key, newFeature)) + } + } + } + + func emitVariableUpdates( + oldVariables: VariableSet?, + newVariables: VariableSet, + variableInstanceDic: VariableInstanceDic + ) { + if self.variableUpdatedHandlers.count == 0 + && self.allVariableUpdatedHandlers.count == 0 { + return + } + + guard let oldVariables = oldVariables else { + newVariables.forEach { (key: String, variable: Variable) in + self.emit(EventEmitValues.variableUpdated(key, variable)) + } + return + } + + let keys = Set(Array(oldVariables.keys) + Array(newVariables.keys)) + keys.forEach { key in + let oldVariable = oldVariables[key] + let newVariable = newVariables[key] + + if oldVariable != newVariable { + self.emit(EventEmitValues.variableUpdated(key, newVariable)) + } + } + } + func emit(_ emitValues: EventEmitValues) { switch emitValues { case .error(let err): @@ -171,10 +221,4 @@ class EventEmitter { allFeatureUpdatedHandlers.forEach { handler in handler.callback(key, feature) } } } - -// private func emitValuesByKey, V>(_ key: String, variable: V, handlersByKey: [String : [T]]) { -// if let handlers = handlersByKey[key] { -// handlers.forEach { handler in handler.callback(key, variable) } -// } -// } } From 7f644eb636d9c4a54912cbc16f8f70540301ca59 Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Fri, 25 Aug 2023 12:01:09 -0400 Subject: [PATCH 6/7] fix: try generic on enum --- DevCycle/DevCycleClient.swift | 12 ++++++------ DevCycle/Utils/EventEmitter.swift | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/DevCycle/DevCycleClient.swift b/DevCycle/DevCycleClient.swift index 337626ac..4ca14b6d 100644 --- a/DevCycle/DevCycleClient.swift +++ b/DevCycle/DevCycleClient.swift @@ -150,7 +150,7 @@ public class DevCycleClient { Log.error("Error getting config: \(error)", tags: ["setup"]) self.cache = self.cacheService.load() - self.eventEmitter.emit(EventEmitValues.error(error)) + self.eventEmitter.emit(EventEmitValues.error(error)) requestErrored = true } else { if let config = config { @@ -217,7 +217,7 @@ public class DevCycleClient { if let error = error { Log.error("Error getting config: \(error)", tags: ["refetchConfig"]) - self.eventEmitter.emit(EventEmitValues.error(error)) + self.eventEmitter.emit(EventEmitValues.error(error)) } else { self.setUserConfig(config) } @@ -242,7 +242,7 @@ public class DevCycleClient { ) if oldConfig == nil || config.etag != oldConfig?.userConfig?.etag { - self.eventEmitter.emit(EventEmitValues.configUpdated(config.variables)) + self.eventEmitter.emit(EventEmitValues.configUpdated(config.variables)) } } } @@ -377,7 +377,7 @@ public class DevCycleClient { } - self.eventEmitter.emit(EventEmitValues.variableEvaluated(variable.key, variable)) + self.eventEmitter.emit(EventEmitValues.variableEvaluated(variable.key, variable)) return variable } } @@ -402,7 +402,7 @@ public class DevCycleClient { if let error = error { Log.error("Error getting config: \(error)", tags: ["identify"]) self.cache = self.cacheService.load() - self.eventEmitter.emit(EventEmitValues.error(error)) + self.eventEmitter.emit(EventEmitValues.error(error)) } else { if let config = config { Log.debug("Config: \(config)", tags: ["identify"]) @@ -432,7 +432,7 @@ public class DevCycleClient { if let previousAnonUserId = cachedAnonUserId { self.cacheService.setAnonUserId(anonUserId: previousAnonUserId) } - self.eventEmitter.emit(EventEmitValues.error(error)) + self.eventEmitter.emit(EventEmitValues.error(error)) callback?(error, nil) return } diff --git a/DevCycle/Utils/EventEmitter.swift b/DevCycle/Utils/EventEmitter.swift index 99250eaf..d3d9e07e 100644 --- a/DevCycle/Utils/EventEmitter.swift +++ b/DevCycle/Utils/EventEmitter.swift @@ -47,12 +47,12 @@ enum EventHandlers { case featureUpdated(FeatureUpdatedHandler) } -enum EventEmitValues { +enum EventEmitValues { case error(Error) case initialized(Bool) case configUpdated(VariableSet) case variableUpdated(String, Variable?) - case variableEvaluated(String, DVCVariable) + case variableEvaluated(String, DVCVariable) case featureUpdated(String, Feature?) } @@ -152,7 +152,7 @@ class EventEmitter { } guard let oldFeatures = oldFeatures else { newFeatures.forEach { (key: String, variable: Feature) in - self.emit(EventEmitValues.featureUpdated(key, variable)) + self.emit(EventEmitValues.featureUpdated(key, variable)) } return } @@ -164,7 +164,7 @@ class EventEmitter { let newFeatureVar = newFeature?._variation if oldFeatureVar != newFeatureVar { - self.emit(EventEmitValues.featureUpdated(key, newFeature)) + self.emit(EventEmitValues.featureUpdated(key, newFeature)) } } } @@ -181,7 +181,7 @@ class EventEmitter { guard let oldVariables = oldVariables else { newVariables.forEach { (key: String, variable: Variable) in - self.emit(EventEmitValues.variableUpdated(key, variable)) + self.emit(EventEmitValues.variableUpdated(key, variable)) } return } @@ -192,12 +192,12 @@ class EventEmitter { let newVariable = newVariables[key] if oldVariable != newVariable { - self.emit(EventEmitValues.variableUpdated(key, newVariable)) + self.emit(EventEmitValues.variableUpdated(key, newVariable)) } } } - func emit(_ emitValues: EventEmitValues) { + func emit(_ emitValues: EventEmitValues) { switch emitValues { case .error(let err): self.errorHandlers.forEach { handler in handler.callback(err) } From 18426b018ff57254f44e370704c551ffad79f848 Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Fri, 25 Aug 2023 12:22:43 -0400 Subject: [PATCH 7/7] fix: type checking --- DevCycle/Models/UserConfig.swift | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/DevCycle/Models/UserConfig.swift b/DevCycle/Models/UserConfig.swift index af1df608..aaa21a91 100644 --- a/DevCycle/Models/UserConfig.swift +++ b/DevCycle/Models/UserConfig.swift @@ -230,16 +230,14 @@ public struct Variable: Equatable { } switch lhs.type { - case "Boolean": + case .Boolean: return (lhs.value as! Bool) == (rhs.value as! Bool) - case "String": + case .String: return (lhs.value as! String) == (rhs.value as! String) - case "Number": + case .Number: return (lhs.value as! NSNumber) == (rhs.value as! NSNumber) - case "JSON": + case .JSON: return (lhs.value as! NSDictionary) == (rhs.value as! NSDictionary) - default: - return false } } }