diff --git a/CHANGELOG.md b/CHANGELOG.md index b270a95e..685cb261 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to the LaunchDarkly iOS SDK will be documented in this file. ### Multiple Environment clients Version 4.0.0 does not support multiple environments. If you use version `2.14.0` or later and set `LDConfig`'s `secondaryMobileKeys` you will not be able to migrate to version `4.0.0`. Multiple Environments will be added in a future release to the Swift SDK. + +## [4.6.0] - 2020-05-26 +### Added +- Added `maxCachedUsers` option to `LDConfig`. You can now specify the number of users to be cached or use `-1` for unlimited cached users. + +### Fixed +- `FlagStore` properly synchronizes reads and writes to prevent a potential race condition. + ## [4.5.0] - 2020-03-26 ### Changed - Updated SDK code to build, run, and test on Xcode 11.4. diff --git a/LaunchDarkly.podspec b/LaunchDarkly.podspec index 9da4d7bb..616451a9 100644 --- a/LaunchDarkly.podspec +++ b/LaunchDarkly.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |ld| ld.name = "LaunchDarkly" - ld.version = "4.5.0" + ld.version = "4.6.0" ld.summary = "iOS SDK for LaunchDarkly" ld.description = <<-DESC @@ -25,7 +25,7 @@ Pod::Spec.new do |ld| ld.tvos.deployment_target = "9.0" ld.osx.deployment_target = "10.10" - ld.source = { :git => "https://github.com/launchdarkly/ios-client-sdk.git", :tag => '4.5.0'} + ld.source = { :git => "https://github.com/launchdarkly/ios-client-sdk.git", :tag => '4.6.0'} ld.source_files = "LaunchDarkly/LaunchDarkly/**/*.{h,m,swift}" diff --git a/LaunchDarkly.xcodeproj/project.pbxproj b/LaunchDarkly.xcodeproj/project.pbxproj index 9251e530..67991513 100644 --- a/LaunchDarkly.xcodeproj/project.pbxproj +++ b/LaunchDarkly.xcodeproj/project.pbxproj @@ -44,7 +44,6 @@ 8311885E2113AE2900D77CB5 /* HTTPURLResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83B8C2461FE4071F0082B8A9 /* HTTPURLResponse.swift */; }; 8311885F2113AE2D00D77CB5 /* HTTPURLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830BF932202D188E006DF9B1 /* HTTPURLRequest.swift */; }; 831188602113AE3400D77CB5 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83EF67891F97CFEC00403126 /* Dictionary.swift */; }; - 831188612113AE3700D77CB5 /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83E2E2081F9FF9E1007514E9 /* Optional.swift */; }; 831188622113AE3A00D77CB5 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DDBEF51FA24A7E00E428B6 /* Data.swift */; }; 831188632113AE3F00D77CB5 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DDBEF91FA24AFB00E428B6 /* Array.swift */; }; 831188642113AE4200D77CB5 /* JSONSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DDBEFB1FA24B2700E428B6 /* JSONSerialization.swift */; }; @@ -97,7 +96,6 @@ 831EF35C20655E730001C643 /* HTTPURLResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83B8C2461FE4071F0082B8A9 /* HTTPURLResponse.swift */; }; 831EF35D20655E730001C643 /* HTTPURLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830BF932202D188E006DF9B1 /* HTTPURLRequest.swift */; }; 831EF35E20655E730001C643 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83EF67891F97CFEC00403126 /* Dictionary.swift */; }; - 831EF35F20655E730001C643 /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83E2E2081F9FF9E1007514E9 /* Optional.swift */; }; 831EF36020655E730001C643 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DDBEF51FA24A7E00E428B6 /* Data.swift */; }; 831EF36120655E730001C643 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DDBEF91FA24AFB00E428B6 /* Array.swift */; }; 831EF36220655E730001C643 /* JSONSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DDBEFB1FA24B2700E428B6 /* JSONSerialization.swift */; }; @@ -259,7 +257,6 @@ 83D9EC8E2062DEAB004D7FA6 /* HTTPURLResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83B8C2461FE4071F0082B8A9 /* HTTPURLResponse.swift */; }; 83D9EC8F2062DEAB004D7FA6 /* HTTPURLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830BF932202D188E006DF9B1 /* HTTPURLRequest.swift */; }; 83D9EC902062DEAB004D7FA6 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83EF67891F97CFEC00403126 /* Dictionary.swift */; }; - 83D9EC912062DEAB004D7FA6 /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83E2E2081F9FF9E1007514E9 /* Optional.swift */; }; 83D9EC922062DEAB004D7FA6 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DDBEF51FA24A7E00E428B6 /* Data.swift */; }; 83D9EC932062DEAB004D7FA6 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DDBEF91FA24AFB00E428B6 /* Array.swift */; }; 83D9EC942062DEAB004D7FA6 /* JSONSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DDBEFB1FA24B2700E428B6 /* JSONSerialization.swift */; }; @@ -278,7 +275,6 @@ 83DDBEFE1FA24F9600E428B6 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DDBEFD1FA24F9600E428B6 /* Date.swift */; }; 83DDBF001FA2589900E428B6 /* FlagStoreSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DDBEFF1FA2589900E428B6 /* FlagStoreSpec.swift */; }; 83E2E2061F9E7AC7007514E9 /* LDUserSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83E2E2051F9E7AC7007514E9 /* LDUserSpec.swift */; }; - 83E2E2091F9FF9E1007514E9 /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83E2E2081F9FF9E1007514E9 /* Optional.swift */; }; 83EBCBA320D9A1F3003A7142 /* FlagValueCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83EBCBA220D9A1F3003A7142 /* FlagValueCounter.swift */; }; 83EBCBA420D9A1F3003A7142 /* FlagValueCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83EBCBA220D9A1F3003A7142 /* FlagValueCounter.swift */; }; 83EBCBA520D9A1F3003A7142 /* FlagValueCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83EBCBA220D9A1F3003A7142 /* FlagValueCounter.swift */; }; @@ -453,7 +449,6 @@ 83DDBEFD1FA24F9600E428B6 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; 83DDBEFF1FA2589900E428B6 /* FlagStoreSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlagStoreSpec.swift; sourceTree = ""; }; 83E2E2051F9E7AC7007514E9 /* LDUserSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LDUserSpec.swift; sourceTree = ""; }; - 83E2E2081F9FF9E1007514E9 /* Optional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Optional.swift; sourceTree = ""; }; 83EBCBA220D9A1F3003A7142 /* FlagValueCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlagValueCounter.swift; sourceTree = ""; }; 83EBCBA920D9A451003A7142 /* FlagValueCounterSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlagValueCounterSpec.swift; sourceTree = ""; }; 83EBCBAB20D9C6A6003A7142 /* FlagCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlagCounter.swift; sourceTree = ""; }; @@ -818,7 +813,6 @@ isa = PBXGroup; children = ( 83EF67891F97CFEC00403126 /* Dictionary.swift */, - 83E2E2081F9FF9E1007514E9 /* Optional.swift */, 83DDBEF51FA24A7E00E428B6 /* Data.swift */, 83DDBEF91FA24AFB00E428B6 /* Array.swift */, 83DDBEFB1FA24B2700E428B6 /* JSONSerialization.swift */, @@ -1447,7 +1441,6 @@ C443A40523145FBF00145710 /* ConnectionInformation.swift in Sources */, 8354AC732243166900CDE602 /* UserEnvironmentFlagCache.swift in Sources */, 8311885B2113AE1D00D77CB5 /* Throttler.swift in Sources */, - 831188612113AE3700D77CB5 /* Optional.swift in Sources */, 8311884E2113ADE500D77CB5 /* Event.swift in Sources */, 832D68A5224A38FC005F052A /* CacheConverter.swift in Sources */, 831188482113ADD100D77CB5 /* LDFlagValueConvertible.swift in Sources */, @@ -1515,7 +1508,6 @@ 8370DF6E225E40B800F84810 /* DeprecatedCache.swift in Sources */, C43C37E7238DF22C003C1624 /* EvaluationDetail.swift in Sources */, 835F43D320D0309A0070DE51 /* EventTrackingContext.swift in Sources */, - 831EF35F20655E730001C643 /* Optional.swift in Sources */, 831EF36020655E730001C643 /* Data.swift in Sources */, 83EBCBB520DABE1B003A7142 /* FlagRequestTracker.swift in Sources */, 831EF36120655E730001C643 /* Array.swift in Sources */, @@ -1548,7 +1540,6 @@ 837EF3742059C237009D628A /* Log.swift in Sources */, 835F43D120D0309A0070DE51 /* EventTrackingContext.swift in Sources */, 83FEF8DD1F266742001CF12C /* FlagSynchronizer.swift in Sources */, - 83E2E2091F9FF9E1007514E9 /* Optional.swift in Sources */, 835E1D471F68B3EC00184DB4 /* ObjcLDFlagValue.swift in Sources */, 83883DD5220B68A000EEAB95 /* ErrorObserver.swift in Sources */, 830BF933202D188E006DF9B1 /* HTTPURLRequest.swift in Sources */, @@ -1712,7 +1703,6 @@ C43C37EA238DF238003C1624 /* DeprecatedCacheModelV6.swift in Sources */, 83D9EC902062DEAB004D7FA6 /* Dictionary.swift in Sources */, 831425B2206B030100F2EF36 /* EnvironmentReporter.swift in Sources */, - 83D9EC912062DEAB004D7FA6 /* Optional.swift in Sources */, 83D9EC922062DEAB004D7FA6 /* Data.swift in Sources */, 8347BB0D21F147E100E56BCD /* LDTimer.swift in Sources */, 8354AC712243166900CDE602 /* UserEnvironmentFlagCache.swift in Sources */, diff --git a/LaunchDarkly/GeneratedCode/mocks.generated.swift b/LaunchDarkly/GeneratedCode/mocks.generated.swift index 0dcc38e6..4392a032 100644 --- a/LaunchDarkly/GeneratedCode/mocks.generated.swift +++ b/LaunchDarkly/GeneratedCode/mocks.generated.swift @@ -300,6 +300,16 @@ final class EventReportingMock: EventReporting { // MARK: - FeatureFlagCachingMock final class FeatureFlagCachingMock: FeatureFlagCaching { + // MARK: maxCachedUsers + var maxCachedUsersSetCount = 0 + var setMaxCachedUsersCallback: (() -> Void)? + var maxCachedUsers: Int = 5 { + didSet { + maxCachedUsersSetCount += 1 + setMaxCachedUsersCallback?() + } + } + // MARK: retrieveFeatureFlags var retrieveFeatureFlagsCallCount = 0 var retrieveFeatureFlagsCallback: (() -> Void)? diff --git a/LaunchDarkly/LaunchDarkly/Extensions/AnyComparer.swift b/LaunchDarkly/LaunchDarkly/Extensions/AnyComparer.swift index 6a90063e..a7e51d94 100644 --- a/LaunchDarkly/LaunchDarkly/Extensions/AnyComparer.swift +++ b/LaunchDarkly/LaunchDarkly/Extensions/AnyComparer.swift @@ -2,7 +2,6 @@ // Any.swift // LaunchDarkly // -// Created by Mark Pokorny on 11/9/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -10,60 +9,60 @@ import Foundation struct AnyComparer { private init() { } - + //If editing this method to add classes here, update AnySpec with tests that verify the comparison for that class //swiftlint:disable:next cyclomatic_complexity public static func isEqual(_ value: Any, to other: Any) -> Bool { switch (value, other) { - case (let value as Bool, let other as Bool): + case let (value, other) as (Bool, Bool): if value != other { return false } - case (let value as Int, let other as Int): + case let (value, other) as (Int, Int): if value != other { return false } - case (let value as Int, let other as Double): + case let (value, other) as (Int, Double): if Double(value) != other { return false } - case (let value as Double, let other as Int): + case let (value, other) as (Double, Int): if value != Double(other) { return false } - case (let value as Int64, let other as Int64): + case let (value, other) as (Int64, Int64): if value != other { return false } - case (let value as Int64, let other as Double): + case let (value, other) as (Int64, Double): if Double(value) != other { return false } - case (let value as Double, let other as Int64): + case let (value, other) as (Double, Int64): if value != Double(other) { return false } - case (let value as Double, let other as Double): + case let (value, other) as (Double, Double): if value != other { return false } - case (let value as String, let other as String): + case let (value, other) as (String, String): if value != other { return false } - case (let value as [Any], let other as [Any]): + case let (value, other) as ([Any], [Any]): if !value.isEqual(to: other) { return false } - case (let value as [String: Any], let other as [String: Any]): + case let (value, other) as ([String: Any], [String: Any]): if !value.isEqual(to: other) { return false } - case (let value as Date, let other as Date): + case let (value, other) as (Date, Date): if value != other { return false } - case (let value as FeatureFlag, let other as FeatureFlag): + case let (value, other) as (FeatureFlag, FeatureFlag): if value != other { return false } diff --git a/LaunchDarkly/LaunchDarkly/Extensions/Array.swift b/LaunchDarkly/LaunchDarkly/Extensions/Array.swift index 4d993fe3..4dfa23e7 100644 --- a/LaunchDarkly/LaunchDarkly/Extensions/Array.swift +++ b/LaunchDarkly/LaunchDarkly/Extensions/Array.swift @@ -2,7 +2,6 @@ // Array.swift // LaunchDarkly // -// Created by Mark Pokorny on 10/26/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // diff --git a/LaunchDarkly/LaunchDarkly/Extensions/Data.swift b/LaunchDarkly/LaunchDarkly/Extensions/Data.swift index f6776d43..506a922d 100644 --- a/LaunchDarkly/LaunchDarkly/Extensions/Data.swift +++ b/LaunchDarkly/LaunchDarkly/Extensions/Data.swift @@ -2,7 +2,6 @@ // Data.swift // LaunchDarkly // -// Created by Mark Pokorny on 10/26/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -10,10 +9,10 @@ import Foundation extension Data { var base64UrlEncodedString: String { - return base64EncodedString().replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_") + base64EncodedString().replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_") } var jsonDictionary: [String: Any]? { - return try? JSONSerialization.jsonDictionary(with: self, options: [.allowFragments]) + try? JSONSerialization.jsonDictionary(with: self, options: [.allowFragments]) } } diff --git a/LaunchDarkly/LaunchDarkly/Extensions/Date.swift b/LaunchDarkly/LaunchDarkly/Extensions/Date.swift index 66a4da2e..f0c0cb32 100644 --- a/LaunchDarkly/LaunchDarkly/Extensions/Date.swift +++ b/LaunchDarkly/LaunchDarkly/Extensions/Date.swift @@ -10,7 +10,7 @@ import Foundation extension Date { var millisSince1970: Int64 { - return Int64(floor(self.timeIntervalSince1970 * 1000)) + Int64(floor(self.timeIntervalSince1970 * 1_000)) } init?(millisSince1970: Int64?) { @@ -18,7 +18,7 @@ extension Date { else { return nil } - self = Date(timeIntervalSince1970: Double(millisSince1970) / 1000) + self = Date(timeIntervalSince1970: Double(millisSince1970) / 1_000) } func isWithin(_ timeInterval: TimeInterval, of otherDate: Date?) -> Bool { @@ -30,7 +30,6 @@ extension Date { } func isEarlierThan(_ otherDate: Date) -> Bool { - let timeDifference = self.timeIntervalSince(otherDate) - return timeDifference < 0.0 + self.timeIntervalSince(otherDate) < 0.0 } } diff --git a/LaunchDarkly/LaunchDarkly/Extensions/DateFormatter.swift b/LaunchDarkly/LaunchDarkly/Extensions/DateFormatter.swift index 81321230..693410d8 100644 --- a/LaunchDarkly/LaunchDarkly/Extensions/DateFormatter.swift +++ b/LaunchDarkly/LaunchDarkly/Extensions/DateFormatter.swift @@ -2,7 +2,6 @@ // DateFormatter.swift // LaunchDarkly // -// Created by Mark Pokorny on 6/15/18. +JMJ // Copyright © 2018 Catamorphic Co. All rights reserved. // @@ -14,7 +13,7 @@ extension DateFormatter { httpUrlHeaderFormatter.locale = Locale(identifier: "en_US_POSIX") httpUrlHeaderFormatter.timeZone = TimeZone(abbreviation: "GMT") httpUrlHeaderFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz" //Mon, 07 May 2018 19:46:29 GMT - + return httpUrlHeaderFormatter } } diff --git a/LaunchDarkly/LaunchDarkly/Extensions/Dictionary.swift b/LaunchDarkly/LaunchDarkly/Extensions/Dictionary.swift index 1139875e..b40a9024 100644 --- a/LaunchDarkly/LaunchDarkly/Extensions/Dictionary.swift +++ b/LaunchDarkly/LaunchDarkly/Extensions/Dictionary.swift @@ -2,7 +2,6 @@ // Dictionary.swift // LaunchDarkly // -// Created by Mark Pokorny on 10/18/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -11,29 +10,21 @@ import Foundation extension Dictionary where Key == String { var jsonString: String? { guard let encodedDictionary = jsonData - else { - return nil - } + else { return nil } return String(data: encodedDictionary, encoding: .utf8) } - + var jsonData: Data? { guard JSONSerialization.isValidJSONObject(self) - else { - return nil - } + else { return nil } return try? JSONSerialization.data(withJSONObject: self, options: []) } func isEqual(to other: [String: Any]) -> Bool { guard self.count == other.count - else { - return false - } + else { return false } guard self.keys.sorted() == other.keys.sorted() - else { - return false - } + else { return false } for key in self.keys { if !AnyComparer.isEqual(self[key], to: other[key]) { return false @@ -47,30 +38,25 @@ extension Dictionary where Key == String { let rightKeys: Set = Set(other.keys) let differingKeys = leftKeys.symmetricDifference(rightKeys) let matchingKeys = leftKeys.intersection(rightKeys) - let matchingKeysWithDifferentValues = matchingKeys.filter { (key) -> Bool in + let matchingKeysWithDifferentValues = matchingKeys.filter { key -> Bool in !AnyComparer.isEqual(self[key], to: other[key]) } return differingKeys.union(matchingKeysWithDifferentValues).sorted() } var base64UrlEncodedString: String? { - return jsonData?.base64UrlEncodedString + jsonData?.base64UrlEncodedString } } extension Dictionary where Key == String, Value == Any { var withNullValuesRemoved: [String: Any] { - var filteredDictionary = self.filter { (_, value) in - !(value is NSNull) - } - filteredDictionary = filteredDictionary.mapValues { (value) in - guard let dictionary = value as? [String: Any] - else { - return value + self.filter { !($1 is NSNull) }.mapValues { value in + if let dictionary = value as? [String: Any] { + return dictionary.withNullValuesRemoved } - return dictionary.withNullValuesRemoved + return value } - return filteredDictionary } } @@ -93,22 +79,6 @@ extension Optional where Wrapped == [String: Any] { } public static func != (lhs: [String: Any]?, rhs: [String: Any]?) -> Bool { - return !(lhs == rhs) - } -} - -extension Dictionary { - func compactMapValues(_ transform: (Dictionary.Value) throws -> T?) rethrows -> Dictionary { - var dictionary = [Dictionary.Key: T]() - try self.mapValues(transform).compactMap { (keyValuePair) -> (Dictionary.Key, T)? in - guard let value = keyValuePair.value - else { - return nil - } - return (keyValuePair.key, value) - }.forEach { (pair: (key: Dictionary.Key, value: T)) in - dictionary[pair.key] = pair.value - } - return dictionary + !(lhs == rhs) } } diff --git a/LaunchDarkly/LaunchDarkly/Extensions/JSONSerialization.swift b/LaunchDarkly/LaunchDarkly/Extensions/JSONSerialization.swift index d1652730..e46e8721 100644 --- a/LaunchDarkly/LaunchDarkly/Extensions/JSONSerialization.swift +++ b/LaunchDarkly/LaunchDarkly/Extensions/JSONSerialization.swift @@ -2,7 +2,6 @@ // JSONSerialization.swift // LaunchDarkly // -// Created by Mark Pokorny on 10/26/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // diff --git a/LaunchDarkly/LaunchDarkly/Extensions/Optional.swift b/LaunchDarkly/LaunchDarkly/Extensions/Optional.swift deleted file mode 100644 index 6eca4671..00000000 --- a/LaunchDarkly/LaunchDarkly/Extensions/Optional.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// Optional.swift -// LaunchDarkly -// -// Created by Mark Pokorny on 10/24/17. +JMJ -// Copyright © 2017 Catamorphic Co. All rights reserved. -// - -import Foundation - -extension Optional where Wrapped: Collection { - var isNilOrEmpty: Bool { - guard case let .some(value) = self - else { - return true - } - return value.isEmpty - } -} diff --git a/LaunchDarkly/LaunchDarkly/Extensions/Thread.swift b/LaunchDarkly/LaunchDarkly/Extensions/Thread.swift index 68c3816e..7742dca5 100644 --- a/LaunchDarkly/LaunchDarkly/Extensions/Thread.swift +++ b/LaunchDarkly/LaunchDarkly/Extensions/Thread.swift @@ -2,7 +2,6 @@ // Thread.swift // LaunchDarkly // -// Created by Mark Pokorny on 3/20/18. +JMJ // Copyright © 2018 Catamorphic Co. All rights reserved. // diff --git a/LaunchDarkly/LaunchDarkly/LDClient.swift b/LaunchDarkly/LaunchDarkly/LDClient.swift index 22290973..3f4007e8 100644 --- a/LaunchDarkly/LaunchDarkly/LDClient.swift +++ b/LaunchDarkly/LaunchDarkly/LDClient.swift @@ -2,7 +2,6 @@ // LDClient.swift // LaunchDarkly // -// Created by Mark Pokorny on 7/11/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -71,10 +70,10 @@ public class LDClient { } } - //Keeps the state of the last setOnline goOnline parameter, used for throttling calls to set the SDK online + // Keeps the state of the last setOnline goOnline parameter, used for throttling calls to set the SDK online private var lastSetOnlineCallValue = false - - //Stores ConnectionInformation in UserDefaults on change + + // Stores ConnectionInformation in UserDefaults on change var connectionInformation: ConnectionInformation { didSet { Log.debug(connectionInformation.description) @@ -84,11 +83,9 @@ public class LDClient { } } } - - //Returns an object containing information about successful and/or failed polling or streaming connections to LaunchDarkly - public func getConnectionInformation() -> ConnectionInformation { - return connectionInformation - } + + // Returns an object containing information about successful and/or failed polling or streaming connections to LaunchDarkly + public func getConnectionInformation() -> ConnectionInformation { connectionInformation } /** Set the LDClient online/offline. @@ -310,6 +307,8 @@ public class LDClient { @available(*, deprecated, message: "Please use the startCompleteWhenFlagsReceived method instead") public func start(config: LDConfig, user: LDUser? = nil, completion: (() -> Void)? = nil) { Log.debug(typeName(and: #function, appending: ": ") + "starting") + flagCache.maxCachedUsers = config.maxCachedUsers + cacheConverter = self.serviceFactory.makeCacheConverter(maxCachedUsers: config.maxCachedUsers) let wasStarted = hasStarted let wasOnline = isOnline isStarting = true @@ -341,6 +340,8 @@ public class LDClient { */ public func startCompleteWhenFlagsReceived(config: LDConfig, user: LDUser? = nil, completion: (() -> Void)? = nil) { Log.debug(typeName(and: #function, appending: ": ") + "starting") + flagCache.maxCachedUsers = config.maxCachedUsers + cacheConverter = self.serviceFactory.makeCacheConverter(maxCachedUsers: config.maxCachedUsers) let wasStarted = hasStarted let wasOnline = isOnline isStarting = true @@ -1018,9 +1019,9 @@ public class LDClient { self.serviceFactory = serviceFactory } environmentReporter = self.serviceFactory.makeEnvironmentReporter() - flagCache = self.serviceFactory.makeFeatureFlagCache() + flagCache = self.serviceFactory.makeFeatureFlagCache(maxCachedUsers: LDConfig.Defaults.maxCachedUsers) LDUserWrapper.configureKeyedArchiversToHandleVersion2_3_0AndOlderUserCacheFormat() - cacheConverter = self.serviceFactory.makeCacheConverter() + cacheConverter = self.serviceFactory.makeCacheConverter(maxCachedUsers: LDConfig.Defaults.maxCachedUsers) flagChangeNotifier = self.serviceFactory.makeFlagChangeNotifier() throttler = self.serviceFactory.makeThrottler(maxDelay: Throttler.Constants.defaultDelay, environmentReporter: environmentReporter) diff --git a/LaunchDarkly/LaunchDarkly/LDCommon.swift b/LaunchDarkly/LaunchDarkly/LDCommon.swift index 8ac28bc5..3c5537ea 100644 --- a/LaunchDarkly/LaunchDarkly/LDCommon.swift +++ b/LaunchDarkly/LaunchDarkly/LDCommon.swift @@ -2,7 +2,6 @@ // LDCommon.swift // LaunchDarkly // -// Created by Mark Pokorny on 8/23/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -25,10 +24,6 @@ public typealias LDConnectionModeChangedHandler = (ConnectionInformation.Connect public typealias LDErrorHandler = (Error) -> Void extension LDFlagKey { - private static var anyKeyIdentifier: LDFlagKey { - return "Darkly.FlagKeyList.Any" - } - static var anyKey: [LDFlagKey] { - return [anyKeyIdentifier] - } + private static var anyKeyIdentifier: LDFlagKey { "Darkly.FlagKeyList.Any" } + static var anyKey: [LDFlagKey] { [anyKeyIdentifier] } } diff --git a/LaunchDarkly/LaunchDarkly/Models/Cache/CacheableEnvironmentFlags.swift b/LaunchDarkly/LaunchDarkly/Models/Cache/CacheableEnvironmentFlags.swift index c4fa3be6..1fb0b393 100644 --- a/LaunchDarkly/LaunchDarkly/Models/Cache/CacheableEnvironmentFlags.swift +++ b/LaunchDarkly/LaunchDarkly/Models/Cache/CacheableEnvironmentFlags.swift @@ -2,7 +2,6 @@ // CacheableEnvironmentFlags.swift // LaunchDarkly // -// Created by Mark Pokorny on 3/19/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -23,34 +22,30 @@ struct CacheableEnvironmentFlags { } var dictionaryValue: [String: Any] { - return [CodingKeys.userKey.rawValue: userKey, - CodingKeys.mobileKey.rawValue: mobileKey, - CodingKeys.featureFlags.rawValue: featureFlags.dictionaryValue.withNullValuesRemoved] + [CodingKeys.userKey.rawValue: userKey, + CodingKeys.mobileKey.rawValue: mobileKey, + CodingKeys.featureFlags.rawValue: featureFlags.dictionaryValue.withNullValuesRemoved] } init?(dictionary: [String: Any]) { guard let userKey = dictionary[CodingKeys.userKey.rawValue] as? String, let mobileKey = dictionary[CodingKeys.mobileKey.rawValue] as? String, let featureFlags = (dictionary[CodingKeys.featureFlags.rawValue] as? [String: Any])?.flagCollection - else { - return nil - } + else { return nil } self.init(userKey: userKey, mobileKey: mobileKey, featureFlags: featureFlags) } init?(object: Any) { guard let dictionary = object as? [String: Any] - else { - return nil - } + else { return nil } self.init(dictionary: dictionary) } } extension CacheableEnvironmentFlags: Equatable { static func == (lhs: CacheableEnvironmentFlags, rhs: CacheableEnvironmentFlags) -> Bool { - return lhs.userKey == rhs.userKey - && lhs.mobileKey == rhs.mobileKey - && lhs.featureFlags == rhs.featureFlags + lhs.userKey == rhs.userKey + && lhs.mobileKey == rhs.mobileKey + && lhs.featureFlags == rhs.featureFlags } } diff --git a/LaunchDarkly/LaunchDarkly/Models/Cache/CacheableUserEnvironmentFlags.swift b/LaunchDarkly/LaunchDarkly/Models/Cache/CacheableUserEnvironmentFlags.swift index 38e24c1a..2b22e841 100644 --- a/LaunchDarkly/LaunchDarkly/Models/Cache/CacheableUserEnvironmentFlags.swift +++ b/LaunchDarkly/LaunchDarkly/Models/Cache/CacheableUserEnvironmentFlags.swift @@ -2,7 +2,6 @@ // CacheableUserEnvironments.swift // LaunchDarkly // -// Created by Mark Pokorny on 3/19/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -51,11 +50,9 @@ struct CacheableUserEnvironmentFlags { guard let userKey = dictionary[CodingKeys.userKey.rawValue] as? String, let environmentFlagsDictionary = dictionary[CodingKeys.environmentFlags.rawValue] as? [MobileKey: [LDFlagKey: Any]], let lastUpdated = (dictionary[CodingKeys.lastUpdated.rawValue] as? String)?.dateValue - else { - return nil - } + else { return nil } let environmentFlags = environmentFlagsDictionary.compactMapValues { (cacheableEnvironmentFlagsDictionary) in - return CacheableEnvironmentFlags(dictionary: cacheableEnvironmentFlagsDictionary) + CacheableEnvironmentFlags(dictionary: cacheableEnvironmentFlagsDictionary) } self.init(userKey: userKey, environmentFlags: environmentFlags, lastUpdated: lastUpdated) } @@ -68,40 +65,23 @@ struct CacheableUserEnvironmentFlags { self.init(dictionary: dictionary) } - static func makeCollection(from dictionary: [String: Any]) -> [UserKey: CacheableUserEnvironmentFlags]? { - guard !dictionary.isEmpty - else { - return [:] - } - let cacheableUserEnvironmentsCollection = dictionary.compactMapValues { (element) in - return CacheableUserEnvironmentFlags(object: element) - } - guard !cacheableUserEnvironmentsCollection.isEmpty - else { - return nil - } - return cacheableUserEnvironmentsCollection + static func makeCollection(from dictionary: [String: Any]) -> [UserKey: CacheableUserEnvironmentFlags] { + dictionary.compactMapValues { CacheableUserEnvironmentFlags(object: $0) } } var dictionaryValue: [String: Any] { - return [CodingKeys.userKey.rawValue: userKey, - CodingKeys.lastUpdated.rawValue: lastUpdated.stringValue, - CodingKeys.environmentFlags.rawValue: environmentFlags.compactMapValues({ (cacheableEnvironmentFlags) -> [String: Any] in - return cacheableEnvironmentFlags.dictionaryValue - })] + [CodingKeys.userKey.rawValue: userKey, + CodingKeys.lastUpdated.rawValue: lastUpdated.stringValue, + CodingKeys.environmentFlags.rawValue: environmentFlags.compactMapValues({ $0.dictionaryValue }) ] } } extension Dictionary where Key == UserKey, Value == CacheableUserEnvironmentFlags { - var dictionaryValues: [UserKey: [String: Any]] { - return compactMapValues { (cacheableUserEnvironment) in - return cacheableUserEnvironment.dictionaryValue - } - } + var dictionaryValues: [UserKey: [String: Any]] { compactMapValues { $0.dictionaryValue } } } extension DateFormatter { - ///Date formatter configured to format dates to/from the format 2018-08-13T19:06:38.123Z + /// Date formatter configured to format dates to/from the format 2018-08-13T19:06:38.123Z class var ldDateFormatter: DateFormatter { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" @@ -111,22 +91,16 @@ extension DateFormatter { } extension Date { - ///Date string using the format 2018-08-13T19:06:38.123Z - var stringValue: String { - return DateFormatter.ldDateFormatter.string(from: self) - } + /// Date string using the format 2018-08-13T19:06:38.123Z + var stringValue: String { DateFormatter.ldDateFormatter.string(from: self) } //When a date is converted to JSON, the resulting string is not as precise as the original date (only to the nearest .001s) //By converting the date to json, then back into a date, the result can be compared with any date re-inflated from json ///Date truncated to the nearest millisecond, which is the precision for string formatted dates - var stringEquivalentDate: Date { - return stringValue.dateValue - } + var stringEquivalentDate: Date { stringValue.dateValue } } extension String { ///Date converted from a string using the format 2018-08-13T19:06:38.123Z - var dateValue: Date { - return DateFormatter.ldDateFormatter.date(from: self) ?? Date() - } + var dateValue: Date { DateFormatter.ldDateFormatter.date(from: self) ?? Date() } } diff --git a/LaunchDarkly/LaunchDarkly/Models/ConnectionInformation.swift b/LaunchDarkly/LaunchDarkly/Models/ConnectionInformation.swift index 196b347b..d10231ba 100644 --- a/LaunchDarkly/LaunchDarkly/Models/ConnectionInformation.swift +++ b/LaunchDarkly/LaunchDarkly/Models/ConnectionInformation.swift @@ -2,7 +2,6 @@ // ConnectionInformation.swift // LaunchDarkly_iOS // -// Created by Joe Cieslik on 8/13/19. // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -12,7 +11,7 @@ public struct ConnectionInformation: Codable, CustomStringConvertible { public enum ConnectionMode: String, Codable { case streaming, offline, establishingStreamingConnection, polling } - + public enum LastConnectionFailureReason: Codable, CustomStringConvertible { public var description: String { switch self { @@ -21,45 +20,47 @@ public struct ConnectionInformation: Codable, CustomStringConvertible { case .none: return "none" case .httpError: - return "httpError: " + String(self.httpValue ?? ConnectionInformation.Constants.noCode) + return "httpError: " + String(self.httpValue ?? Constants.noCode) case .unknownError: - return "unknownError: " + (self.unknownValue ?? ConnectionInformation.Constants.unknownError) + return "unknownError: " + (self.unknownValue ?? Constants.unknownError) } } - + case unauthorized, httpError(Int), unknownError(String), none //We need .none for a non-failable initializer to conform to Codable - + var unknownValue: String? { - guard case let .unknownError(value) = self else { return nil } + guard case let .unknownError(value) = self + else { return nil } return value } - + var httpValue: Int? { - guard case let .httpError(value) = self else { return nil } + guard case let .httpError(value) = self + else { return nil } return value } } - + public struct Constants { static let noCode: Int = 0 static let unknownError: String = "Unknown Error" static let decodeError: String = "Unable to Decode error." } - - //lastKnownFlagValidity is nil if either no connection has ever been successfully made or if the SDK has an active streaming connection. It will have a value if 1) in polling mode and at least one poll has completed successfully, or 2) if in streaming mode whenever the streaming connection closes. + + // lastKnownFlagValidity is nil if either no connection has ever been successfully made or if the SDK has an active streaming connection. It will have a value if 1) in polling mode and at least one poll has completed successfully, or 2) if in streaming mode whenever the streaming connection closes. public internal(set) var lastKnownFlagValidity: Date? public internal(set) var lastFailedConnection: Date? public internal(set) var currentConnectionMode: ConnectionMode public internal(set) var lastConnectionFailureReason: LastConnectionFailureReason - + init(currentConnectionMode: ConnectionMode, lastConnectionFailureReason: LastConnectionFailureReason, lastKnownFlagValidity: Date? = nil, lastFailedConnection: Date? = nil) { self.currentConnectionMode = currentConnectionMode self.lastConnectionFailureReason = lastConnectionFailureReason self.lastKnownFlagValidity = lastKnownFlagValidity self.lastFailedConnection = lastFailedConnection } - - //Returns ConnectionInformation as a prettyfied string + + // Returns ConnectionInformation as a prettyfied string public var description: String { var connInfoString: String = "" connInfoString.append("Current Connection Mode: \(currentConnectionMode.rawValue) | ") @@ -68,47 +69,47 @@ public struct ConnectionInformation: Codable, CustomStringConvertible { connInfoString.append("Last Failed Connection: \(lastFailedConnection?.debugDescription ?? "NONE")") return connInfoString } - - //Restores ConnectionInformation from UserDefaults if it exists + + // Restores ConnectionInformation from UserDefaults if it exists static func uncacheConnectionInformation(config: LDConfig, ldClient: LDClient, clientServiceFactory: ClientServiceCreating) -> ConnectionInformation { var connectionInformation = ConnectionInformationStore.retrieveStoredConnectionInformation() ?? clientServiceFactory.makeConnectionInformation() connectionInformation = onlineSetCheck(connectionInformation: connectionInformation, ldClient: ldClient, config: config) return connectionInformation } - - //Used for updating lastSuccessfulConnection when connected to streaming and connection closes + + // Used for updating lastSuccessfulConnection when connected to streaming and connection closes static func lastSuccessfulConnectionCheck(connectionInformation: ConnectionInformation) -> ConnectionInformation { var connectionInformationVar = connectionInformation - if connectionInformationVar.currentConnectionMode == ConnectionInformation.ConnectionMode.streaming { + if connectionInformationVar.currentConnectionMode == .streaming { connectionInformationVar.lastKnownFlagValidity = Date() } return connectionInformationVar } - - //Used for updating ConnectionInformation inside of LDClient.setOnline + + // Used for updating ConnectionInformation inside of LDClient.setOnline static func onlineSetCheck(connectionInformation: ConnectionInformation, ldClient: LDClient, config: LDConfig) -> ConnectionInformation { var connectionInformationVar = connectionInformation if ldClient.isOnline && NetworkReporter.isConnectedToNetwork() { - connectionInformationVar.currentConnectionMode = effectiveStreamingMode(config: config, ldClient: ldClient) == LDStreamingMode.streaming ? ConnectionInformation.ConnectionMode.establishingStreamingConnection : ConnectionInformation.ConnectionMode.polling + connectionInformationVar.currentConnectionMode = effectiveStreamingMode(config: config, ldClient: ldClient) == LDStreamingMode.streaming ? .establishingStreamingConnection : .polling } else { - connectionInformationVar.currentConnectionMode = ConnectionInformation.ConnectionMode.offline + connectionInformationVar.currentConnectionMode = .offline } return connectionInformationVar } - - //Used for parsing SynchronizingError in LDClient.process + + // Used for parsing SynchronizingError in LDClient.process static func synchronizingErrorCheck(synchronizingError: SynchronizingError, connectionInformation: ConnectionInformation) -> ConnectionInformation { var connectionInformationVar = connectionInformation if synchronizingError.isClientUnauthorized { - connectionInformationVar.lastConnectionFailureReason = ConnectionInformation.LastConnectionFailureReason.unauthorized + connectionInformationVar.lastConnectionFailureReason = .unauthorized } else { switch synchronizingError { case .request(let error): - let errorString = error.localizedDescription != "" ? error.localizedDescription : Constants.unknownError - connectionInformationVar.lastConnectionFailureReason = ConnectionInformation.LastConnectionFailureReason.unknownError(errorString) + let errorString = error.localizedDescription.isEmpty ? Constants.unknownError : error.localizedDescription + connectionInformationVar.lastConnectionFailureReason = .unknownError(errorString) case .response(let urlResponse): let statusCode = (urlResponse as? HTTPURLResponse)?.statusCode - connectionInformationVar.lastConnectionFailureReason = ConnectionInformation.LastConnectionFailureReason.httpError(statusCode ?? ConnectionInformation.Constants.noCode) + connectionInformationVar.lastConnectionFailureReason = .httpError(statusCode ?? ConnectionInformation.Constants.noCode) default: break } } @@ -116,19 +117,19 @@ public struct ConnectionInformation: Codable, CustomStringConvertible { return connectionInformationVar } - //This function is used to ensure we switch from establishing a streaming connection to streaming once we are connected. + // This function is used to ensure we switch from establishing a streaming connection to streaming once we are connected. static func checkEstablishingStreaming(connectionInformation: ConnectionInformation) -> ConnectionInformation { var connectionInformationVar = connectionInformation - if connectionInformationVar.currentConnectionMode == ConnectionInformation.ConnectionMode.establishingStreamingConnection { - connectionInformationVar.currentConnectionMode = ConnectionInformation.ConnectionMode.streaming + if connectionInformationVar.currentConnectionMode == .establishingStreamingConnection { + connectionInformationVar.currentConnectionMode = .streaming connectionInformationVar.lastKnownFlagValidity = nil } - if connectionInformationVar.currentConnectionMode == ConnectionInformation.ConnectionMode.polling { + if connectionInformationVar.currentConnectionMode == .polling { connectionInformationVar.lastKnownFlagValidity = Date() } return connectionInformationVar } - + static func effectiveStreamingMode(config: LDConfig, ldClient: LDClient) -> LDStreamingMode { var reason = "" let streamingMode: LDStreamingMode = ldClient.isInSupportedRunMode && config.streamingMode == .streaming && config.allowStreamingMode ? .streaming : .polling @@ -142,7 +143,7 @@ public struct ConnectionInformation: Codable, CustomStringConvertible { Log.debug(ldClient.typeName(and: #function, appending: ": ") + "\(streamingMode)\(reason)") return streamingMode } - + static func backgroundBehavior(connectionInformation: ConnectionInformation, streamingMode: LDStreamingMode, goOnline: Bool) -> ConnectionInformation { var connectionInformationVar = connectionInformation if !goOnline { @@ -160,11 +161,11 @@ extension ConnectionInformation.LastConnectionFailureReason { private enum CodingKeys: String, CodingKey { case type, payload } - + public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let type = try container.decode(String.self, forKey: .type) - + switch type { case "unknownError": let payload = try? container.decode(String.self, forKey: .payload) @@ -178,10 +179,10 @@ extension ConnectionInformation.LastConnectionFailureReason { self = .none } } - + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - + switch self { case .unknownError(let error): try container.encode("unknownError", forKey: .type) diff --git a/LaunchDarkly/LaunchDarkly/Models/ErrorObserver.swift b/LaunchDarkly/LaunchDarkly/Models/ErrorObserver.swift index 59d8fa72..67eea949 100644 --- a/LaunchDarkly/LaunchDarkly/Models/ErrorObserver.swift +++ b/LaunchDarkly/LaunchDarkly/Models/ErrorObserver.swift @@ -2,7 +2,6 @@ // ErrorObserver.swift // Darkly // -// Created by Mark Pokorny on 2/6/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -10,7 +9,7 @@ import Foundation struct ErrorObserver { private(set) var key: UUID - weak private(set) var owner: LDObserverOwner? + private(set) weak var owner: LDObserverOwner? var errorHandler: LDErrorHandler? init(owner: LDObserverOwner, errorHandler: @escaping LDErrorHandler) { @@ -22,7 +21,7 @@ struct ErrorObserver { extension ErrorObserver: Equatable { static func == (lhs: ErrorObserver, rhs: ErrorObserver) -> Bool { - return lhs.key == rhs.key && lhs.owner === rhs.owner + lhs.key == rhs.key && lhs.owner === rhs.owner } } diff --git a/LaunchDarkly/LaunchDarkly/Models/Event.swift b/LaunchDarkly/LaunchDarkly/Models/Event.swift index c74b44bd..66d51076 100644 --- a/LaunchDarkly/LaunchDarkly/Models/Event.swift +++ b/LaunchDarkly/LaunchDarkly/Models/Event.swift @@ -2,7 +2,6 @@ // Event.swift // LaunchDarkly // -// Created by Mark Pokorny on 7/11/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -17,19 +16,19 @@ struct Event { //sdk internal, not publically accessible case feature, debug, identify, custom, summary static var allKinds: [Kind] { - return [feature, debug, identify, custom, summary] + [feature, debug, identify, custom, summary] } static var alwaysInlineUserKinds: [Kind] { - return [identify, debug] + [identify, debug] } var isAlwaysInlineUserKind: Bool { - return Kind.alwaysInlineUserKinds.contains(self) + Kind.alwaysInlineUserKinds.contains(self) } static var alwaysIncludeValueKinds: [Kind] { - return [feature, debug] + [feature, debug] } var isAlwaysIncludeValueKinds: Bool { - return Kind.alwaysIncludeValueKinds.contains(self) + Kind.alwaysIncludeValueKinds.contains(self) } } @@ -128,8 +127,8 @@ struct Event { //sdk internal, not publically accessible eventDictionary[CodingKeys.version.rawValue] = featureFlag?.flagVersion ?? featureFlag?.version eventDictionary[CodingKeys.data.rawValue] = data if let flagRequestTracker = flagRequestTracker { - eventDictionary.merge(flagRequestTracker.dictionaryValue) { (_, trackerItem) in - return trackerItem //This should never happen because the eventDictionary does not use any conflicting keys with the flagRequestTracker + eventDictionary.merge(flagRequestTracker.dictionaryValue) { _, trackerItem in + trackerItem // This should never happen because the eventDictionary does not use any conflicting keys with the flagRequestTracker } } eventDictionary[CodingKeys.endDate.rawValue] = endDate?.millisSince1970 @@ -142,67 +141,53 @@ struct Event { //sdk internal, not publically accessible extension Array where Element == Event { func dictionaryValues(config: LDConfig) -> [[String: Any]] { - return self.map { (event) in - event.dictionaryValue(config: config) - } + self.map { $0.dictionaryValue(config: config) } } } extension Array where Element == [String: Any] { var jsonData: Data? { guard JSONSerialization.isValidJSONObject(self) - else { - return nil - } + else { return nil } return try? JSONSerialization.data(withJSONObject: self, options: []) } func contains(_ eventDictionary: [String: Any]) -> Bool { - return !self.filter { (element) in - element.matches(eventDictionary: eventDictionary) - }.isEmpty + !self.filter { $0.matches(eventDictionary: eventDictionary) }.isEmpty } } extension Dictionary where Key == String, Value == Any { private var eventKindString: String? { - return self[Event.CodingKeys.kind.rawValue] as? String + self[Event.CodingKeys.kind.rawValue] as? String } var eventKind: Event.Kind? { guard let eventKindString = eventKindString - else { - return nil - } + else { return nil } return Event.Kind(rawValue: eventKindString) } var eventKey: String? { - return self[Event.CodingKeys.key.rawValue] as? String + self[Event.CodingKeys.key.rawValue] as? String } var eventCreationDateMillis: Int64? { - return self[Event.CodingKeys.creationDate.rawValue] as? Int64 + self[Event.CodingKeys.creationDate.rawValue] as? Int64 } var eventEndDate: Date? { - return Date(millisSince1970: self[Event.CodingKeys.endDate.rawValue] as? Int64) + Date(millisSince1970: self[Event.CodingKeys.endDate.rawValue] as? Int64) } func matches(eventDictionary other: [String: Any]) -> Bool { guard let kind = eventKind - else { - return false - } + else { return false } if kind == .summary { guard kind == other.eventKind, let eventEndDate = eventEndDate, eventEndDate.isWithin(0.001, of: other.eventEndDate) - else { - return false - } + else { return false } return true } guard let key = eventKey, let creationDateMillis = eventCreationDateMillis, let otherKey = other.eventKey, let otherCreationDateMillis = other.eventCreationDateMillis - else { - return false - } + else { return false } return key == otherKey && creationDateMillis == otherCreationDateMillis } } diff --git a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/EvaluationDetail.swift b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/EvaluationDetail.swift index 5c98624c..633fe1a1 100644 --- a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/EvaluationDetail.swift +++ b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/EvaluationDetail.swift @@ -2,7 +2,6 @@ // EvaluationDetail.swift // LaunchDarkly_iOS // -// Created by Joe Cieslik on 10/31/19. // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -11,9 +10,9 @@ import Foundation public final class EvaluationDetail { public internal(set) var value: T public internal(set) var variationIndex: Int? - public internal(set) var reason: Dictionary? + public internal(set) var reason: [String: Any]? - internal init(value: T, variationIndex: Int?, reason: Dictionary?) { + internal init(value: T, variationIndex: Int?, reason: [String: Any]?) { self.value = value self.variationIndex = variationIndex self.reason = reason diff --git a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/EventTrackingContext.swift b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/EventTrackingContext.swift index 1ce7e35f..688ef4be 100644 --- a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/EventTrackingContext.swift +++ b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/EventTrackingContext.swift @@ -2,7 +2,6 @@ // EventTrackingContext.swift // LaunchDarkly // -// Created by Mark Pokorny on 6/12/18. +JMJ // Copyright © 2018 Catamorphic Co. All rights reserved. // @@ -23,17 +22,13 @@ struct EventTrackingContext { init?(dictionary: [String: Any]) { guard let trackEvents = dictionary.trackEvents - else { - return nil - } + else { return nil } self.init(trackEvents: trackEvents, debugEventsUntilDate: Date(millisSince1970: dictionary.debugEventsUntilDate)) } init?(object: Any?) { guard let dictionary = object as? [String: Any] - else { - return nil - } + else { return nil } self.init(dictionary: dictionary) } @@ -45,9 +40,7 @@ struct EventTrackingContext { func shouldCreateDebugEvents(lastEventReportResponseTime: Date?) -> Bool { guard let debugEventsUntilDate = debugEventsUntilDate - else { - return false - } + else { return false } let comparisonDate = lastEventReportResponseTime ?? Date() return comparisonDate.isEarlierThan(debugEventsUntilDate) || comparisonDate == debugEventsUntilDate } @@ -55,9 +48,9 @@ struct EventTrackingContext { extension Dictionary where Key == String, Value == Any { var trackEvents: Bool? { - return self[EventTrackingContext.CodingKeys.trackEvents.rawValue] as? Bool + self[EventTrackingContext.CodingKeys.trackEvents.rawValue] as? Bool } var debugEventsUntilDate: Int64? { - return self[EventTrackingContext.CodingKeys.debugEventsUntilDate.rawValue] as? Int64 + self[EventTrackingContext.CodingKeys.debugEventsUntilDate.rawValue] as? Int64 } } diff --git a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FeatureFlag.swift b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FeatureFlag.swift index 52baf3dd..bb96e62d 100644 --- a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FeatureFlag.swift +++ b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FeatureFlag.swift @@ -2,7 +2,6 @@ // FeatureFlag.swift // LaunchDarkly // -// Created by Mark Pokorny on 7/24/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -22,10 +21,10 @@ struct FeatureFlag { ///The feature flag version. It changes whenever this feature flag changes. Used for event reporting only. Server json lists this as "flagVersion". Event json lists this as "version". let flagVersion: Int? let eventTrackingContext: EventTrackingContext? - let reason: Dictionary? + let reason: [String: Any]? let trackReason: Bool? - init(flagKey: LDFlagKey, value: Any?, variation: Int?, version: Int?, flagVersion: Int?, eventTrackingContext: EventTrackingContext?, reason: Dictionary?, trackReason: Bool?) { + init(flagKey: LDFlagKey, value: Any?, variation: Int?, version: Int?, flagVersion: Int?, eventTrackingContext: EventTrackingContext?, reason: [String: Any]?, trackReason: Bool?) { self.flagKey = flagKey self.value = value is NSNull ? nil : value self.variation = variation @@ -39,9 +38,7 @@ struct FeatureFlag { init?(dictionary: [String: Any]?) { guard let dictionary = dictionary, let flagKey = dictionary.flagKey - else { - return nil - } + else { return nil } self.init(flagKey: flagKey, value: dictionary.value, variation: dictionary.variation, @@ -62,81 +59,60 @@ struct FeatureFlag { dictionaryValue[CodingKeys.reason.rawValue] = reason ?? NSNull() dictionaryValue[CodingKeys.trackReason.rawValue] = trackReason ?? NSNull() if let eventTrackingContext = eventTrackingContext { - dictionaryValue.merge(eventTrackingContext.dictionaryValue) { (_, eventTrackingContextValue) in - return eventTrackingContextValue //this should never happen since the feature flag dictionary does not have any keys also used by the eventTrackingContext dictionary + dictionaryValue.merge(eventTrackingContext.dictionaryValue) { _, eventTrackingContextValue in + eventTrackingContextValue // this should never happen since the feature flag dictionary does not have any keys also used by the eventTrackingContext dictionary } } return dictionaryValue } -} -extension FeatureFlag: Equatable { - static func == (lhs: FeatureFlag, rhs: FeatureFlag) -> Bool { - if lhs.flagKey != rhs.flagKey { - return false - } - if lhs.variation == nil && rhs.variation != nil || lhs.variation != rhs.variation { - return false - } - if lhs.version == nil && rhs.version != nil || lhs.version != rhs.version { - return false - } - if lhs.reason == nil && rhs.reason != nil || lhs.reason != rhs.reason { - return false - } - if lhs.trackReason == nil && rhs.trackReason != nil || lhs.trackReason != rhs.trackReason { - return false - } - return true + func matchesVariation(_ other: FeatureFlag) -> Bool { + variation == other.variation } } -extension FeatureFlag { - func matchesVariation(_ other: FeatureFlag) -> Bool { - guard variation != nil - else { - return other.variation == nil - } - return variation == other.variation +extension FeatureFlag: Equatable { + static func == (lhs: FeatureFlag, rhs: FeatureFlag) -> Bool { + lhs.flagKey == rhs.flagKey && + lhs.variation == rhs.variation && + lhs.version == rhs.version && + lhs.reason == rhs.reason && + lhs.trackReason == rhs.trackReason } } extension Dictionary where Key == LDFlagKey, Value == FeatureFlag { - var dictionaryValue: [String: Any] { - return self.compactMapValues { (featureFlag) in - featureFlag.dictionaryValue - } - } + var dictionaryValue: [String: Any] { self.compactMapValues { $0.dictionaryValue } } } extension Dictionary where Key == String, Value == Any { var flagKey: String? { - return self[FeatureFlag.CodingKeys.flagKey.rawValue] as? String + self[FeatureFlag.CodingKeys.flagKey.rawValue] as? String } var value: Any? { - return self[FeatureFlag.CodingKeys.value.rawValue] + self[FeatureFlag.CodingKeys.value.rawValue] } var variation: Int? { - return self[FeatureFlag.CodingKeys.variation.rawValue] as? Int + self[FeatureFlag.CodingKeys.variation.rawValue] as? Int } var version: Int? { - return self[FeatureFlag.CodingKeys.version.rawValue] as? Int + self[FeatureFlag.CodingKeys.version.rawValue] as? Int } var flagVersion: Int? { - return self[FeatureFlag.CodingKeys.flagVersion.rawValue] as? Int + self[FeatureFlag.CodingKeys.flagVersion.rawValue] as? Int } - var reason: Dictionary? { - return self[FeatureFlag.CodingKeys.reason.rawValue] as? Dictionary + var reason: [String: Any]? { + self[FeatureFlag.CodingKeys.reason.rawValue] as? [String: Any] } var trackReason: Bool? { - return self[FeatureFlag.CodingKeys.trackReason.rawValue] as? Bool + self[FeatureFlag.CodingKeys.trackReason.rawValue] as? Bool } var flagCollection: [LDFlagKey: FeatureFlag]? { @@ -144,7 +120,7 @@ extension Dictionary where Key == String, Value == Any { else { return self as? [LDFlagKey: FeatureFlag] } - let flagCollection = [LDFlagKey: FeatureFlag](uniqueKeysWithValues: compactMap { (flagKey, value) -> (LDFlagKey, FeatureFlag)? in + let flagCollection = [LDFlagKey: FeatureFlag](uniqueKeysWithValues: compactMap { flagKey, value -> (LDFlagKey, FeatureFlag)? in var elementDictionary = value as? [String: Any] if elementDictionary?[FeatureFlag.CodingKeys.flagKey.rawValue] == nil { elementDictionary?[FeatureFlag.CodingKeys.flagKey.rawValue] = flagKey diff --git a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagChange/ConnectionModeChangeObserver.swift b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagChange/ConnectionModeChangeObserver.swift index 855f3b8d..97ae45bb 100644 --- a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagChange/ConnectionModeChangeObserver.swift +++ b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagChange/ConnectionModeChangeObserver.swift @@ -2,16 +2,15 @@ // ConnectionModeChangeObserver.swift // LaunchDarkly // -// Created by Joe Cieslik on 8/29/19. // Copyright © 2019 Catamorphic Co. All rights reserved. // import Foundation struct ConnectionModeChangedObserver { - weak private(set) var owner: LDObserverOwner? + private(set) weak var owner: LDObserverOwner? let connectionModeChangedHandler: LDConnectionModeChangedHandler? - + init(owner: LDObserverOwner, connectionModeChangedHandler: @escaping LDConnectionModeChangedHandler) { self.owner = owner self.connectionModeChangedHandler = connectionModeChangedHandler diff --git a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagChange/FlagChangeObserver.swift b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagChange/FlagChangeObserver.swift index 0399a489..f487bc49 100644 --- a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagChange/FlagChangeObserver.swift +++ b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagChange/FlagChangeObserver.swift @@ -2,14 +2,13 @@ // LDFlagObserver.swift // LaunchDarkly // -// Created by Mark Pokorny on 8/18/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // import Foundation struct FlagChangeObserver { - weak private(set) var owner: LDObserverOwner? + private(set) weak var owner: LDObserverOwner? let flagKeys: [LDFlagKey] let flagChangeHandler: LDFlagChangeHandler? let flagCollectionChangeHandler: LDFlagCollectionChangeHandler? @@ -31,6 +30,6 @@ struct FlagChangeObserver { extension FlagChangeObserver: Equatable { static func == (lhs: FlagChangeObserver, rhs: FlagChangeObserver) -> Bool { - return lhs.flagKeys == rhs.flagKeys && lhs.owner === rhs.owner + lhs.flagKeys == rhs.flagKeys && lhs.owner === rhs.owner } } diff --git a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagChange/FlagsUnchangedObserver.swift b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagChange/FlagsUnchangedObserver.swift index 1e51c735..30c6ef42 100644 --- a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagChange/FlagsUnchangedObserver.swift +++ b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagChange/FlagsUnchangedObserver.swift @@ -2,14 +2,13 @@ // FlagsUnchangedObserver.swift // LaunchDarkly // -// Created by Mark Pokorny on 3/6/18. +JMJ // Copyright © 2018 Catamorphic Co. All rights reserved. // import Foundation struct FlagsUnchangedObserver { - weak private(set) var owner: LDObserverOwner? + private(set) weak var owner: LDObserverOwner? let flagsUnchangedHandler: LDFlagsUnchangedHandler? init(owner: LDObserverOwner, flagsUnchangedHandler: @escaping LDFlagsUnchangedHandler) { diff --git a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagChange/LDChangedFlag.swift b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagChange/LDChangedFlag.swift index 370c81a2..85170b83 100644 --- a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagChange/LDChangedFlag.swift +++ b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagChange/LDChangedFlag.swift @@ -2,7 +2,6 @@ // LDChangedFlag.swift // LaunchDarkly // -// Created by Mark Pokorny on 8/18/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -22,7 +21,7 @@ public struct LDChangedFlag { public let newValue: Any? ///The feature flag value's source after the change public let newValueSource: LDFlagValueSource? - + init(key: LDFlagKey, oldValue: Any?, oldValueSource: LDFlagValueSource?, newValue: Any?, newValueSource: LDFlagValueSource?) { self.key = key self.oldValue = oldValue diff --git a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagRequestTracking/FlagValueCounter.swift b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagRequestTracking/FlagValueCounter.swift index c83330f8..1a280e19 100644 --- a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagRequestTracking/FlagValueCounter.swift +++ b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagRequestTracking/FlagValueCounter.swift @@ -2,7 +2,6 @@ // FlagValueCounter.swift // LaunchDarkly // -// Created by Mark Pokorny on 6/19/18. +JMJ // Copyright © 2018 Catamorphic Co. All rights reserved. // diff --git a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagValue/LDFlagBaseTypeConvertible.swift b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagValue/LDFlagBaseTypeConvertible.swift index e2023bfb..20471e55 100644 --- a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagValue/LDFlagBaseTypeConvertible.swift +++ b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagValue/LDFlagBaseTypeConvertible.swift @@ -2,7 +2,6 @@ // LDFlagBaseTypeConvertible.swift // LaunchDarkly // -// Created by Mark Pokorny on 9/5/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -34,11 +33,8 @@ extension LDFlagValue { extension Bool: LDFlagBaseTypeConvertible { init?(_ flag: LDFlagValue?) { - guard let flag = flag, - case let .bool(bool) = flag - else { - return nil - } + guard case let .bool(bool) = flag + else { return nil } self = bool } } @@ -48,11 +44,8 @@ extension Bool: LDFlagBaseTypeConvertible { extension Int: LDFlagBaseTypeConvertible { init?(_ flag: LDFlagValue?) { //TODO: Assess whether we need to initialize with a double or string too - guard let flag = flag, - case let .int(value) = flag - else { - return nil - } + guard case let .int(value) = flag + else { return nil } self = value } } @@ -62,11 +55,8 @@ extension Int: LDFlagBaseTypeConvertible { extension Double: LDFlagBaseTypeConvertible { init?(_ flag: LDFlagValue?) { //TODO: Assess whether we need to initialize with an int or string too - guard let flag = flag, - case let .double(value) = flag - else { - return nil - } + guard case let .double(value) = flag + else { return nil } self = value } } @@ -75,11 +65,8 @@ extension Double: LDFlagBaseTypeConvertible { extension String: LDFlagBaseTypeConvertible { init?(_ flag: LDFlagValue?) { - guard let flag = flag, - case let .string(value) = flag - else { - return nil - } + guard case let .string(value) = flag + else { return nil } self = value } } @@ -89,24 +76,18 @@ extension String: LDFlagBaseTypeConvertible { extension Array: LDFlagBaseTypeConvertible { init?(_ flag: LDFlagValue?) { guard let flagArray = flag?.baseArray as? [Element] - else { - return nil - } + else { return nil } self = flagArray } } extension LDFlagValue { func toBaseTypeArray() -> [BaseType]? { - return self.flagValueArray?.compactMap { - BaseType($0) - } + self.flagValueArray?.compactMap { BaseType($0) } } var baseArray: [LDFlagBaseTypeConvertible]? { - return self.flagValueArray?.compactMap { (flagValue) in - flagValue.baseValue - } + self.flagValueArray?.compactMap { $0.baseValue } } } @@ -114,26 +95,18 @@ extension LDFlagValue { extension LDFlagValue { func toBaseTypeDictionary() -> [LDFlagKey: Value]? { - return baseDictionary as? [LDFlagKey: Value] + baseDictionary as? [LDFlagKey: Value] } var baseDictionary: [String: LDFlagBaseTypeConvertible]? { - guard let flagValues = flagValueDictionary - else { - return nil - } - return flagValues.compactMapValues { (dictionaryValue) in - dictionaryValue.baseValue - } + return flagValueDictionary?.compactMapValues { $0.baseValue } } } extension Dictionary: LDFlagBaseTypeConvertible { init?(_ flag: LDFlagValue?) { guard let flagValue = flag?.baseDictionary as? [Key: Value] - else { - return nil - } + else { return nil } self = flagValue } } diff --git a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagValue/LDFlagValue.swift b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagValue/LDFlagValue.swift index c219095c..6fdfcf5b 100644 --- a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagValue/LDFlagValue.swift +++ b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagValue/LDFlagValue.swift @@ -2,7 +2,6 @@ // LDFlagValue.swift // LaunchDarkly // -// Created by Mark Pokorny on 9/5/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -20,7 +19,7 @@ public enum LDFlagValueSource: CaseIterable { } ///Defines the types and values of a feature flag. The SDK limits feature flags to these types by use of the `LDFlagValueConvertible` protocol, which uses this type. Client app developers should not construct an LDFlagValue. -public enum LDFlagValue { +public enum LDFlagValue: Equatable { ///Bool flag value case bool(Bool) ///Int flag value @@ -38,7 +37,7 @@ public enum LDFlagValue { ///An NSObject wrapper for the Swift LDFlagValue enum. Intended for use in mixed apps when Swift code needs to pass a LDFlagValue into an Objective-C method. public var objcLdFlagValue: ObjcLDFlagValue { - return ObjcLDFlagValue(self) + ObjcLDFlagValue(self) } } @@ -115,9 +114,7 @@ public enum LDFlagValue { extension LDFlagValue { var flagValueArray: [LDFlagValue]? { guard case let .array(array) = self - else { - return nil - } + else { return nil } return array } } @@ -148,64 +145,13 @@ extension LDFlagValue { extension LDFlagValue { var flagValueDictionary: [LDFlagKey: LDFlagValue]? { guard case let .dictionary(value) = self - else { - return nil - } + else { return nil } return value } } -extension LDFlagValue: Equatable { - public static func == (lhs: LDFlagValue, rhs: LDFlagValue) -> Bool { - switch (lhs, rhs) { - case let (.bool(leftValue), .bool(rightValue)): return leftValue == rightValue - case let (.int(leftValue), .int(rightValue)): return leftValue == rightValue - case let (.double(leftValue), .double(rightValue)): return leftValue == rightValue - case let (.string(leftValue), .string(rightValue)): return leftValue == rightValue - case let (.array(leftValue), .array(rightValue)): return leftValue == rightValue - case let (.dictionary(leftValue), .dictionary(rightValue)): return leftValue == rightValue - case (.null, .null): return true - default: return false - } - } -} - extension Array where Element == LDFlagValue { - static func == (lhs: [LDFlagValue], rhs: [LDFlagValue]) -> Bool { - guard lhs.count == rhs.count - else { - return false - } - //the idea for this came from https://stackoverflow.com/questions/39161168/how-to-compare-two-array-of-objects - return zip(lhs, rhs).enumerated().filter { (item) -> Bool in - let (_, (left, right)) = item - return left != right - }.isEmpty - } - func isEqual(to other: [LDFlagValue]) -> Bool { - return self == other - } -} - -extension Dictionary where Key == LDFlagKey, Value == LDFlagValue { - static func == (lhs: [LDFlagKey: LDFlagValue], rhs: [LDFlagKey: LDFlagValue]) -> Bool { - guard lhs.count == rhs.count - else { - return false - } - let leftKeys = lhs.keys.sorted() - let rightKeys = rhs.keys.sorted() - guard leftKeys == rightKeys - else { - return false - } - let leftValues = leftKeys.map { (key) -> LDFlagValue in - return lhs[key] ?? .null - } - let rightValues = rightKeys.map { (key) -> LDFlagValue in - return rhs[key] ?? .null - } - return leftValues == rightValues + self == other } } diff --git a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagValue/LDFlagValueConvertible.swift b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagValue/LDFlagValueConvertible.swift index 5edfea9b..f3b58e29 100644 --- a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagValue/LDFlagValueConvertible.swift +++ b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagValue/LDFlagValueConvertible.swift @@ -2,7 +2,6 @@ // LDFlagValueConvertible.swift // LaunchDarkly // -// Created by Mark Pokorny on 9/5/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // diff --git a/LaunchDarkly/LaunchDarkly/Models/LDConfig.swift b/LaunchDarkly/LaunchDarkly/Models/LDConfig.swift index 7a718408..405268dc 100644 --- a/LaunchDarkly/LaunchDarkly/Models/LDConfig.swift +++ b/LaunchDarkly/LaunchDarkly/Models/LDConfig.swift @@ -69,6 +69,9 @@ public struct LDConfig { /// The default setting for whether we request evaluation reasons for all flags. static let evaluationReasons = false + + /// The default setting for the maximum number of locally cached users. + static let maxCachedUsers = 5 } /// The minimum values allowed to be set into LDConfig. @@ -179,6 +182,9 @@ public struct LDConfig { /// Enables requesting evaluation reasons for all flags. (Default: false) public var evaluationReasons: Bool = Defaults.evaluationReasons + + /// An Integer that tells UserEnvironmentFlagCache the maximum number of users to locally cache. Can be set to -1 for unlimited cached users. + public var maxCachedUsers: Int = Defaults.maxCachedUsers /// LaunchDarkly defined minima for selected configurable items public let minima: Minima @@ -241,6 +247,7 @@ extension LDConfig: Equatable { && lhs.inlineUserInEvents == rhs.inlineUserInEvents && lhs.isDebugMode == rhs.isDebugMode && lhs.evaluationReasons == rhs.evaluationReasons + && lhs.maxCachedUsers == rhs.maxCachedUsers } } diff --git a/LaunchDarkly/LaunchDarkly/Models/User/LDUser.swift b/LaunchDarkly/LaunchDarkly/Models/User/LDUser.swift index ceb257f4..0c05f453 100644 --- a/LaunchDarkly/LaunchDarkly/Models/User/LDUser.swift +++ b/LaunchDarkly/LaunchDarkly/Models/User/LDUser.swift @@ -2,7 +2,6 @@ // LDUser.swift // LaunchDarkly // -// Created by Mark Pokorny on 7/11/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -18,7 +17,7 @@ typealias UserKey = String //use for identifying semantics for strings, particu The SDK does not cache user information collected, except for the user key. The user key is used to identify the cached feature flags for that user. Client app developers should use caution not to use sensitive user information as the user-key. */ public struct LDUser { - + ///String keys associated with LDUser properties. public enum CodingKeys: String, CodingKey { ///Key names match the corresponding LDUser property @@ -33,10 +32,10 @@ public struct LDUser { See Also: `LDConfig.allUserAttributesPrivate`, `LDConfig.privateUserAttributes`, and `privateAttributes`. */ public static var privatizableAttributes: [String] { - return [CodingKeys.name.rawValue, CodingKeys.firstName.rawValue, CodingKeys.lastName.rawValue, CodingKeys.country.rawValue, CodingKeys.ipAddress.rawValue, CodingKeys.email.rawValue, CodingKeys.avatar.rawValue, CodingKeys.custom.rawValue] + [CodingKeys.name.rawValue, CodingKeys.firstName.rawValue, CodingKeys.lastName.rawValue, CodingKeys.country.rawValue, CodingKeys.ipAddress.rawValue, CodingKeys.email.rawValue, CodingKeys.avatar.rawValue, CodingKeys.custom.rawValue] } static var sdkSetAttributes: [String] { - return [CodingKeys.device.rawValue, CodingKeys.operatingSystem.rawValue] + [CodingKeys.device.rawValue, CodingKeys.operatingSystem.rawValue] } ///Client app defined string that uniquely identifies the user. If the client app does not define a key, the SDK will assign an identifier associated with the anonymous user. The key cannot be made private. @@ -74,14 +73,14 @@ public struct LDUser { */ public var privateAttributes: [String]? - + ///An NSObject wrapper for the Swift LDUser struct. Intended for use in mixed apps when Swift code needs to pass a user into an Objective-C method. public var objcLdUser: ObjcLDUser { return ObjcLDUser(self) } internal var flagStore: FlagMaintaining = FlagStore() - + /** Initializer to create a LDUser. Client configurable attributes each have an optional parameter to facilitate setting user information into the LDUser. The SDK will automatically set `key`, `device`, `operatingSystem`, and `isAnonymous` attributes if the client does not provide them. The SDK embeds `device` and `operatingSystem` into the `custom` dictionary for transmission to LaunchDarkly. @@ -129,7 +128,7 @@ public struct LDUser { self.privateAttributes = privateAttributes Log.debug(typeName(and: #function) + "user: \(self)") } - + /** Failable Initializer that takes any object and attempts to create a LDUser from the object. If the object is a [String: Any], constructs the LDUser via `init(userDictionary:)` @@ -137,9 +136,7 @@ public struct LDUser { */ public init?(object: Any?) { guard let userDictionary = object as? [String: Any] - else { - return nil - } + else { return nil } self = LDUser(userDictionary: userDictionary) } @@ -198,9 +195,7 @@ public struct LDUser { } ///Returns the custom dictionary without the SDK set device and operatingSystem attributes var customWithoutSdkSetAttributes: [String: Any]? { - return custom?.filter { (key, _) in - !LDUser.sdkSetAttributes.contains(key) - } + custom?.filter { key, _ in !LDUser.sdkSetAttributes.contains(key) } } ///Dictionary with LDUser attribute keys and values, with options to include feature flags and private attributes. LDConfig object used to help resolving what attributes should be private. @@ -215,12 +210,12 @@ public struct LDUser { dictionary[CodingKeys.key.rawValue] = key - let optionalAttributes = LDUser.privatizableAttributes.filter { (attribute) in + let optionalAttributes = LDUser.privatizableAttributes.filter { attribute in attribute != CodingKeys.custom.rawValue } - optionalAttributes.forEach { (attribute) in + optionalAttributes.forEach { attribute in let value = self.value(for: attribute) - if !includePrivate && combinedPrivateAttributes.isPrivate(attribute) && value != nil { + if !includePrivate && combinedPrivateAttributes.contains(attribute) && value != nil { redactedAttributes.append(attribute) } else { dictionary[attribute] = value @@ -228,12 +223,12 @@ public struct LDUser { } var customDictionary = [String: Any]() - if !includePrivate && combinedPrivateAttributes.isPrivate(CodingKeys.custom.rawValue) && !customWithoutSdkSetAttributes.isNilOrEmpty { + if !includePrivate && combinedPrivateAttributes.contains(CodingKeys.custom.rawValue) && !(customWithoutSdkSetAttributes?.isEmpty ?? true) { redactedAttributes.append(CodingKeys.custom.rawValue) } else { if let custom = customWithoutSdkSetAttributes, !custom.isEmpty { - custom.keys.forEach { (customAttribute) in - if !includePrivate && combinedPrivateAttributes.isPrivate(customAttribute) && custom[customAttribute] != nil { + custom.keys.forEach { customAttribute in + if !includePrivate && combinedPrivateAttributes.contains(customAttribute) && custom[customAttribute] != nil { redactedAttributes.append(customAttribute) } else { customDictionary[customAttribute] = custom[customAttribute] @@ -287,13 +282,7 @@ extension UserDefaults { extension LDUser: Equatable { ///Compares users by comparing their user keys only, to allow the client app to collect user information over time public static func == (lhs: LDUser, rhs: LDUser) -> Bool { - return lhs.key == rhs.key - } -} - -extension Array where Element == String { - fileprivate func isPrivate(_ attribute: String) -> Bool { - return contains(attribute) + lhs.key == rhs.key } } @@ -362,12 +351,12 @@ extension LDUser: TypeIdentifying { } extension LDUser { ///Testing method to get the user attribute value from a LDUser struct func value(forAttribute attribute: String) -> Any? { - return value(for: attribute) + value(for: attribute) } //Compares all user properties. Excludes the composed FlagStore, which contains the users feature flags func isEqual(to otherUser: LDUser) -> Bool { - return key == otherUser.key + key == otherUser.key && name == otherUser.name && firstName == otherUser.firstName && lastName == otherUser.lastName @@ -379,7 +368,7 @@ extension LDUser: TypeIdentifying { } && isAnonymous == otherUser.isAnonymous && device == otherUser.device && operatingSystem == otherUser.operatingSystem - && AnyComparer.isEqual(privateAttributes, to: otherUser.privateAttributes) + && privateAttributes == otherUser.privateAttributes } } #endif diff --git a/LaunchDarkly/LaunchDarkly/Networking/HTTPHeaders.swift b/LaunchDarkly/LaunchDarkly/Networking/HTTPHeaders.swift index 6db6a1ed..145d6cdd 100644 --- a/LaunchDarkly/LaunchDarkly/Networking/HTTPHeaders.swift +++ b/LaunchDarkly/LaunchDarkly/Networking/HTTPHeaders.swift @@ -2,14 +2,13 @@ // HTTPHeaders.swift // LaunchDarkly // -// Created by Mark Pokorny on 9/19/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // import Foundation struct HTTPHeaders { - + struct HeaderKey { static let authorization = "Authorization" static let userAgent = "User-Agent" @@ -63,7 +62,7 @@ struct HTTPHeaders { return headers } var hasFlagRequestEtag: Bool { - return HTTPHeaders.flagRequestEtags[mobileKey] != nil + HTTPHeaders.flagRequestEtags[mobileKey] != nil } var eventRequestHeaders: [String: String] { return [HeaderKey.authorization: authKey, diff --git a/LaunchDarkly/LaunchDarkly/Networking/HTTPURLRequest.swift b/LaunchDarkly/LaunchDarkly/Networking/HTTPURLRequest.swift index 9023aaaa..7d91fbfb 100644 --- a/LaunchDarkly/LaunchDarkly/Networking/HTTPURLRequest.swift +++ b/LaunchDarkly/LaunchDarkly/Networking/HTTPURLRequest.swift @@ -2,7 +2,6 @@ // HTTPURLRequest.swift // LaunchDarkly // -// Created by Mark Pokorny on 2/8/18. +JMJ // Copyright © 2018 Catamorphic Co. All rights reserved. // diff --git a/LaunchDarkly/LaunchDarkly/Networking/HTTPURLResponse.swift b/LaunchDarkly/LaunchDarkly/Networking/HTTPURLResponse.swift index cf65327a..f92efe87 100644 --- a/LaunchDarkly/LaunchDarkly/Networking/HTTPURLResponse.swift +++ b/LaunchDarkly/LaunchDarkly/Networking/HTTPURLResponse.swift @@ -2,7 +2,6 @@ // HTTPURLResponse.swift // LaunchDarkly // -// Created by Mark Pokorny on 12/15/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -29,13 +28,11 @@ extension HTTPURLResponse { var headerDate: Date? { guard let dateHeader = self.allHeaderFields[HeaderKeys.date] as? String - else { - return nil - } + else { return nil } return DateFormatter.httpUrlHeaderFormatter.date(from: dateHeader) } var headerEtag: String? { - return self.allHeaderFields[HeaderKeys.etag] as? String + self.allHeaderFields[HeaderKeys.etag] as? String } } diff --git a/LaunchDarkly/LaunchDarkly/Networking/URLResponse.swift b/LaunchDarkly/LaunchDarkly/Networking/URLResponse.swift index 8412be7f..c1af3228 100644 --- a/LaunchDarkly/LaunchDarkly/Networking/URLResponse.swift +++ b/LaunchDarkly/LaunchDarkly/Networking/URLResponse.swift @@ -2,18 +2,12 @@ // URLResponse.swift // LaunchDarkly // -// Created by Mark Pokorny on 3/13/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // import Foundation extension URLResponse { - var httpStatusCode: Int? { - return (self as? HTTPURLResponse)?.statusCode - } - - var httpHeaderEtag: String? { - return (self as? HTTPURLResponse)?.headerEtag - } + var httpStatusCode: Int? { (self as? HTTPURLResponse)?.statusCode } + var httpHeaderEtag: String? { (self as? HTTPURLResponse)?.headerEtag } } diff --git a/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjCEvaluationDetail.swift b/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjCEvaluationDetail.swift index 58f5a956..3c4c0668 100644 --- a/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjCEvaluationDetail.swift +++ b/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjCEvaluationDetail.swift @@ -2,7 +2,6 @@ // ObjCEvaluationDetail.swift // LaunchDarkly_iOS // -// Created by Joe Cieslik on 12/1/19. // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -11,9 +10,9 @@ import Foundation @objc public final class ObjCBoolEvaluationDetail: NSObject { public internal(set) var value: Bool public internal(set) var variationIndex: Int? - public internal(set) var reason: Dictionary? + public internal(set) var reason: [String: Any]? - internal init(value: Bool, variationIndex: Int?, reason: Dictionary?) { + internal init(value: Bool, variationIndex: Int?, reason: [String: Any]?) { self.value = value self.variationIndex = variationIndex self.reason = reason @@ -23,9 +22,9 @@ import Foundation @objc public final class ObjCDoubleEvaluationDetail: NSObject { public internal(set) var value: Double public internal(set) var variationIndex: Int? - public internal(set) var reason: Dictionary? + public internal(set) var reason: [String: Any]? - internal init(value: Double, variationIndex: Int?, reason: Dictionary?) { + internal init(value: Double, variationIndex: Int?, reason: [String: Any]?) { self.value = value self.variationIndex = variationIndex self.reason = reason @@ -35,9 +34,9 @@ import Foundation @objc public final class ObjCIntegerEvaluationDetail: NSObject { public internal(set) var value: Int public internal(set) var variationIndex: Int? - public internal(set) var reason: Dictionary? + public internal(set) var reason: [String: Any]? - internal init(value: Int, variationIndex: Int?, reason: Dictionary?) { + internal init(value: Int, variationIndex: Int?, reason: [String: Any]?) { self.value = value self.variationIndex = variationIndex self.reason = reason @@ -47,9 +46,9 @@ import Foundation @objc public final class ObjCStringEvaluationDetail: NSObject { public internal(set) var value: String? public internal(set) var variationIndex: Int? - public internal(set) var reason: Dictionary? + public internal(set) var reason: [String: Any]? - internal init(value: String?, variationIndex: Int?, reason: Dictionary?) { + internal init(value: String?, variationIndex: Int?, reason: [String: Any]?) { self.value = value self.variationIndex = variationIndex self.reason = reason @@ -59,9 +58,9 @@ import Foundation @objc public final class ObjCArrayEvaluationDetail: NSObject { public internal(set) var value: [Any]? public internal(set) var variationIndex: Int? - public internal(set) var reason: Dictionary? + public internal(set) var reason: [String: Any]? - internal init(value: [Any]?, variationIndex: Int?, reason: Dictionary?) { + internal init(value: [Any]?, variationIndex: Int?, reason: [String: Any]?) { self.value = value self.variationIndex = variationIndex self.reason = reason @@ -69,11 +68,11 @@ import Foundation } @objc public final class ObjCDictionaryEvaluationDetail: NSObject { - public internal(set) var value: Dictionary? + public internal(set) var value: [String: Any]? public internal(set) var variationIndex: Int? - public internal(set) var reason: Dictionary? + public internal(set) var reason: [String: Any]? - internal init(value: Dictionary?, variationIndex: Int?, reason: Dictionary?) { + internal init(value: Dictionary?, variationIndex: Int?, reason: [String: Any]?) { self.value = value self.variationIndex = variationIndex self.reason = reason diff --git a/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDChangedFlag.swift b/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDChangedFlag.swift index 875a008a..e36afdcf 100644 --- a/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDChangedFlag.swift +++ b/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDChangedFlag.swift @@ -2,7 +2,6 @@ // LDChangedFlagObject.swift // LaunchDarkly // -// Created by Mark Pokorny on 9/12/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -19,23 +18,23 @@ public class ObjcLDChangedFlag: NSObject { @objc public static let nilSource = "" ///String that identifies the feature flag value's type does not match the requested type @objc public static let typeMismatch = "type mismatch" - + fileprivate let changedFlag: LDChangedFlag fileprivate var sourceValue: Any? { - return changedFlag.oldValue ?? changedFlag.newValue + changedFlag.oldValue ?? changedFlag.newValue } ///The changed feature flag's key @objc public var key: String { - return changedFlag.key + changedFlag.key } - + fileprivate init(_ changedFlag: LDChangedFlag) { self.changedFlag = changedFlag } - + fileprivate func sourceString(forSource source: LDFlagValueSource?, typeMismatch: Bool) -> String { - return typeMismatch ? ObjcLDChangedFlag.typeMismatch : LDFlagValueSource.toString(source) + typeMismatch ? ObjcLDChangedFlag.typeMismatch : LDFlagValueSource.toString(source) } } @@ -46,27 +45,27 @@ public class ObjcLDChangedFlag: NSObject { public final class ObjcLDBoolChangedFlag: ObjcLDChangedFlag { ///The changed flag's value before it changed @objc public var oldValue: Bool { - return (changedFlag.oldValue as? Bool) ?? false + (changedFlag.oldValue as? Bool) ?? false } ///The changed flag's value after it changed @objc public var newValue: Bool { - return (changedFlag.newValue as? Bool) ?? false + (changedFlag.newValue as? Bool) ?? false } ///The changed flag value's source before it changed @objc public var oldValueSource: ObjcLDFlagValueSource { - return ObjcLDFlagValueSource(changedFlag.oldValueSource, typeMismatch: typeMismatch) + ObjcLDFlagValueSource(changedFlag.oldValueSource, typeMismatch: typeMismatch) } ///The changed flag value's source after it changed @objc public var newValueSource: ObjcLDFlagValueSource { - return ObjcLDFlagValueSource(changedFlag.newValueSource, typeMismatch: typeMismatch) + ObjcLDFlagValueSource(changedFlag.newValueSource, typeMismatch: typeMismatch) } override init(_ changedFlag: LDChangedFlag) { super.init(changedFlag) } - + private var typeMismatch: Bool { - return !(sourceValue is Bool) + !(sourceValue is Bool) } } @@ -77,27 +76,27 @@ public final class ObjcLDBoolChangedFlag: ObjcLDChangedFlag { public final class ObjcLDIntegerChangedFlag: ObjcLDChangedFlag { ///The changed flag's value before it changed @objc public var oldValue: Int { - return (changedFlag.oldValue as? Int) ?? 0 + (changedFlag.oldValue as? Int) ?? 0 } ///The changed flag's value after it changed @objc public var newValue: Int { - return (changedFlag.newValue as? Int) ?? 0 + (changedFlag.newValue as? Int) ?? 0 } ///The changed flag value's source before it changed @objc public var oldValueSource: ObjcLDFlagValueSource { - return ObjcLDFlagValueSource(changedFlag.oldValueSource, typeMismatch: typeMismatch) + ObjcLDFlagValueSource(changedFlag.oldValueSource, typeMismatch: typeMismatch) } ///The changed flag value's source after it changed @objc public var newValueSource: ObjcLDFlagValueSource { - return ObjcLDFlagValueSource(changedFlag.newValueSource, typeMismatch: typeMismatch) + ObjcLDFlagValueSource(changedFlag.newValueSource, typeMismatch: typeMismatch) } - + override init(_ changedFlag: LDChangedFlag) { super.init(changedFlag) } - + private var typeMismatch: Bool { - return !(sourceValue is Int) + !(sourceValue is Int) } } @@ -108,27 +107,27 @@ public final class ObjcLDIntegerChangedFlag: ObjcLDChangedFlag { public final class ObjcLDDoubleChangedFlag: ObjcLDChangedFlag { ///The changed flag's value before it changed @objc public var oldValue: Double { - return (changedFlag.oldValue as? Double) ?? 0.0 + (changedFlag.oldValue as? Double) ?? 0.0 } ///The changed flag's value after it changed @objc public var newValue: Double { - return (changedFlag.newValue as? Double) ?? 0.0 + (changedFlag.newValue as? Double) ?? 0.0 } ///The changed flag value's source before it changed @objc public var oldValueSource: ObjcLDFlagValueSource { - return ObjcLDFlagValueSource(changedFlag.oldValueSource, typeMismatch: typeMismatch) + ObjcLDFlagValueSource(changedFlag.oldValueSource, typeMismatch: typeMismatch) } ///The changed flag value's source after it changed @objc public var newValueSource: ObjcLDFlagValueSource { - return ObjcLDFlagValueSource(changedFlag.newValueSource, typeMismatch: typeMismatch) + ObjcLDFlagValueSource(changedFlag.newValueSource, typeMismatch: typeMismatch) } - + override init(_ changedFlag: LDChangedFlag) { super.init(changedFlag) } - + private var typeMismatch: Bool { - return !(sourceValue is Double) + !(sourceValue is Double) } } @@ -139,27 +138,27 @@ public final class ObjcLDDoubleChangedFlag: ObjcLDChangedFlag { public final class ObjcLDStringChangedFlag: ObjcLDChangedFlag { ///The changed flag's value before it changed @objc public var oldValue: String? { - return (changedFlag.oldValue as? String) + (changedFlag.oldValue as? String) } ///The changed flag's value after it changed @objc public var newValue: String? { - return (changedFlag.newValue as? String) + (changedFlag.newValue as? String) } ///The changed flag value's source before it changed @objc public var oldValueSource: ObjcLDFlagValueSource { - return ObjcLDFlagValueSource(changedFlag.oldValueSource, typeMismatch: typeMismatch) + ObjcLDFlagValueSource(changedFlag.oldValueSource, typeMismatch: typeMismatch) } ///The changed flag value's source after it changed @objc public var newValueSource: ObjcLDFlagValueSource { - return ObjcLDFlagValueSource(changedFlag.newValueSource, typeMismatch: typeMismatch) + ObjcLDFlagValueSource(changedFlag.newValueSource, typeMismatch: typeMismatch) } override init(_ changedFlag: LDChangedFlag) { super.init(changedFlag) } - + private var typeMismatch: Bool { - return !(sourceValue is String) + !(sourceValue is String) } } @@ -170,27 +169,27 @@ public final class ObjcLDStringChangedFlag: ObjcLDChangedFlag { public final class ObjcLDArrayChangedFlag: ObjcLDChangedFlag { ///The changed flag's value before it changed @objc public var oldValue: [Any]? { - return changedFlag.oldValue as? [Any] + changedFlag.oldValue as? [Any] } ///The changed flag's value after it changed @objc public var newValue: [Any]? { - return changedFlag.newValue as? [Any] + changedFlag.newValue as? [Any] } ///The changed flag value's source before it changed @objc public var oldValueSource: ObjcLDFlagValueSource { - return ObjcLDFlagValueSource(changedFlag.oldValueSource, typeMismatch: typeMismatch) + ObjcLDFlagValueSource(changedFlag.oldValueSource, typeMismatch: typeMismatch) } ///The changed flag value's source after it changed @objc public var newValueSource: ObjcLDFlagValueSource { - return ObjcLDFlagValueSource(changedFlag.newValueSource, typeMismatch: typeMismatch) + ObjcLDFlagValueSource(changedFlag.newValueSource, typeMismatch: typeMismatch) } - + override init(_ changedFlag: LDChangedFlag) { super.init(changedFlag) } - + private var typeMismatch: Bool { - return !(sourceValue is [Any]) + !(sourceValue is [Any]) } } @@ -201,27 +200,27 @@ public final class ObjcLDArrayChangedFlag: ObjcLDChangedFlag { public final class ObjcLDDictionaryChangedFlag: ObjcLDChangedFlag { ///The changed flag's value before it changed @objc public var oldValue: [String: Any]? { - return changedFlag.oldValue as? [String: Any] + changedFlag.oldValue as? [String: Any] } ///The changed flag's value after it changed @objc public var newValue: [String: Any]? { - return changedFlag.newValue as? [String: Any] + changedFlag.newValue as? [String: Any] } ///The changed flag value's source before it changed @objc public var oldValueSource: ObjcLDFlagValueSource { - return ObjcLDFlagValueSource(changedFlag.oldValueSource, typeMismatch: typeMismatch) + ObjcLDFlagValueSource(changedFlag.oldValueSource, typeMismatch: typeMismatch) } ///The changed flag value's source after it changed @objc public var newValueSource: ObjcLDFlagValueSource { - return ObjcLDFlagValueSource(changedFlag.newValueSource, typeMismatch: typeMismatch) + ObjcLDFlagValueSource(changedFlag.newValueSource, typeMismatch: typeMismatch) } - + override init(_ changedFlag: LDChangedFlag) { super.init(changedFlag) } - + private var typeMismatch: Bool { - return !(sourceValue is [String: Any]) + !(sourceValue is [String: Any]) } } diff --git a/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDClient.swift b/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDClient.swift index 97e7b7d6..c9e7c1e7 100644 --- a/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDClient.swift +++ b/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDClient.swift @@ -2,7 +2,6 @@ // LDClientWrapper.swift // LaunchDarkly // -// Created by Mark Pokorny on 9/7/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -64,9 +63,7 @@ public final class ObjcLDClient: NSObject { Use `-[LDClient setOnline: completion:]` (`ObjcLDClient.setOnline(_:completion:)`) to change the online/offline state. */ - @objc public var isOnline: Bool { - return LDClient.shared.isOnline - } + @objc public var isOnline: Bool { LDClient.shared.isOnline } /** Set the LDClient online/offline. @@ -112,12 +109,8 @@ public final class ObjcLDClient: NSObject { When a new config is set, the LDClient goes offline and reconfigures using the new config. If the client was online when the new config was set, it goes online again, subject to a throttling delay if in force (see `ObjcLDClient.setOnline(_:completion:)` for details). To change both the `config` and `user`, set the LDClient offline, set both properties, then set the LDClient online. */ @objc public var config: ObjcLDConfig { - get { - return LDClient.shared.config.objcLdConfig - } - set { - LDClient.shared.config = newValue.config - } + get { LDClient.shared.config.objcLdConfig } + set { LDClient.shared.config = newValue.config } } /** The LDUser set into the LDClient may affect the set of feature flags returned by the LaunchDarkly server, and ties event tracking to the user. See `LDUser` (`ObjcLDUser`) for details about what information can be retained. @@ -129,15 +122,11 @@ public final class ObjcLDClient: NSObject { When a new user is set, the LDClient goes offline and sets the new user. If the client was online when the new user was set, it goes online again, subject to a throttling delay if in force (see `ObjcLDClient.setOnline(_:completion:)` for details). To change both the `config` and `user`, set the LDClient offline, set both properties, then set the LDClient online. */ @objc public var user: ObjcLDUser { - get { - return LDClient.shared.user.objcLdUser - } + get { LDClient.shared.user.objcLdUser } @available(*, deprecated, message: "Please use the identify method instead") - set { - LDClient.shared.identify(user: newValue.user) - } + set { LDClient.shared.identify(user: newValue.user) } } - + @objc public func identify(user: ObjcLDUser) { LDClient.shared.identify(user: user.user) } @@ -178,12 +167,12 @@ public final class ObjcLDClient: NSObject { @objc public func start(config configWrapper: ObjcLDConfig, user userWrapper: ObjcLDUser? = nil, completion: (() -> Void)? = nil) { LDClient.shared.start(config: configWrapper.config, user: userWrapper?.user, completion: completion) } - + /** See [start](x-source-tag://start) for more information on starting the SDK. - + This method listens for flag updates so the completion will only return once an update has occurred. - + - parameter configWrapper: The LDConfig that contains the desired configuration. (Required) - parameter userWrapper: The LDUser set with the desired user. If omitted, LDClient retains the previously set user, or default if one was never set. (Optional) - parameter completion: Closure called when the embedded `setOnlineIdentify` call completes, subject to throttling delays. (Optional) @@ -228,22 +217,22 @@ public final class ObjcLDClient: NSObject { */ /// - Tag: boolVariation @objc public func boolVariation(forKey key: LDFlagKey, fallback: Bool) -> Bool { - return LDClient.shared.variation(forKey: key, fallback: fallback) + LDClient.shared.variation(forKey: key, fallback: fallback) } - + /** See [boolVariation](x-source-tag://boolVariation) for more information on variation methods. - + - parameter key: The LDFlagKey for the requested feature flag. - parameter fallback: The fallback value to return if the feature flag key does not exist. - + - returns: ObjCBoolEvaluationDetail: This class contains your value as well as useful information on why that value was returned. */ @objc public func boolVariationDetail(forKey key: LDFlagKey, fallback: Bool) -> ObjCBoolEvaluationDetail { let evaluationDetail = LDClient.shared.variationDetail(forKey: key, fallback: fallback) return ObjCBoolEvaluationDetail(value: evaluationDetail.value, variationIndex: evaluationDetail.variationIndex, reason: evaluationDetail.reason) } - + /** Returns the `LDBoolVariationValue` (`ObjcLDBoolVariationValue`) containing the value and source for the given feature flag. If the flag does not exist, cannot be cast to a BOOL, or the LDClient is not started, returns the fallback value and `LDFlagValueSourceFallback` for the source. @@ -271,7 +260,7 @@ public final class ObjcLDClient: NSObject { */ @available(*, deprecated, message: "Please use the boolVariationDetail method for additional insight into flag evaluation.") @objc public func boolVariationAndSource(forKey key: LDFlagKey, fallback: Bool) -> ObjcLDBoolVariationValue { - return ObjcLDBoolVariationValue(LDClient.shared.variationAndSourceInternal(forKey: key, fallback: fallback)) + ObjcLDBoolVariationValue(LDClient.shared.variationAndSourceInternal(forKey: key, fallback: fallback)) } /** @@ -299,7 +288,7 @@ public final class ObjcLDClient: NSObject { */ /// - Tag: integerVariation @objc public func integerVariation(forKey key: LDFlagKey, fallback: Int) -> Int { - return LDClient.shared.variation(forKey: key, fallback: fallback) + LDClient.shared.variation(forKey: key, fallback: fallback) } /** @@ -342,7 +331,7 @@ public final class ObjcLDClient: NSObject { */ @available(*, deprecated, message: "Please use the integerVariationDetail method for additional insight into flag evaluation.") @objc public func integerVariationAndSource(forKey key: LDFlagKey, fallback: Int) -> ObjcLDIntegerVariationValue { - return ObjcLDIntegerVariationValue(LDClient.shared.variationAndSourceInternal(forKey: key, fallback: fallback)) + ObjcLDIntegerVariationValue(LDClient.shared.variationAndSourceInternal(forKey: key, fallback: fallback)) } /** @@ -370,7 +359,7 @@ public final class ObjcLDClient: NSObject { */ /// - Tag: doubleVariation @objc public func doubleVariation(forKey key: LDFlagKey, fallback: Double) -> Double { - return LDClient.shared.variation(forKey: key, fallback: fallback) + LDClient.shared.variation(forKey: key, fallback: fallback) } /** @@ -413,7 +402,7 @@ public final class ObjcLDClient: NSObject { */ @available(*, deprecated, message: "Please use the doubleVariationDetail method for additional insight into flag evaluation.") @objc public func doubleVariationAndSource(forKey key: LDFlagKey, fallback: Double) -> ObjcLDDoubleVariationValue { - return ObjcLDDoubleVariationValue(LDClient.shared.variationAndSourceInternal(forKey: key, fallback: fallback)) + ObjcLDDoubleVariationValue(LDClient.shared.variationAndSourceInternal(forKey: key, fallback: fallback)) } /** @@ -441,7 +430,7 @@ public final class ObjcLDClient: NSObject { */ /// - Tag: stringVariation @objc public func stringVariation(forKey key: LDFlagKey, fallback: String?) -> String? { - return LDClient.shared.variation(forKey: key, fallback: fallback) + LDClient.shared.variation(forKey: key, fallback: fallback) } /** @@ -484,7 +473,7 @@ public final class ObjcLDClient: NSObject { */ @available(*, deprecated, message: "Please use the stringVariationDetail method for additional insight into flag evaluation.") @objc public func stringVariationAndSource(forKey key: LDFlagKey, fallback: String?) -> ObjcLDStringVariationValue { - return ObjcLDStringVariationValue(LDClient.shared.variationAndSourceInternal(forKey: key, fallback: fallback)) + ObjcLDStringVariationValue(LDClient.shared.variationAndSourceInternal(forKey: key, fallback: fallback)) } /** @@ -512,7 +501,7 @@ public final class ObjcLDClient: NSObject { */ /// - Tag: arrayVariation @objc public func arrayVariation(forKey key: LDFlagKey, fallback: [Any]?) -> [Any]? { - return LDClient.shared.variation(forKey: key, fallback: fallback) + LDClient.shared.variation(forKey: key, fallback: fallback) } /** @@ -555,7 +544,7 @@ public final class ObjcLDClient: NSObject { */ @available(*, deprecated, message: "Please use the arrayVariationDetail method for additional insight into flag evaluation.") @objc public func arrayVariationAndSource(forKey key: LDFlagKey, fallback: [Any]?) -> ObjcLDArrayVariationValue { - return ObjcLDArrayVariationValue(LDClient.shared.variationAndSourceInternal(forKey: key, fallback: fallback)) + ObjcLDArrayVariationValue(LDClient.shared.variationAndSourceInternal(forKey: key, fallback: fallback)) } /** @@ -583,7 +572,7 @@ public final class ObjcLDClient: NSObject { */ /// - Tag: dictionaryVariation @objc public func dictionaryVariation(forKey key: LDFlagKey, fallback: [String: Any]?) -> [String: Any]? { - return LDClient.shared.variation(forKey: key, fallback: fallback) + LDClient.shared.variation(forKey: key, fallback: fallback) } /** @@ -626,7 +615,7 @@ public final class ObjcLDClient: NSObject { */ @available(*, deprecated, message: "Please use the dictionaryVariationDetail method for additional insight into flag evaluation.") @objc public func dictionaryVariationAndSource(forKey key: LDFlagKey, fallback: [String: Any]?) -> ObjcLDDictionaryVariationValue { - return ObjcLDDictionaryVariationValue(LDClient.shared.variationAndSourceInternal(forKey: key, fallback: fallback)) + ObjcLDDictionaryVariationValue(LDClient.shared.variationAndSourceInternal(forKey: key, fallback: fallback)) } /** @@ -636,9 +625,7 @@ public final class ObjcLDClient: NSObject { LDClient will not provide any source or change information, only flag keys and flag values. The client app should convert the feature flag value into the desired type. */ - @objc public var allFlagValues: [LDFlagKey: Any]? { - return LDClient.shared.allFlagValues - } + @objc public var allFlagValues: [LDFlagKey: Any]? { LDClient.shared.allFlagValues } // MARK: - Feature Flag Updates @@ -665,7 +652,7 @@ public final class ObjcLDClient: NSObject { - parameter handler: The block the SDK will execute when the feature flag changes. */ @objc public func observeBool(_ key: LDFlagKey, owner: LDObserverOwner, handler: @escaping ObjcLDBoolChangedFlagHandler) { - LDClient.shared.observe(key: key, owner: owner) { (changedFlag) in handler(ObjcLDBoolChangedFlag(changedFlag)) } + LDClient.shared.observe(key: key, owner: owner) { changedFlag in handler(ObjcLDBoolChangedFlag(changedFlag)) } } /** @@ -691,7 +678,7 @@ public final class ObjcLDClient: NSObject { - parameter handler: The block the SDK will execute when the feature flag changes. */ @objc public func observeInteger(_ key: LDFlagKey, owner: LDObserverOwner, handler: @escaping ObjcLDIntegerChangedFlagHandler) { - LDClient.shared.observe(key: key, owner: owner) { (changedFlag) in + LDClient.shared.observe(key: key, owner: owner) { changedFlag in handler(ObjcLDIntegerChangedFlag(changedFlag)) } } @@ -719,7 +706,7 @@ public final class ObjcLDClient: NSObject { - parameter handler: The block the SDK will execute when the feature flag changes. */ @objc public func observeDouble(_ key: LDFlagKey, owner: LDObserverOwner, handler: @escaping ObjcLDDoubleChangedFlagHandler) { - LDClient.shared.observe(key: key, owner: owner) { (changedFlag) in + LDClient.shared.observe(key: key, owner: owner) { changedFlag in handler(ObjcLDDoubleChangedFlag(changedFlag)) } } @@ -747,7 +734,7 @@ public final class ObjcLDClient: NSObject { - parameter handler: The block the SDK will execute when the feature flag changes. */ @objc public func observeString(_ key: LDFlagKey, owner: LDObserverOwner, handler: @escaping ObjcLDStringChangedFlagHandler) { - LDClient.shared.observe(key: key, owner: owner) { (changedFlag) in + LDClient.shared.observe(key: key, owner: owner) { changedFlag in handler(ObjcLDStringChangedFlag(changedFlag)) } } @@ -775,7 +762,7 @@ public final class ObjcLDClient: NSObject { - parameter handler: The block the SDK will execute when the feature flag changes. */ @objc public func observeArray(_ key: LDFlagKey, owner: LDObserverOwner, handler: @escaping ObjcLDArrayChangedFlagHandler) { - LDClient.shared.observe(key: key, owner: owner) { (changedFlag) in + LDClient.shared.observe(key: key, owner: owner) { changedFlag in handler(ObjcLDArrayChangedFlag(changedFlag)) } } @@ -803,7 +790,7 @@ public final class ObjcLDClient: NSObject { - parameter handler: The block the SDK will execute when the feature flag changes. */ @objc public func observeDictionary(_ key: LDFlagKey, owner: LDObserverOwner, handler: @escaping ObjcLDDictionaryChangedFlagHandler) { - LDClient.shared.observe(key: key, owner: owner) { (changedFlag) in + LDClient.shared.observe(key: key, owner: owner) { changedFlag in handler(ObjcLDDictionaryChangedFlag(changedFlag)) } } @@ -832,8 +819,8 @@ public final class ObjcLDClient: NSObject { - parameter handler: The LDFlagCollectionChangeHandler the SDK will execute 1 time when any of the observed feature flags change. */ @objc public func observeKeys(_ keys: [LDFlagKey], owner: LDObserverOwner, handler: @escaping ObjcLDChangedFlagCollectionHandler) { - LDClient.shared.observe(keys: keys, owner: owner) { (changedFlags) in - let objcChangedFlags = changedFlags.mapValues { (changedFlag) -> ObjcLDChangedFlag in + LDClient.shared.observe(keys: keys, owner: owner) { changedFlags in + let objcChangedFlags = changedFlags.mapValues { changedFlag -> ObjcLDChangedFlag in changedFlag.objcChangedFlag } handler(objcChangedFlags) @@ -863,8 +850,8 @@ public final class ObjcLDClient: NSObject { - parameter handler: The LDFlagCollectionChangeHandler the SDK will execute 1 time when any of the observed feature flags change. */ @objc public func observeAllKeys(owner: LDObserverOwner, handler: @escaping ObjcLDChangedFlagCollectionHandler) { - LDClient.shared.observeAll(owner: owner) { (changedFlags) in - let objcChangedFlags = changedFlags.mapValues { (changedFlag) -> ObjcLDChangedFlag in + LDClient.shared.observeAll(owner: owner) { changedFlags in + let objcChangedFlags = changedFlags.mapValues { changedFlag -> ObjcLDChangedFlag in changedFlag.objcChangedFlag } handler(objcChangedFlags) @@ -1006,10 +993,10 @@ public final class ObjcLDClient: NSObject { @objc public func trackEvent(key: String, data: Any? = nil) throws { try LDClient.shared.trackEvent(key: key, data: data, metricValue: nil) } - + /** See (trackEvent)[x-source-tag://trackEvent] for full documentation. - + - parameter key: The key for the event. The SDK does nothing with the key, which can be any string the client app sends - parameter data: The data for the event. The SDK does nothing with the data, which can be any valid JSON item the client app sends. (Optional) - parameter metricValue: A numeric value used by the LaunchDarkly experimentation feature in numeric custom metrics. Can be omitted if this event is used by only non-numeric metrics. This field will also be returned as part of the custom event for Data Export. @@ -1028,7 +1015,7 @@ public final class ObjcLDClient: NSObject { // MARK: - Private - private override init() { + override private init() { _ = LDClient.shared } } diff --git a/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDConfig.swift b/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDConfig.swift index 389e18d1..6ac269c4 100644 --- a/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDConfig.swift +++ b/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDConfig.swift @@ -2,7 +2,6 @@ // LDConfigObject.swift // LaunchDarkly // -// Created by Mark Pokorny on 9/7/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -19,124 +18,76 @@ public final class ObjcLDConfig: NSObject { /// The Mobile key from your [LaunchDarkly Account](app.launchdarkly.com) settings (on the left at the bottom). If you have multiple projects be sure to choose the correct Mobile key. @objc public var mobileKey: String { - get { - return config.mobileKey - } - set { - config.mobileKey = newValue - } + get { config.mobileKey } + set { config.mobileKey = newValue } } /// The url for making feature flag requests. Do not change unless instructed by LaunchDarkly. @objc public var baseUrl: URL { - get { - return config.baseUrl - } - set { - config.baseUrl = newValue - } + get { config.baseUrl } + set { config.baseUrl = newValue } } /// The url for making event reports. Do not change unless instructed by LaunchDarkly. @objc public var eventsUrl: URL { - get { - return config.eventsUrl - } - set { - config.eventsUrl = newValue - } + get { config.eventsUrl } + set { config.eventsUrl = newValue } } /// The url for connecting to the *clientstream*. Do not change unless instructed by LaunchDarkly. @objc public var streamUrl: URL { - get { - return config.streamUrl - } - set { - config.streamUrl = newValue - } + get { config.streamUrl } + set { config.streamUrl = newValue } } /// The maximum number of analytics events the LDClient can store. When the LDClient event store reaches the eventCapacity, the SDK discards events until it successfully reports them to LaunchDarkly. (Default: 100) @objc public var eventCapacity: Int { - get { - return config.eventCapacity - } - set { - config.eventCapacity = newValue - } + get { config.eventCapacity } + set { config.eventCapacity = newValue } } /// The timeout interval for flag requests and event reports. (Default: 10 seconds) @objc public var connectionTimeout: TimeInterval { - get { - return config.connectionTimeout - } - set { - config.connectionTimeout = newValue - } + get { config.connectionTimeout } + set { config.connectionTimeout = newValue } } /// The time interval between event reports (Default: 30 seconds) @objc public var eventFlushInterval: TimeInterval { - get { - return config.eventFlushInterval - } - set { - config.eventFlushInterval = newValue - } + get { config.eventFlushInterval } + set { config.eventFlushInterval = newValue } } /// The interval between feature flag requests. Used only for polling mode. (Default: 5 minutes) @objc public var flagPollingInterval: TimeInterval { - get { - return config.flagPollingInterval - } - set { - config.flagPollingInterval = newValue - } + get { config.flagPollingInterval } + set { config.flagPollingInterval = newValue } } /// The interval between feature flag requests while running in the background. Used only for polling mode. (Default: 60 minutes) @objc public var backgroundFlagPollingInterval: TimeInterval { - get { - return config.backgroundFlagPollingInterval - } - set { - config.backgroundFlagPollingInterval = newValue - } + get { config.backgroundFlagPollingInterval } + set { config.backgroundFlagPollingInterval = newValue } } /// The minimum interval between feature flag requests. Used only for polling mode. (5 minutes) @objc public var minFlagPollingInterval: TimeInterval { - return config.minima.flagPollingInterval + config.minima.flagPollingInterval } /// The minimum interval between feature flag requests while running in the background. Used only for polling mode. (15 minutes) @objc public var minBackgroundFlagPollInterval: TimeInterval { - return config.minima.backgroundFlagPollingInterval + config.minima.backgroundFlagPollingInterval } /// Controls the method the SDK uses to keep feature flags updated. When set to .streaming, connects to `clientstream` which notifies the SDK of feature flag changes. When set to .polling, an efficient polling mechanism is used to periodically request feature flag values. Ignored for watchOS, which always uses .polling. See `LDStreamingMode` for more details. (Default: .streaming) @objc public var streamingMode: Bool { - get { - return config.streamingMode == .streaming - } - set { - config.streamingMode = newValue ? .streaming : .polling - } + get { config.streamingMode == .streaming } + set { config.streamingMode = newValue ? .streaming : .polling } } /// Enables feature flag updates when your app is in the background. Allowed on macOS only. (Default: NO) @objc public var enableBackgroundUpdates: Bool { - get { - return config.enableBackgroundUpdates - } - set { - config.enableBackgroundUpdates = newValue - } + get { config.enableBackgroundUpdates } + set { config.enableBackgroundUpdates = newValue } } /// Controls LDClient start behavior. When YES, calling start causes LDClient to go online. When NO, calling start causes LDClient to remain offline. If offline at start, set the client online to receive flag updates. (Default: YES) @objc public var startOnline: Bool { - get { - return config.startOnline - } - set { - config.startOnline = newValue - } + get { config.startOnline } + set { config.startOnline = newValue } } /** @@ -149,12 +100,8 @@ public final class ObjcLDConfig: NSObject { See Also: `privateUserAttributes` and `LDUser.privateAttributes` (`ObjcLDUser.privateAttributes`) */ @objc public var allUserAttributesPrivate: Bool { - get { - return config.allUserAttributesPrivate - } - set { - config.allUserAttributesPrivate = newValue - } + get { config.allUserAttributesPrivate } + set { config.allUserAttributesPrivate = newValue } } /** User attributes and top level custom dictionary keys to treat as private for event reporting for all users. @@ -166,46 +113,36 @@ public final class ObjcLDConfig: NSObject { See Also: `allUserAttributesPrivate`, `LDUser.privatizableAttributes` (`ObjcLDUser.privatizableAttributes`), and `LDUser.privateAttributes` (`ObjcLDUser.privateAttributes`). */ @objc public var privateUserAttributes: [String]? { - get { - return config.privateUserAttributes - } - set { - config.privateUserAttributes = newValue - } + get { config.privateUserAttributes } + set { config.privateUserAttributes = newValue } } /** Directs the SDK to use REPORT for HTTP requests to connect to `clientstream` and make feature flag requests. When NO the SDK uses GET for these requests. Do not use unless advised by LaunchDarkly. (Default: NO) */ @objc public var useReport: Bool { - get { - return config.useReport - } - set { - config.useReport = newValue - } + get { config.useReport } + set { config.useReport = newValue } } /** Controls how the SDK reports the user in analytics event reports. When set to YES, event reports will contain the user attributes, except attributes marked as private. When set to NO, event reports will contain the user's key only, reducing the size of event reports. (Default: NO) */ @objc public var inlineUserInEvents: Bool { - get { - return config.inlineUserInEvents - } - set { - config.inlineUserInEvents = newValue - } + get { config.inlineUserInEvents } + set { config.inlineUserInEvents = newValue } } ///Enables logging for debugging. (Default: NO) @objc public var debugMode: Bool { - get { - return config.isDebugMode - } - set { - config.isDebugMode = newValue - } + get { config.isDebugMode } + set { config.isDebugMode = newValue } + } + + ///An Integer that tells UserEnvironmentFlagCache the maximum number of users to locally cache. Can be set to -1 for unlimited cached users. (Default: 5) + @objc public var maxCachedUsers: Int { + get { config.maxCachedUsers } + set { config.maxCachedUsers = newValue } } ///LDConfig constructor. Configurable values are all set to their default values. The client app can modify these values as desired. Note that client app developers may prefer to get the LDConfig from `LDClient.config` (`ObjcLDClient.config`) in order to retain previously set values. @@ -221,9 +158,7 @@ public final class ObjcLDConfig: NSObject { ///Compares the settable properties in 2 LDConfig structs @objc public func isEqual(object: Any?) -> Bool { guard let other = object as? ObjcLDConfig - else { - return false - } + else { return false } return config == other.config } } diff --git a/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDFlagValue.swift b/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDFlagValue.swift index 456d61ae..4458a1c8 100644 --- a/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDFlagValue.swift +++ b/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDFlagValue.swift @@ -2,7 +2,6 @@ // ObjcLDFlagValue.swift // LaunchDarkly // -// Created by Mark Pokorny on 9/12/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -12,10 +11,10 @@ import Foundation @objc(LDFlagValue) public final class ObjcLDFlagValue: NSObject { let flagVal: LDFlagValue - + ///String representation of the type of the feature flag. @objc public var flagValueType: String? { - return flagVal.typeString + flagVal.typeString } init(_ flagValue: LDFlagValue) { @@ -68,9 +67,7 @@ public class ObjcLDFlagValueSource: NSObject { ///Initializer that takes an integer and returns the LDFlagValueSource provided the integer matches one of the LDFlagValueSource constants. Otherwise, returns nil. @objc public init?(rawValue: Int) { guard rawValue >= ObjcLDFlagValueSource.nilSource && rawValue <= ObjcLDFlagValueSource.typeMismatch - else { - return nil - } + else { return nil } self.typeMismatch = rawValue == ObjcLDFlagValueSource.typeMismatch self.flagValueSource = LDFlagValueSource(rawValue: rawValue) super.init() @@ -111,16 +108,14 @@ public class ObjcLDFlagValueSource: NSObject { ///Compares a LDFlagValueSource to an Int, returning true when the receiver has the same raw value as the constantValue. @objc public func isEqual(toConstant constantValue: Int) -> Bool { - return rawValue == constantValue + rawValue == constantValue } } private extension LDFlagValueSource { init?(rawValue: Int) { guard rawValue >= ObjcLDFlagValueSource.server && rawValue <= ObjcLDFlagValueSource.fallback - else { - return nil - } + else { return nil } switch rawValue { case ObjcLDFlagValueSource.server: self = .server @@ -148,7 +143,7 @@ extension LDFlagValueSource { self.init(rawValue: intValue) } var intRawValue: Int { - return self.intValue + self.intValue } } #endif diff --git a/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDUser.swift b/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDUser.swift index ea6ac382..2c2ad23a 100644 --- a/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDUser.swift +++ b/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDUser.swift @@ -2,7 +2,6 @@ // LDUserObject.swift // LaunchDarkly // -// Created by Mark Pokorny on 9/7/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -18,7 +17,7 @@ import Foundation @objc (LDUser) public final class ObjcLDUser: NSObject { var user: LDUser - + /** LDUser attributes that can be marked private. @@ -27,39 +26,39 @@ public final class ObjcLDUser: NSObject { See Also: `ObjcLDConfig.allUserAttributesPrivate`, `ObjcLDConfig.privateUserAttributes`, and `privateAttributes`. */ @objc public class var privatizableAttributes: [String] { - return LDUser.privatizableAttributes + LDUser.privatizableAttributes } ///LDUser name attribute used to make `name` private @objc public class var attributeName: String { - return LDUser.CodingKeys.name.rawValue + LDUser.CodingKeys.name.rawValue } ///LDUser firstName attribute used to make `firstName` private @objc public class var attributeFirstName: String { - return LDUser.CodingKeys.firstName.rawValue + LDUser.CodingKeys.firstName.rawValue } ///LDUser lastName attribute used to make `lastName` private @objc public class var attributeLastName: String { - return LDUser.CodingKeys.lastName.rawValue + LDUser.CodingKeys.lastName.rawValue } ///LDUser country attribute used to make `country` private @objc public class var attributeCountry: String { - return LDUser.CodingKeys.country.rawValue + LDUser.CodingKeys.country.rawValue } ///LDUser ipAddress attribute used to make `ipAddress` private @objc public class var attributeIPAddress: String { - return LDUser.CodingKeys.ipAddress.rawValue + LDUser.CodingKeys.ipAddress.rawValue } ///LDUser email attribute used to make `email` private @objc public class var attributeEmail: String { - return LDUser.CodingKeys.email.rawValue + LDUser.CodingKeys.email.rawValue } ///LDUser avatar attribute used to make `avatar` private @objc public class var attributeAvatar: String { - return LDUser.CodingKeys.avatar.rawValue + LDUser.CodingKeys.avatar.rawValue } ///LDUser custom attribute used to make `custom` private @objc public class var attributeCustom: String { - return LDUser.CodingKeys.custom.rawValue + LDUser.CodingKeys.custom.rawValue } ///Client app defined string that uniquely identifies the user. If the client app does not define a key, the SDK will assign an identifier associated with the anonymous user. The key cannot be made private. @@ -68,102 +67,58 @@ public final class ObjcLDUser: NSObject { } ///Client app defined name for the user. (Default: nil) @objc public var name: String? { - get { - return user.name - } - set { - user.name = newValue - } + get { user.name } + set { user.name = newValue } } ///Client app defined first name for the user. (Default: nil) @objc public var firstName: String? { - get { - return user.firstName - } - set { - user.firstName = newValue - } + get { user.firstName } + set { user.firstName = newValue } } ///Client app defined last name for the user. (Default: nil) @objc public var lastName: String? { - get { - return user.lastName - } - set { - user.lastName = newValue - } + get { user.lastName } + set { user.lastName = newValue } } ///Client app defined country for the user. (Default: nil) @objc public var country: String? { - get { - return user.country - } - set { - user.country = newValue - } + get { user.country } + set { user.country = newValue } } ///Client app defined ipAddress for the user. (Default: nil) @objc public var ipAddress: String? { - get { - return user.ipAddress - } - set { - user.ipAddress = newValue - } + get { user.ipAddress } + set { user.ipAddress = newValue } } ///Client app defined email address for the user. (Default: nil) @objc public var email: String? { - get { - return user.email - } - set { - user.email = newValue - } + get { user.email } + set { user.email = newValue } } ///Client app defined avatar for the user. (Default: nil) @objc public var avatar: String? { - get { - return user.avatar - } - set { - user.avatar = newValue - } + get { user.avatar } + set { user.avatar = newValue } } ///Client app defined dictionary for the user. The client app may declare top level dictionary items as private. If the client app defines custom as private, the SDK considers the dictionary private except for device & operatingSystem (which cannot be made private). See `privateAttributes` for details. (Default: nil) @objc public var custom: [String: Any]? { - get { - return user.custom - } - set { - user.custom = newValue - } + get { user.custom } + set { user.custom = newValue } } ///Client app defined isAnonymous for the user. If the client app does not define isAnonymous, the SDK will use the `key` to set this attribute. isAnonymous cannot be made private. (Default: YES) @objc public var isAnonymous: Bool { - get { - return user.isAnonymous - } - set { - user.isAnonymous = newValue - } + get { user.isAnonymous } + set { user.isAnonymous = newValue } } ///Client app defined device for the user. The SDK will determine the device automatically, however the client app can override the value. The SDK will insert the device into the `custom` dictionary. The device cannot be made private. (Default: the system identified device) @objc public var device: String? { - get { - return user.device - } - set { - user.device = newValue - } + get { user.device } + set { user.device = newValue } } ///Client app defined operatingSystem for the user. The SDK will determine the operatingSystem automatically, however the client app can override the value. The SDK will insert the operatingSystem into the `custom` dictionary. The operatingSystem cannot be made private. (Default: the system identified operating system) @objc public var operatingSystem: String? { - get { - return user.operatingSystem - } - set { - user.operatingSystem = newValue - } + get { user.operatingSystem } + set { user.operatingSystem = newValue } } /** Client app defined privateAttributes for the user. @@ -176,18 +131,14 @@ public final class ObjcLDUser: NSObject { */ @objc public var privateAttributes: [String]? { - get { - return user.privateAttributes - } - set { - user.privateAttributes = newValue - } + get { user.privateAttributes } + set { user.privateAttributes = newValue } } /** Initializer to create a LDUser. Client configurable attributes are set to their default value. The SDK will automatically set `key`, `device`, `operatingSystem`, and `isAnonymous` attributes. The SDK embeds `device` and `operatingSystem` into the `custom` dictionary for transmission to LaunchDarkly. */ - @objc public override init() { + @objc override public init() { user = LDUser() } @@ -212,9 +163,7 @@ public final class ObjcLDUser: NSObject { */ @objc public init?(object: Any?) { guard let userDictionary = object as? [String: Any] - else { - return nil - } + else { return nil } self.user = LDUser(userDictionary: userDictionary) } @@ -230,9 +179,7 @@ public final class ObjcLDUser: NSObject { ///Compares users by comparing their user keys only, to allow the client app to collect user information over time @objc public func isEqual(object: Any) -> Bool { guard let otherUser = object as? ObjcLDUser - else { - return false - } + else { return false } return user == otherUser.user } } diff --git a/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDVariationValue.swift b/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDVariationValue.swift index 6e11e9f9..541fbbcc 100644 --- a/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDVariationValue.swift +++ b/LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDVariationValue.swift @@ -2,7 +2,6 @@ // ObjcLDVariationValue.swift // LaunchDarkly // -// Created by Mark Pokorny on 9/14/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -15,15 +14,13 @@ public final class ObjcLDBoolVariationValue: NSObject { @objc public let value: Bool ///The feature flag value's source @objc public let source: ObjcLDFlagValueSource - + init(_ variationValue: (value: Bool, source: LDFlagValueSource)) { self.value = variationValue.value self.source = ObjcLDFlagValueSource(variationValue.source) } ///A string representation of the feature flag value's source - @objc public var sourceString: String { - return source.stringValue - } + @objc public var sourceString: String { source.stringValue } } ///Objective-C object that contains an NSInteger feature flag's value and source. @@ -33,16 +30,14 @@ public final class ObjcLDIntegerVariationValue: NSObject { @objc public let value: Int ///The feature flag value's source @objc public let source: ObjcLDFlagValueSource - + init(_ variationValue: (value: Int, source: LDFlagValueSource)) { self.value = variationValue.value self.source = ObjcLDFlagValueSource(variationValue.source) } ///A string representation of the feature flag value's source - @objc public var sourceString: String { - return source.stringValue - } + @objc public var sourceString: String { source.stringValue } } ///Objective-C object that contains a double feature flag's value and source. @@ -52,16 +47,14 @@ public final class ObjcLDDoubleVariationValue: NSObject { @objc public let value: Double ///The feature flag value's source @objc public let source: ObjcLDFlagValueSource - + init(_ variationValue: (value: Double, source: LDFlagValueSource)) { self.value = variationValue.value self.source = ObjcLDFlagValueSource(variationValue.source) } ///A string representation of the feature flag value's source - @objc public var sourceString: String { - return source.stringValue - } + @objc public var sourceString: String { source.stringValue } } ///Objective-C object that contains an NSString feature flag's value and source. @@ -71,16 +64,14 @@ public final class ObjcLDStringVariationValue: NSObject { @objc public let value: String? ///The feature flag value's source @objc public let source: ObjcLDFlagValueSource - + init(_ variationValue: (value: String?, source: LDFlagValueSource)) { self.value = variationValue.value self.source = ObjcLDFlagValueSource(variationValue.source) } ///A string representation of the feature flag value's source - @objc public var sourceString: String { - return source.stringValue - } + @objc public var sourceString: String { source.stringValue } } ///Objective-C object that contains an NSArray feature flag's value and source. @@ -90,16 +81,14 @@ public final class ObjcLDArrayVariationValue: NSObject { @objc public let value: [Any]? ///The feature flag value's source @objc public let source: ObjcLDFlagValueSource - + init(_ variationValue: (value: [Any]?, source: LDFlagValueSource)) { self.value = variationValue.value self.source = ObjcLDFlagValueSource(variationValue.source) } ///A string representation of the feature flag value's source - @objc public var sourceString: String { - return source.stringValue - } + @objc public var sourceString: String { source.stringValue } } ///Objective-C object that contains an NSDictionary feature flag's value and source. @@ -109,14 +98,12 @@ public final class ObjcLDDictionaryVariationValue: NSObject { @objc public let value: [String: Any]? ///The feature flag value's source @objc public let source: ObjcLDFlagValueSource - + init(_ variationValue: (value: [String: Any]?, source: LDFlagValueSource)) { self.value = variationValue.value self.source = ObjcLDFlagValueSource(variationValue.source) } ///A string representation of the feature flag value's source - @objc public var sourceString: String { - return source.stringValue - } + @objc public var sourceString: String { source.stringValue } } diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/CacheConverter.swift b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/CacheConverter.swift index 033a2f43..01dcb6b4 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/CacheConverter.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/CacheConverter.swift @@ -2,7 +2,6 @@ // CacheConverter.swift // LaunchDarkly // -// Created by Mark Pokorny on 3/26/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -29,10 +28,10 @@ final class CacheConverter: CacheConverting { private(set) var deprecatedCaches = [DeprecatedCacheModel: DeprecatedCache]() let maxAge: TimeInterval - init(serviceFactory: ClientServiceCreating, maxAge: TimeInterval = Constants.maxAge) { - currentCache = serviceFactory.makeFeatureFlagCache() + init(serviceFactory: ClientServiceCreating, maxCachedUsers: Int, maxAge: TimeInterval = Constants.maxAge) { + currentCache = serviceFactory.makeFeatureFlagCache(maxCachedUsers: maxCachedUsers) self.maxAge = maxAge - DeprecatedCacheModel.allCases.forEach { (version) in + DeprecatedCacheModel.allCases.forEach { version in deprecatedCaches[version] = serviceFactory.makeDeprecatedCacheModel(version) } } @@ -40,7 +39,7 @@ final class CacheConverter: CacheConverting { func convertCacheData(for user: LDUser, and config: LDConfig) { var mobileKeys = [MobileKey]() //TODO: When implementing Multiple Environments, initialize this with the secondary mobile keys mobileKeys.insert(config.mobileKey, at: 0) - mobileKeys.forEach { (mobileKey) in + mobileKeys.forEach { mobileKey in convertCacheData(for: user, mobileKey: mobileKey) } removeData() @@ -65,7 +64,7 @@ final class CacheConverter: CacheConverting { private func removeData() { let maxAge = Date().addingTimeInterval(self.maxAge) - deprecatedCaches.values.forEach { (deprecatedCache) in + deprecatedCaches.values.forEach { deprecatedCache in deprecatedCache.removeData(olderThan: maxAge) } } diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/ConnectionInformationStore.swift b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/ConnectionInformationStore.swift index ad066a88..c79e6386 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/ConnectionInformationStore.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/ConnectionInformationStore.swift @@ -2,7 +2,6 @@ // ConnectionInformationStore.swift // LaunchDarkly_iOS // -// Created by Joe Cieslik on 8/13/19. // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -10,11 +9,11 @@ import Foundation final class ConnectionInformationStore { private static let connectionInformationKey = "com.launchDarkly.ConnectionInformationStore.connectionInformationKey" - + static func retrieveStoredConnectionInformation() -> ConnectionInformation? { - return UserDefaults.standard.retrieve(object: ConnectionInformation.self, fromKey: ConnectionInformationStore.connectionInformationKey) + UserDefaults.standard.retrieve(object: ConnectionInformation.self, fromKey: ConnectionInformationStore.connectionInformationKey) } - + static func storeConnectionInformation(connectionInformation: ConnectionInformation) { UserDefaults.standard.save(customObject: connectionInformation, forKey: ConnectionInformationStore.connectionInformationKey) } @@ -27,7 +26,7 @@ private extension UserDefaults { self.set(encoded, forKey: key) } } - + func retrieve(object type: T.Type, fromKey key: String) -> T? { guard let data = self.data(forKey: key), let object = try? JSONDecoder().decode(type, from: data) diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCache.swift b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCache.swift index b3726494..cc132e71 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCache.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCache.swift @@ -2,7 +2,6 @@ // DeprecatedCache.swift // LaunchDarkly // -// Created by Mark Pokorny on 4/10/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -20,21 +19,17 @@ protocol DeprecatedCache { extension DeprecatedCache { func removeData(olderThan expirationDate: Date) { guard let cachedUserData = keyedValueCache.dictionary(forKey: cachedDataKey) as? [UserKey: [String: Any]], !cachedUserData.isEmpty - else { - return //no cached data - } + else { return } // no cached data let expiredUserKeys = userKeys(from: cachedUserData, olderThan: expirationDate) guard !expiredUserKeys.isEmpty - else { - return //no expired user cached data, leave the cache alone - } + else { return } // no expired user cached data, leave the cache alone guard expiredUserKeys.count != cachedUserData.count else { - keyedValueCache.removeObject(forKey: cachedDataKey) //all user cached data is expired, remove the cache key & values + keyedValueCache.removeObject(forKey: cachedDataKey) //all user cached data is expired, remove the cache key & values return } let unexpiredUserData: [UserKey: [String: Any]] = cachedUserData.filter { (userKey, _) in - return !expiredUserKeys.contains(userKey) + !expiredUserKeys.contains(userKey) } keyedValueCache.set(unexpiredUserData, forKey: cachedDataKey) } @@ -51,7 +46,7 @@ private extension LDUser.CodingKeys { extension Dictionary where Key == String, Value == Any { var lastUpdated: Date? { - return (self[LDUser.CodingKeys.lastUpdated] as? String)?.dateValue + (self[LDUser.CodingKeys.lastUpdated] as? String)?.dateValue } } diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV2.swift b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV2.swift index 964febf3..48414709 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV2.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV2.swift @@ -2,7 +2,6 @@ // UserDictionaryCacheModel.swift // LaunchDarkly // -// Created by Mark Pokorny on 3/26/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -46,18 +45,16 @@ final class DeprecatedCacheModelV2: DeprecatedCache { else { return (nil, nil) } - let featureFlags = Dictionary(uniqueKeysWithValues: featureFlagDictionaries.compactMap { (flagKey, value) in - return (flagKey, FeatureFlag(flagKey: flagKey, value: value, variation: nil, version: nil, flagVersion: nil, eventTrackingContext: nil, reason: nil, trackReason: nil)) + let featureFlags = Dictionary(uniqueKeysWithValues: featureFlagDictionaries.compactMap { flagKey, value in + (flagKey, FeatureFlag(flagKey: flagKey, value: value, variation: nil, version: nil, flagVersion: nil, eventTrackingContext: nil, reason: nil, trackReason: nil)) }) return (featureFlags, cachedUserDictionary.lastUpdated) } func userKeys(from cachedUserData: [UserKey: [String: Any]], olderThan expirationDate: Date) -> [UserKey] { - return cachedUserData.filter { (_, userDictionary) in + cachedUserData.compactMap { userKey, userDictionary in let lastUpdated = userDictionary.lastUpdated ?? Date.distantFuture - return lastUpdated.isExpired(expirationDate: expirationDate) - }.map { (userKey, _) in - return userKey + return lastUpdated.isExpired(expirationDate: expirationDate) ? userKey : nil } } } diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV3.swift b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV3.swift index 229d21ef..2e50e951 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV3.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV3.swift @@ -2,7 +2,6 @@ // DeprecatedCacheModelV3.swift // LaunchDarkly // -// Created by Mark Pokorny on 3/28/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -52,24 +51,22 @@ final class DeprecatedCacheModelV3: DeprecatedCache { return (nil, nil) } let featureFlags = Dictionary(uniqueKeysWithValues: featureFlagDictionaries.compactMap { (flagKey, flagValueDictionary) in - return (flagKey, FeatureFlag(flagKey: flagKey, - value: flagValueDictionary.value, - variation: nil, - version: flagValueDictionary.version, - flagVersion: nil, - eventTrackingContext: nil, - reason: nil, - trackReason: nil)) + (flagKey, FeatureFlag(flagKey: flagKey, + value: flagValueDictionary.value, + variation: nil, + version: flagValueDictionary.version, + flagVersion: nil, + eventTrackingContext: nil, + reason: nil, + trackReason: nil)) }) return (featureFlags, cachedUserDictionary.lastUpdated) } func userKeys(from cachedUserData: [UserKey: [String: Any]], olderThan expirationDate: Date) -> [UserKey] { - return cachedUserData.filter { (_, userDictionary) in + cachedUserData.compactMap { userKey, userDictionary in let lastUpdated = userDictionary.lastUpdated ?? Date.distantFuture - return lastUpdated.isExpired(expirationDate: expirationDate) - }.map { (userKey, _) in - return userKey + return lastUpdated.isExpired(expirationDate: expirationDate) ? userKey : nil } } } diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV4.swift b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV4.swift index 67cea594..c8dd6790 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV4.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV4.swift @@ -2,7 +2,6 @@ // DeprecatedCacheModelV4.swift // LaunchDarkly // -// Created by Mark Pokorny on 3/28/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -56,24 +55,22 @@ final class DeprecatedCacheModelV4: DeprecatedCache { return (nil, nil) } let featureFlags = Dictionary(uniqueKeysWithValues: featureFlagDictionaries.compactMap { (flagKey, flagValueDictionary) in - return (flagKey, FeatureFlag(flagKey: flagKey, - value: flagValueDictionary.value, - variation: flagValueDictionary.variation, - version: flagValueDictionary.version, - flagVersion: flagValueDictionary.flagVersion, - eventTrackingContext: EventTrackingContext(dictionary: flagValueDictionary), - reason: nil, - trackReason: nil)) + (flagKey, FeatureFlag(flagKey: flagKey, + value: flagValueDictionary.value, + variation: flagValueDictionary.variation, + version: flagValueDictionary.version, + flagVersion: flagValueDictionary.flagVersion, + eventTrackingContext: EventTrackingContext(dictionary: flagValueDictionary), + reason: nil, + trackReason: nil)) }) return (featureFlags, cachedUserDictionary.lastUpdated) } func userKeys(from cachedUserData: [UserKey: [String: Any]], olderThan expirationDate: Date) -> [UserKey] { - return cachedUserData.filter { (_, userDictionary) in + cachedUserData.compactMap { userKey, userDictionary in let lastUpdated = userDictionary.lastUpdated ?? Date.distantFuture - return lastUpdated.isExpired(expirationDate: expirationDate) - }.map { (userKey, _) in - return userKey + return lastUpdated.isExpired(expirationDate: expirationDate) ? userKey : nil } } } diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV5.swift b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV5.swift index 5116e9ce..1e50595a 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV5.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV5.swift @@ -2,7 +2,6 @@ // UserEnvironmentCacheModel.swift // LaunchDarkly // -// Created by Mark Pokorny on 3/26/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -82,11 +81,9 @@ final class DeprecatedCacheModelV5: DeprecatedCache { } func userKeys(from cachedUserData: [UserKey: [String: Any]], olderThan expirationDate: Date) -> [UserKey] { - return cachedUserData.filter { (_, userEnvironmentsDictionary) in - let lastUpdated = userEnvironmentsDictionary.environments?.lastUpdatedDates?.youngest ?? Date.distantFuture - return lastUpdated.isExpired(expirationDate: expirationDate) - }.map { (userKey, _) in - return userKey + cachedUserData.compactMap { userKey, userEnvsDictionary in + let lastUpdated = userEnvsDictionary.environments?.lastUpdatedDates?.youngest ?? Date.distantFuture + return lastUpdated.isExpired(expirationDate: expirationDate) ? userKey : nil } } } diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV6.swift b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV6.swift index dc8249d2..a27d5339 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV6.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/DeprecatedCacheModelV6.swift @@ -2,7 +2,6 @@ // DeprecatedCacheModelV6.swift // LaunchDarkly_iOS // -// Created by Joe Cieslik on 11/4/19. // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -47,20 +46,20 @@ import Foundation ] */ final class DeprecatedCacheModelV6: DeprecatedCache { - + struct CacheKeys { static let userEnvironments = "com.launchdarkly.dataManager.userEnvironments" static let environments = "environments" } - + let model = DeprecatedCacheModel.version6 let keyedValueCache: KeyedValueCaching let cachedDataKey = CacheKeys.userEnvironments - + init(keyedValueCache: KeyedValueCaching) { self.keyedValueCache = keyedValueCache } - + func retrieveFlags(for userKey: UserKey, and mobileKey: MobileKey) -> (featureFlags: [LDFlagKey: FeatureFlag]?, lastUpdated: Date?) { guard let cachedUserEnvironmentsCollection = keyedValueCache.dictionary(forKey: cachedDataKey), !cachedUserEnvironmentsCollection.isEmpty, let cachedUserEnvironments = cachedUserEnvironmentsCollection[userKey] as? [String: Any], !cachedUserEnvironments.isEmpty, @@ -71,49 +70,42 @@ final class DeprecatedCacheModelV6: DeprecatedCache { return (nil, nil) } let featureFlags = Dictionary(uniqueKeysWithValues: featureFlagDictionaries.compactMap { (flagKey, featureFlagDictionary) in - return (flagKey, FeatureFlag(flagKey: flagKey, - value: featureFlagDictionary.value, - variation: featureFlagDictionary.variation, - version: featureFlagDictionary.version, - flagVersion: featureFlagDictionary.flagVersion, - eventTrackingContext: EventTrackingContext(dictionary: featureFlagDictionary), - reason: featureFlagDictionary.reason, - trackReason: featureFlagDictionary.trackReason)) + (flagKey, FeatureFlag(flagKey: flagKey, + value: featureFlagDictionary.value, + variation: featureFlagDictionary.variation, + version: featureFlagDictionary.version, + flagVersion: featureFlagDictionary.flagVersion, + eventTrackingContext: EventTrackingContext(dictionary: featureFlagDictionary), + reason: featureFlagDictionary.reason, + trackReason: featureFlagDictionary.trackReason)) }) return (featureFlags, cachedUserDictionary.lastUpdated) } - + func userKeys(from cachedUserData: [UserKey: [String: Any]], olderThan expirationDate: Date) -> [UserKey] { - return cachedUserData.filter { (_, userEnvironmentsDictionary) in - let lastUpdated = userEnvironmentsDictionary.environments?.lastUpdatedDates?.youngest ?? Date.distantFuture - return lastUpdated.isExpired(expirationDate: expirationDate) - }.map { (userKey, _) in - return userKey + cachedUserData.compactMap { userKey, userEnvsDictionary in + let lastUpdated = userEnvsDictionary.environments?.lastUpdatedDates?.youngest ?? Date.distantFuture + return lastUpdated.isExpired(expirationDate: expirationDate) ? userKey : nil } } } extension Dictionary where Key == String, Value == Any { var environments: [MobileKey: [String: Any]]? { - return self[DeprecatedCacheModelV6.CacheKeys.environments] as? [MobileKey: [String: Any]] + self[DeprecatedCacheModelV6.CacheKeys.environments] as? [MobileKey: [String: Any]] } } extension Dictionary where Key == MobileKey, Value == [String: Any] { var lastUpdatedDates: [Date]? { - return compactMap { (_, userDictionary) in - return userDictionary.lastUpdated - } + compactMap { (_, userDictionary) in userDictionary.lastUpdated } } } extension Array where Element == Date { - var youngest: Date? { - return sorted.last - } - + var youngest: Date? { sorted.last } var sorted: [Date] { - return self.sorted { (date1, date2) -> Bool in + self.sorted { date1, date2 -> Bool in date1.isEarlierThan(date2) } } diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/KeyedValueCache.swift b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/KeyedValueCache.swift index 4fba93e4..7662e812 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/KeyedValueCache.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/KeyedValueCache.swift @@ -2,7 +2,6 @@ // KeyedValueCache.swift // LaunchDarkly // -// Created by Mark Pokorny on 12/6/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/UserEnvironmentFlagCache.swift b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/UserEnvironmentFlagCache.swift index 5368b8da..92a63e62 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/Cache/UserEnvironmentFlagCache.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/Cache/UserEnvironmentFlagCache.swift @@ -2,7 +2,6 @@ // UserEnvironmentCache.swift // LaunchDarkly // -// Created by Mark Pokorny on 3/20/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -14,6 +13,8 @@ enum FlagCachingStoreMode: CaseIterable { //sourcery: autoMockable protocol FeatureFlagCaching { + //sourcery: defaultMockValue = 5 + var maxCachedUsers: Int { get set } func retrieveFeatureFlags(forUserWithKey userKey: String, andMobileKey mobileKey: String) -> [LDFlagKey: FeatureFlag]? func storeFeatureFlags(_ featureFlags: [LDFlagKey: FeatureFlag], forUser user: LDUser, andMobileKey mobileKey: String, lastUpdated: Date, storeMode: FlagCachingStoreMode) } @@ -22,7 +23,6 @@ final class UserEnvironmentFlagCache: FeatureFlagCaching { struct Constants { static let cacheStoreOperationQueueLabel = "com.launchDarkly.FeatureFlagCaching.cacheStoreOperationQueue" - static let maxCachedUsers = 5 } struct CacheKeys { @@ -30,11 +30,13 @@ final class UserEnvironmentFlagCache: FeatureFlagCaching { } private(set) var keyedValueCache: KeyedValueCaching + var maxCachedUsers: Int - static private let cacheStoreOperationQueue = DispatchQueue(label: Constants.cacheStoreOperationQueueLabel, qos: .background) + private static let cacheStoreOperationQueue = DispatchQueue(label: Constants.cacheStoreOperationQueueLabel, qos: .background) - init(withKeyedValueCache keyedValueCache: KeyedValueCaching) { + init(withKeyedValueCache keyedValueCache: KeyedValueCaching, maxCachedUsers: Int) { self.keyedValueCache = keyedValueCache + self.maxCachedUsers = maxCachedUsers } func retrieveFeatureFlags(forUserWithKey userKey: String, andMobileKey mobileKey: String) -> [LDFlagKey: FeatureFlag]? { @@ -86,28 +88,23 @@ final class UserEnvironmentFlagCache: FeatureFlagCaching { } private func removeOldestUsersIfNeeded(from cacheableUserEnvironmentsCollection: [UserKey: CacheableUserEnvironmentFlags]) -> [UserKey: CacheableUserEnvironmentFlags] { - guard cacheableUserEnvironmentsCollection.count > Constants.maxCachedUsers + guard cacheableUserEnvironmentsCollection.count > maxCachedUsers && maxCachedUsers >= 0 else { return cacheableUserEnvironmentsCollection } //sort collection into key-value pairs in descending order...youngest to oldest - var userEnvironmentsCollection = cacheableUserEnvironmentsCollection.sorted { (pair1, pair2) -> Bool in - return pair2.value.lastUpdated.isEarlierThan(pair1.value.lastUpdated) + var userEnvironmentsCollection = cacheableUserEnvironmentsCollection.sorted { pair1, pair2 -> Bool in + pair2.value.lastUpdated.isEarlierThan(pair1.value.lastUpdated) } - while userEnvironmentsCollection.count > Constants.maxCachedUsers { + while userEnvironmentsCollection.count > maxCachedUsers && maxCachedUsers >= 0 { userEnvironmentsCollection.removeLast() } - return [UserKey: CacheableUserEnvironmentFlags](userEnvironmentsCollection, uniquingKeysWith: { (value1, _) in - return value1 + return [UserKey: CacheableUserEnvironmentFlags](userEnvironmentsCollection, uniquingKeysWith: { value1, _ in + value1 }) } private func retrieveCacheableUserEnvironmentsCollection() -> [UserKey: CacheableUserEnvironmentFlags] { - guard let retrievedCollection = keyedValueCache.dictionary(forKey: CacheKeys.cachedUserEnvironmentFlags), - let cacheableUserEnvironmentsCollection = CacheableUserEnvironmentFlags.makeCollection(from: retrievedCollection) - else { - return [:] - } - return cacheableUserEnvironmentsCollection + CacheableUserEnvironmentFlags.makeCollection(from: keyedValueCache.dictionary(forKey: CacheKeys.cachedUserEnvironmentFlags) ?? [:]) } } diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/ClientServiceFactory.swift b/LaunchDarkly/LaunchDarkly/Service Objects/ClientServiceFactory.swift index ca2f8582..a7a4b244 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/ClientServiceFactory.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/ClientServiceFactory.swift @@ -2,7 +2,6 @@ // ClientServiceFactory.swift // LaunchDarkly // -// Created by Mark Pokorny on 11/13/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -11,8 +10,8 @@ import DarklyEventSource protocol ClientServiceCreating { func makeKeyedValueCache() -> KeyedValueCaching - func makeFeatureFlagCache() -> FeatureFlagCaching - func makeCacheConverter() -> CacheConverting + func makeFeatureFlagCache(maxCachedUsers: Int) -> FeatureFlagCaching + func makeCacheConverter(maxCachedUsers: Int) -> CacheConverting func makeDeprecatedCacheModel(_ model: DeprecatedCacheModel) -> DeprecatedCache func makeDarklyServiceProvider(config: LDConfig, user: LDUser) -> DarklyServiceProvider func makeFlagSynchronizer(streamingMode: LDStreamingMode, pollingInterval: TimeInterval, useReport: Bool, service: DarklyServiceProvider) -> LDFlagSynchronizing @@ -34,15 +33,15 @@ protocol ClientServiceCreating { final class ClientServiceFactory: ClientServiceCreating { func makeKeyedValueCache() -> KeyedValueCaching { - return UserDefaults.standard + UserDefaults.standard } - func makeFeatureFlagCache() -> FeatureFlagCaching { - return UserEnvironmentFlagCache(withKeyedValueCache: makeKeyedValueCache()) + func makeFeatureFlagCache(maxCachedUsers: Int) -> FeatureFlagCaching { + UserEnvironmentFlagCache(withKeyedValueCache: makeKeyedValueCache(), maxCachedUsers: maxCachedUsers) } - func makeCacheConverter() -> CacheConverting { - return CacheConverter(serviceFactory: self) + func makeCacheConverter(maxCachedUsers: Int) -> CacheConverting { + CacheConverter(serviceFactory: self, maxCachedUsers: maxCachedUsers) } func makeDeprecatedCacheModel(_ model: DeprecatedCacheModel) -> DeprecatedCache { @@ -56,11 +55,11 @@ final class ClientServiceFactory: ClientServiceCreating { } func makeDarklyServiceProvider(config: LDConfig, user: LDUser) -> DarklyServiceProvider { - return DarklyService(config: config, user: user, serviceFactory: self) + DarklyService(config: config, user: user, serviceFactory: self) } func makeFlagSynchronizer(streamingMode: LDStreamingMode, pollingInterval: TimeInterval, useReport: Bool, service: DarklyServiceProvider) -> LDFlagSynchronizing { - return makeFlagSynchronizer(streamingMode: streamingMode, pollingInterval: pollingInterval, useReport: useReport, service: service, onSyncComplete: nil) + makeFlagSynchronizer(streamingMode: streamingMode, pollingInterval: pollingInterval, useReport: useReport, service: service, onSyncComplete: nil) } func makeFlagSynchronizer(streamingMode: LDStreamingMode, @@ -68,42 +67,42 @@ final class ClientServiceFactory: ClientServiceCreating { useReport: Bool, service: DarklyServiceProvider, onSyncComplete: FlagSyncCompleteClosure?) -> LDFlagSynchronizing { - return FlagSynchronizer(streamingMode: streamingMode, pollingInterval: pollingInterval, useReport: useReport, service: service, onSyncComplete: onSyncComplete) + FlagSynchronizer(streamingMode: streamingMode, pollingInterval: pollingInterval, useReport: useReport, service: service, onSyncComplete: onSyncComplete) } func makeFlagChangeNotifier() -> FlagChangeNotifying { - return FlagChangeNotifier() + FlagChangeNotifier() } func makeEventReporter(config: LDConfig, service: DarklyServiceProvider) -> EventReporting { - return makeEventReporter(config: config, service: service, onSyncComplete: nil) + makeEventReporter(config: config, service: service, onSyncComplete: nil) } func makeEventReporter(config: LDConfig, service: DarklyServiceProvider, onSyncComplete: EventSyncCompleteClosure? = nil) -> EventReporting { - return EventReporter(config: config, service: service, onSyncComplete: onSyncComplete) + EventReporter(config: config, service: service, onSyncComplete: onSyncComplete) } func makeStreamingProvider(url: URL, httpHeaders: [String: String]) -> DarklyStreamingProvider { - return LDEventSource(url: url, httpHeaders: httpHeaders) + LDEventSource(url: url, httpHeaders: httpHeaders) } func makeStreamingProvider(url: URL, httpHeaders: [String: String], connectMethod: String?, connectBody: Data?) -> DarklyStreamingProvider { - return LDEventSource(url: url, httpHeaders: httpHeaders, connectMethod: connectMethod, connectBody: connectBody) + LDEventSource(url: url, httpHeaders: httpHeaders, connectMethod: connectMethod, connectBody: connectBody) } func makeEnvironmentReporter() -> EnvironmentReporting { - return EnvironmentReporter() + EnvironmentReporter() } func makeThrottler(maxDelay: TimeInterval, environmentReporter: EnvironmentReporting) -> Throttling { - return Throttler(maxDelay: maxDelay, environmentReporter: environmentReporter) + Throttler(maxDelay: maxDelay, environmentReporter: environmentReporter) } func makeErrorNotifier() -> ErrorNotifying { - return ErrorNotifier() + ErrorNotifier() } func makeConnectionInformation() -> ConnectionInformation { - return ConnectionInformation(currentConnectionMode: .offline, lastConnectionFailureReason: .none) + ConnectionInformation(currentConnectionMode: .offline, lastConnectionFailureReason: .none) } } diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/EnvironmentReporter.swift b/LaunchDarkly/LaunchDarkly/Service Objects/EnvironmentReporter.swift index 4e13736d..87dadd1c 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/EnvironmentReporter.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/EnvironmentReporter.swift @@ -22,21 +22,21 @@ enum OperatingSystem: String { case iOS, watchOS, macOS, tvOS static var allOperatingSystems: [OperatingSystem] { - return [.iOS, .watchOS, .macOS, .tvOS] + [.iOS, .watchOS, .macOS, .tvOS] } var isBackgroundEnabled: Bool { - return OperatingSystem.backgroundEnabledOperatingSystems.contains(self) + OperatingSystem.backgroundEnabledOperatingSystems.contains(self) } static var backgroundEnabledOperatingSystems: [OperatingSystem] { - return [.macOS] + [.macOS] } var isStreamingEnabled: Bool { - return OperatingSystem.streamingEnabledOperatingSystems.contains(self) + OperatingSystem.streamingEnabledOperatingSystems.contains(self) } static var streamingEnabledOperatingSystems: [OperatingSystem] { - return [.iOS, .macOS, .tvOS] + [.iOS, .macOS, .tvOS] } } @@ -66,13 +66,9 @@ protocol EnvironmentReporting { struct EnvironmentReporter: EnvironmentReporting { #if DEBUG - var isDebugBuild: Bool { - return true - } + var isDebugBuild: Bool { true } #else - var isDebugBuild: Bool { - return false - } + var isDebugBuild: Bool { false } #endif struct Constants { @@ -99,114 +95,54 @@ struct EnvironmentReporter: EnvironmentReporting { } #if os(iOS) - var deviceType: String { - return UIDevice.current.model - } - var systemVersion: String { - return UIDevice.current.systemVersion - } - var systemName: String { - return UIDevice.current.systemName - } - var operatingSystem: OperatingSystem { - return .iOS - } - var backgroundNotification: Notification.Name? { - return UIApplication.didEnterBackgroundNotification - } - var foregroundNotification: Notification.Name? { - return UIApplication.willEnterForegroundNotification - } - var vendorUUID: String? { - return UIDevice.current.identifierForVendor?.uuidString - } + var deviceType: String { UIDevice.current.model } + var systemVersion: String { UIDevice.current.systemVersion } + var systemName: String { UIDevice.current.systemName } + var operatingSystem: OperatingSystem { .iOS } + var backgroundNotification: Notification.Name? { UIApplication.didEnterBackgroundNotification } + var foregroundNotification: Notification.Name? { UIApplication.willEnterForegroundNotification } + var vendorUUID: String? { UIDevice.current.identifierForVendor?.uuidString } #elseif os(watchOS) - var deviceType: String { - return WKInterfaceDevice.current().model - } - var systemVersion: String { - return WKInterfaceDevice.current().systemVersion - } - var systemName: String { - return WKInterfaceDevice.current().systemName - } - var operatingSystem: OperatingSystem { - return .watchOS - } - var backgroundNotification: Notification.Name? { - return nil - } - var foregroundNotification: Notification.Name? { - return nil - } - var vendorUUID: String? { - return nil - } + var deviceType: String { WKInterfaceDevice.current().model } + var systemVersion: String { WKInterfaceDevice.current().systemVersion } + var systemName: String { WKInterfaceDevice.current().systemName } + var operatingSystem: OperatingSystem { .watchOS } + var backgroundNotification: Notification.Name? { nil } + var foregroundNotification: Notification.Name? { nil } + var vendorUUID: String? { nil } #elseif os(OSX) - var deviceType: String { - return Sysctl.modelWithoutVersion - } - var systemVersion: String { - return ProcessInfo.processInfo.operatingSystemVersion.compactVersionString - } - var systemName: String { - return "macOS" - } - var operatingSystem: OperatingSystem { - return .macOS - } - var backgroundNotification: Notification.Name? { - return NSApplication.willResignActiveNotification - } - var foregroundNotification: Notification.Name? { - return NSApplication.didBecomeActiveNotification - } - var vendorUUID: String? { - return nil - } + var deviceType: String { Sysctl.modelWithoutVersion } + var systemVersion: String { ProcessInfo.processInfo.operatingSystemVersion.compactVersionString } + var systemName: String { "macOS" } + var operatingSystem: OperatingSystem { .macOS } + var backgroundNotification: Notification.Name? { NSApplication.willResignActiveNotification } + var foregroundNotification: Notification.Name? { NSApplication.didBecomeActiveNotification } + var vendorUUID: String? { nil } #elseif os(tvOS) - var deviceType: String { - return UIDevice.current.model - } - var systemVersion: String { - return UIDevice.current.systemVersion - } - var systemName: String { - return UIDevice.current.systemName - } - var operatingSystem: OperatingSystem { - return .tvOS - } - var backgroundNotification: Notification.Name? { - return UIApplication.didEnterBackgroundNotification - } - var foregroundNotification: Notification.Name? { - return UIApplication.willEnterForegroundNotification - } - var vendorUUID: String? { - return UIDevice.current.identifierForVendor?.uuidString - } + var deviceType: String { UIDevice.current.model } + var systemVersion: String { UIDevice.current.systemVersion } + var systemName: String { UIDevice.current.systemName } + var operatingSystem: OperatingSystem { .tvOS } + var backgroundNotification: Notification.Name? { UIApplication.didEnterBackgroundNotification } + var foregroundNotification: Notification.Name? { UIApplication.willEnterForegroundNotification } + var vendorUUID: String? { UIDevice.current.identifierForVendor?.uuidString } #endif #if INTEGRATION_HARNESS - var shouldThrottleOnlineCalls: Bool { - return !isDebugBuild - } + var shouldThrottleOnlineCalls: Bool { !isDebugBuild } #else - var shouldThrottleOnlineCalls: Bool { - return true - } + var shouldThrottleOnlineCalls: Bool { true } #endif var sdkVersion: String { - return Bundle(for: LDClient.self).infoDictionary?["CFBundleShortVersionString"] as? String ?? "4.x" + Bundle(for: LDClient.self).infoDictionary?["CFBundleShortVersionString"] as? String ?? "4.x" } } #if os(OSX) extension OperatingSystemVersion { var compactVersionString: String { - return "\(majorVersion).\(minorVersion).\(patchVersion)" + "\(majorVersion).\(minorVersion).\(patchVersion)" } } @@ -223,16 +159,14 @@ private extension String { func substring(_ range: NSRange) -> String? { guard range.location >= 0 && range.location < self.count, range.location + range.length >= 0 && range.location + range.length < self.count - else { - return nil - } + else { return nil } let startIndex = index(self.startIndex, offsetBy: range.location) let endIndex = index(self.startIndex, offsetBy: range.length) return String(self[startIndex.. String? { guard let match = self.firstMatch(in: string, options: [], range: string.range), let group = string.substring(match.range(at: 1)) - else { - return nil - } + else { return nil } return group } } diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/ErrorNotifier.swift b/LaunchDarkly/LaunchDarkly/Service Objects/ErrorNotifier.swift index 3b1a564b..9c7c9c3f 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/ErrorNotifier.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/ErrorNotifier.swift @@ -2,7 +2,6 @@ // ErrorNotifier.swift // Darkly // -// Created by Mark Pokorny on 2/6/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -23,22 +22,16 @@ final class ErrorNotifier: ErrorNotifying { } func removeObservers(for owner: LDObserverOwner) { - errorObservers = errorObservers.filter { (observer) in - return observer.owner !== owner - } + errorObservers = errorObservers.filter { $0.owner !== owner } } func notifyObservers(of error: Error) { removeOldObservers() - errorObservers.forEach { (errorObserver) in - errorObserver.errorHandler?(error) - } + errorObservers.forEach { $0.errorHandler?(error) } } private func removeOldObservers() { - errorObservers = errorObservers.filter { (errorObserver) in - return errorObserver.owner != nil - } + errorObservers = errorObservers.filter { $0.owner != nil } } } diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/EventReporter.swift b/LaunchDarkly/LaunchDarkly/Service Objects/EventReporter.swift index 8e02f3b1..50c5474b 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/EventReporter.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/EventReporter.swift @@ -2,7 +2,6 @@ // EventReporter.swift // LaunchDarkly // -// Created by Mark Pokorny on 7/24/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -46,7 +45,7 @@ class EventReporter: EventReporting { fileprivate struct Constants { static let eventQueueLabel = "com.launchdarkly.eventSyncQueue" } - + var config: LDConfig { didSet { Log.debug(typeName(and: #function, appending: ": ") + "\(config)") @@ -65,11 +64,9 @@ class EventReporter: EventReporting { var service: DarklyServiceProvider private(set) var eventStore = [[String: Any]]() private(set) var flagRequestTracker = FlagRequestTracker() - + private var eventReportTimer: TimeResponding? - var isReportingActive: Bool { - return eventReportTimer != nil - } + var isReportingActive: Bool { eventReportTimer != nil } private let onSyncComplete: EventSyncCompleteClosure? @@ -87,15 +84,14 @@ class EventReporter: EventReporting { completion?() } } - guard !self.isEventStoreFull - else { + if self.eventStore.count >= self.config.eventCapacity { Log.debug(self.typeName(and: #function) + "aborted. Event store is full") return } self.eventStore.append(event.dictionaryValue(config: self.config)) } } - + func recordFlagEvaluationEvents(flagKey: LDFlagKey, value: Any?, defaultValue: Any?, featureFlag: FeatureFlag?, user: LDUser, includeReason: Bool) { recordFlagEvaluationEvents(flagKey: flagKey, value: value, defaultValue: defaultValue, featureFlag: featureFlag, user: user, includeReason: includeReason, completion: nil) } @@ -157,17 +153,13 @@ class EventReporter: EventReporting { private func startReporting() { guard isOnline && !isReportingActive - else { - return - } + else { return } eventReportTimer = LDTimer(withTimeInterval: config.eventFlushInterval, repeats: true, fireQueue: eventQueue, execute: reportEvents) } private func stopReporting() { guard isReportingActive - else { - return - } + else { return } eventReportTimer?.cancel() eventReportTimer = nil } @@ -218,7 +210,7 @@ class EventReporter: EventReporting { } } } - + private func processEventResponse(reportedEventDictionaries: [[String: Any]], serviceResponse: ServiceResponse, attempt: Int) { if let serviceResponseError = serviceResponse.error { Log.debug(typeName(and: #function) + "error: \(String(describing: serviceResponseError))") @@ -240,46 +232,31 @@ class EventReporter: EventReporting { Log.debug(typeName(and: #function) + " completed for keys: " + reportedEventDictionaries.eventKeys) reportSyncComplete(.success(reportedEventDictionaries)) } - + private func reportSyncComplete(_ result: EventSyncResult) { //The eventReporter is created when the LDClient singleton is created, and kept for the app's lifetime. So while the use of self in the async block does setup a retain cycle, it's not going to cause a memory leak guard let onSyncComplete = onSyncComplete - else { - return - } + else { return } DispatchQueue.main.async { onSyncComplete(result) } } - + private func updateEventStore(reportedEventDictionaries: [[String: Any]]) { //The eventReporter is created when the LDClient singleton is created, and kept for the app's lifetime. So while the use of self in the async block does setup a retain cycle, it's not going to cause a memory leak eventQueue.async { - let remainingEventDictionaries = self.eventStore.filter { (eventDictionary) in + let remainingEventDictionaries = self.eventStore.filter { eventDictionary in !reportedEventDictionaries.contains(eventDictionary) } self.eventStore = remainingEventDictionaries } } - - private var isEventStoreFull: Bool { - return eventStore.count >= config.eventCapacity - } } extension EventReporter: TypeIdentifying { } extension Array where Element == [String: Any] { - var eventKeys: String { - let keys = compactMap { (eventDictionary) in - eventDictionary.eventKey - } - guard !keys.isEmpty - else { - return "" - } - return keys.joined(separator: ", ") - } + var eventKeys: String { compactMap { $0.eventKey }.joined(separator: ", ") } } #if DEBUG @@ -298,9 +275,7 @@ extension Array where Element == [String: Any] { } } - var testOnSyncComplete: EventSyncCompleteClosure? { - return onSyncComplete - } + var testOnSyncComplete: EventSyncCompleteClosure? { onSyncComplete } func add(_ events: [Event]) { eventStore.append(contentsOf: events.dictionaryValues(config: config)) diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/FlagChangeNotifier.swift b/LaunchDarkly/LaunchDarkly/Service Objects/FlagChangeNotifier.swift index 3c107209..3a617342 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/FlagChangeNotifier.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/FlagChangeNotifier.swift @@ -2,7 +2,6 @@ // FlagChangeNotifier.swift // LaunchDarkly // -// Created by Mark Pokorny on 8/18/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -26,17 +25,17 @@ final class FlagChangeNotifier: FlagChangeNotifying { private var flagChangeObservers = [FlagChangeObserver]() private var flagsUnchangedObservers = [FlagsUnchangedObserver]() private var connectionModeChangedObservers = [ConnectionModeChangedObserver]() - + func addFlagChangeObserver(_ observer: FlagChangeObserver) { Log.debug(typeName(and: #function) + "observer: \(observer)") flagChangeObservers.append(observer) } - + func addFlagsUnchangedObserver(_ observer: FlagsUnchangedObserver) { Log.debug(typeName(and: #function) + "observer: \(observer)") flagsUnchangedObservers.append(observer) } - + func addConnectionModeChangedObserver(_ observer: ConnectionModeChangedObserver) { Log.debug(typeName(and: #function) + "observer: \(observer)") connectionModeChangedObservers.append(observer) @@ -47,31 +46,25 @@ final class FlagChangeNotifier: FlagChangeNotifying { Log.debug(typeName(and: #function) + "key: \(key), owner: \(owner)") removeObserver([key], owner: owner) } - + ///Removes any change handling closures for flag keys from owner func removeObserver(_ keys: [LDFlagKey], owner: LDObserverOwner) { Log.debug(typeName(and: #function) + "keys: \(keys), owner: \(owner)") - flagChangeObservers = flagChangeObservers.filter { (observer) in + flagChangeObservers = flagChangeObservers.filter { observer in !(observer.flagKeys == keys && observer.owner === owner) } } - + ///Removes all change handling closures from owner func removeObserver(owner: LDObserverOwner) { Log.debug(typeName(and: #function) + "owner: \(owner)") - flagChangeObservers = flagChangeObservers.filter { (observer) in - observer.owner !== owner - } - flagsUnchangedObservers = flagsUnchangedObservers.filter { (observer) in - observer.owner !== owner - } - connectionModeChangedObservers = connectionModeChangedObservers.filter { (observer) in - observer.owner !== owner - } + flagChangeObservers = flagChangeObservers.filter { $0.owner !== owner } + flagsUnchangedObservers = flagsUnchangedObservers.filter { $0.owner !== owner } + connectionModeChangedObservers = connectionModeChangedObservers.filter { $0.owner !== owner } } - + func notifyConnectionModeChangedObservers(connectionMode: ConnectionInformation.ConnectionMode) { - connectionModeChangedObservers.forEach { (connectionModeChangedObserver) in + connectionModeChangedObservers.forEach { connectionModeChangedObserver in if let connectionModeChangedHandler = connectionModeChangedObserver.connectionModeChangedHandler { DispatchQueue.main.async { connectionModeChangedHandler(connectionMode) @@ -79,7 +72,7 @@ final class FlagChangeNotifier: FlagChangeNotifying { } } } - + func notifyObservers(user: LDUser, oldFlags: [LDFlagKey: FeatureFlag], oldFlagSource: LDFlagValueSource) { removeOldObservers() @@ -93,7 +86,7 @@ final class FlagChangeNotifier: FlagChangeNotifying { logMessage = "notifying observers that flags are unchanged." } Log.debug(typeName(and: #function) + logMessage) - flagsUnchangedObservers.forEach { (flagsUnchangedObserver) in + flagsUnchangedObservers.forEach { flagsUnchangedObserver in if let flagsUnchangedHandler = flagsUnchangedObserver.flagsUnchangedHandler { DispatchQueue.main.async { flagsUnchangedHandler() @@ -111,15 +104,15 @@ final class FlagChangeNotifier: FlagChangeNotifying { } let changedFlags = [LDFlagKey: LDChangedFlag](uniqueKeysWithValues: changedFlagKeys.map { (flagKey) in - return (flagKey, LDChangedFlag(key: flagKey, - oldValue: oldFlags[flagKey]?.value, - oldValueSource: oldFlagSource, - newValue: user.flagStore.featureFlags[flagKey]?.value, - newValueSource: user.flagStore.flagValueSource)) + (flagKey, LDChangedFlag(key: flagKey, + oldValue: oldFlags[flagKey]?.value, + oldValueSource: oldFlagSource, + newValue: user.flagStore.featureFlags[flagKey]?.value, + newValueSource: user.flagStore.flagValueSource)) }) Log.debug(typeName(and: #function) + "notifying observers for changes to flags: \(changedFlags.keys.joined(separator: ", ")).") - selectedObservers.forEach { (observer) in - let filteredChangedFlags = changedFlags.filter { (flagKey, _) -> Bool in + selectedObservers.forEach { observer in + let filteredChangedFlags = changedFlags.filter { flagKey, _ -> Bool in observer.flagKeys == LDFlagKey.anyKey || observer.flagKeys.contains(flagKey) } if let changeHandler = observer.flagCollectionChangeHandler { @@ -128,7 +121,7 @@ final class FlagChangeNotifier: FlagChangeNotifying { } return } - filteredChangedFlags.forEach({ (_, changedFlag) in + filteredChangedFlags.forEach({ _, changedFlag in if let changeHandler = observer.flagChangeHandler { DispatchQueue.main.async { changeHandler(changedFlag) @@ -140,19 +133,13 @@ final class FlagChangeNotifier: FlagChangeNotifying { private func removeOldObservers() { Log.debug(typeName(and: #function)) - let newFlagChangeObservers = flagChangeObservers.filter { (observer) in - observer.owner != nil - } - flagChangeObservers = newFlagChangeObservers - let newFlagsUnchangedObservers = flagsUnchangedObservers.filter { (observer) in - observer.owner != nil - } - flagsUnchangedObservers = newFlagsUnchangedObservers + flagChangeObservers = flagChangeObservers.filter { $0.owner != nil } + flagsUnchangedObservers = flagsUnchangedObservers.filter { $0.owner != nil } } private func findChangedFlagKeys(oldFlags: [LDFlagKey: FeatureFlag], newFlags: [LDFlagKey: FeatureFlag]) -> [LDFlagKey] { - return oldFlags.symmetricDifference(newFlags) //symmetricDifference tests for equality, which includes version. Exclude version here. - .filter { (flagKey) in + oldFlags.symmetricDifference(newFlags) //symmetricDifference tests for equality, which includes version. Exclude version here. + .filter { flagKey in guard let oldFeatureFlag = oldFlags[flagKey], let newFeatureFlag = newFlags[flagKey] else { @@ -165,13 +152,13 @@ final class FlagChangeNotifier: FlagChangeNotifying { extension Array where Element == LDFlagKey { func containsAny(_ other: [LDFlagKey]) -> Bool { - return !Set(self).isDisjoint(with: Set(other)) + !Set(self).isDisjoint(with: Set(other)) } } extension Array where Element == FlagChangeObserver { func watching(_ flagKeys: [LDFlagKey]) -> [FlagChangeObserver] { - return filter { (observer) in + filter { observer in observer.flagKeys == LDFlagKey.anyKey || observer.flagKeys.containsAny(flagKeys) } } @@ -181,12 +168,8 @@ extension FlagChangeNotifier: TypeIdentifying { } //Test support #if DEBUG extension FlagChangeNotifier { - var flagObservers: [FlagChangeObserver] { - return flagChangeObservers - } - var noChangeObservers: [FlagsUnchangedObserver] { - return flagsUnchangedObservers - } + var flagObservers: [FlagChangeObserver] { flagChangeObservers } + var noChangeObservers: [FlagsUnchangedObserver] { flagsUnchangedObservers } convenience init(flagChangeObservers: [FlagChangeObserver], flagsUnchangedObservers: [FlagsUnchangedObserver]) { self.init() diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/FlagStore.swift b/LaunchDarkly/LaunchDarkly/Service Objects/FlagStore.swift index 155ee236..8bd6e132 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/FlagStore.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/FlagStore.swift @@ -2,7 +2,6 @@ // FlagStore.swift // LaunchDarkly // -// Created by Mark Pokorny on 9/20/17. JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -32,21 +31,25 @@ final class FlagStore: FlagMaintaining { struct Constants { fileprivate static let flagQueueLabel = "com.launchdarkly.flagStore.flagQueue" } - + struct Keys { static let flagKey = "key" } - private(set) var featureFlags: [LDFlagKey: FeatureFlag] = [:] - private(set) var flagValueSource = LDFlagValueSource.fallback - private var flagQueue = DispatchQueue(label: Constants.flagQueueLabel) + var featureFlags: [LDFlagKey: FeatureFlag] { flagQueue.sync { _featureFlags } } + var flagValueSource: LDFlagValueSource { flagQueue.sync { _flagValueSource } } + + private var _featureFlags: [LDFlagKey: FeatureFlag] = [:] + private var _flagValueSource = LDFlagValueSource.fallback + // Used with .barrier as reader writer lock on _featureFlags, _flagValueSource + private var flagQueue = DispatchQueue(label: Constants.flagQueueLabel, attributes: .concurrent) init() { } init(featureFlags: [LDFlagKey: FeatureFlag]?, flagValueSource: LDFlagValueSource = .fallback) { Log.debug(typeName(and: #function) + "featureFlags: \(String(describing: featureFlags)), " + "flagValueSource: \(flagValueSource)") - self.featureFlags = featureFlags ?? [:] - self.flagValueSource = flagValueSource + self._featureFlags = featureFlags ?? [:] + self._flagValueSource = flagValueSource } convenience init(featureFlagDictionary: [LDFlagKey: Any]?, flagValueSource: LDFlagValueSource = .fallback) { @@ -56,9 +59,9 @@ final class FlagStore: FlagMaintaining { ///Replaces all feature flags with new flags. Pass nil to reset to an empty flag store func replaceStore(newFlags: [LDFlagKey: Any]?, source: LDFlagValueSource, completion: CompletionClosure?) { Log.debug(typeName(and: #function) + "newFlags: \(String(describing: newFlags)), " + "source: \(source)") - flagQueue.async { - self.featureFlags = newFlags?.flagCollection ?? [:] - self.flagValueSource = source + flagQueue.async(flags: .barrier) { + self._featureFlags = newFlags?.flagCollection ?? [:] + self._flagValueSource = source if let completion = completion { DispatchQueue.main.async { completion() @@ -78,7 +81,7 @@ final class FlagStore: FlagMaintaining { } */ func updateStore(updateDictionary: [String: Any], source: LDFlagValueSource, completion: CompletionClosure?) { - flagQueue.async { + flagQueue.async(flags: .barrier) { defer { if let completion = completion { DispatchQueue.main.async { @@ -87,29 +90,23 @@ final class FlagStore: FlagMaintaining { } } guard let flagKey = updateDictionary[Keys.flagKey] as? String, - let newFlag = FeatureFlag(dictionary: updateDictionary) + let newFlag = FeatureFlag(dictionary: updateDictionary) else { Log.debug(self.typeName(and: #function) + "aborted. Malformed update dictionary. updateDictionary: \(String(describing: updateDictionary))") return } - //Use only the version for comparison, ignore the flagVersion. The flagVersion is only used for event reporting. guard self.isValidVersion(for: flagKey, newVersion: newFlag.version) else { Log.debug(self.typeName(and: #function) + "aborted. Invalid version. updateDictionary: \(String(describing: updateDictionary)) " - + "existing flag: \(String(describing: self.featureFlags[flagKey]))") + + "existing flag: \(String(describing: self._featureFlags[flagKey]))") return } - Log.debug(self.typeName(and: #function) + "succeeded. new flag: \(newFlag), " + "prior flag: \(String(describing: self.featureFlags[flagKey]))") - var localFeatureFlags: [LDFlagKey: FeatureFlag] = [:] - self.featureFlags.forEach { key, value in - localFeatureFlags[key] = value - } - localFeatureFlags[flagKey] = newFlag - self.featureFlags = localFeatureFlags + Log.debug(self.typeName(and: #function) + "succeeded. new flag: \(newFlag), " + "prior flag: \(String(describing: self._featureFlags[flagKey]))") + self._featureFlags.updateValue(newFlag, forKey: flagKey) } } - + /* deleteDictionary should have the form: { "key": , @@ -117,7 +114,7 @@ final class FlagStore: FlagMaintaining { } */ func deleteFlag(deleteDictionary: [String: Any], completion: CompletionClosure?) { - flagQueue.async { + flagQueue.async(flags: .barrier) { defer { if let completion = completion { DispatchQueue.main.async { @@ -126,70 +123,60 @@ final class FlagStore: FlagMaintaining { } } guard deleteDictionary.keys.sorted() == [Keys.flagKey, FeatureFlag.CodingKeys.version.rawValue], - let flagKey = deleteDictionary[Keys.flagKey] as? String, - let newVersion = deleteDictionary[FeatureFlag.CodingKeys.version.rawValue] as? Int + let flagKey = deleteDictionary[Keys.flagKey] as? String, + let newVersion = deleteDictionary[FeatureFlag.CodingKeys.version.rawValue] as? Int else { Log.debug(self.typeName(and: #function) + "aborted. Malformed delete dictionary. deleteDictionary: \(String(describing: deleteDictionary))") return } - //Use only the version for comparison, ignore the flagVersion. The flagVersion is only used for event reporting. guard self.isValidVersion(for: flagKey, newVersion: newVersion) else { Log.debug(self.typeName(and: #function) + "aborted. Invalid version. deleteDictionary: \(String(describing: deleteDictionary)) " - + "existing flag: \(String(describing: self.featureFlags[flagKey]))") + + "existing flag: \(String(describing: self._featureFlags[flagKey]))") return } Log.debug(self.typeName(and: #function) + "deleted flag with key: " + flagKey) - var localFeatureFlags: [LDFlagKey: FeatureFlag] = [:] - self.featureFlags.forEach { key, value in - localFeatureFlags[key] = value - } - localFeatureFlags.removeValue(forKey: flagKey) - self.featureFlags = localFeatureFlags + self._featureFlags.removeValue(forKey: flagKey) } } private func isValidVersion(for flagKey: LDFlagKey, newVersion: Int?) -> Bool { - //Use only the version, here called "environmentVersion" for comparison, ignore the flagVersion. The flagVersion is only used for event reporting. - guard let environmentVersion = featureFlags[flagKey]?.version, - let newEnvironmentVersion = newVersion - else { - return true //new flags ignore version, ignore missing version too + // Currently only called from within barrier, only call on flagQueue + // Use only the version, here called "environmentVersion" for comparison. The flagVersion is only used for event reporting. + if let environmentVersion = _featureFlags[flagKey]?.version, + let newEnvironmentVersion = newVersion { + return newEnvironmentVersion > environmentVersion } - return newEnvironmentVersion > environmentVersion + // Always update if either environment version is missing, or updating non-existent flag + return true } func featureFlag(for flagKey: LDFlagKey) -> FeatureFlag? { - let (featureFlag, _) = featureFlagAndSource(for: flagKey) - return featureFlag + featureFlagAndSource(for: flagKey).0 } func featureFlagAndSource(for flagKey: LDFlagKey) -> (FeatureFlag?, LDFlagValueSource?) { - let featureFlag = featureFlags[flagKey] - return (featureFlag, featureFlag == nil ? nil : flagValueSource) + flagQueue.sync { + let featureFlag = _featureFlags[flagKey] + return (featureFlag, featureFlag == nil ? nil : _flagValueSource) + } } func variation(forKey key: LDFlagKey, fallback: T) -> T { - let (flagValue, _) = variationAndSource(forKey: key, fallback: fallback) - return flagValue + variationAndSource(forKey: key, fallback: fallback).0 } func variationAndSource(forKey key: LDFlagKey, fallback: T) -> (T, LDFlagValueSource) { - var (flagValue, source) = (fallback, LDFlagValueSource.fallback) - if let foundValue = featureFlags[key]?.value as? T { - (flagValue, source) = (foundValue, flagValueSource) + flagQueue.sync { + let foundValue = _featureFlags[key]?.value as? T + return (foundValue ?? fallback, foundValue == nil ? .fallback : _flagValueSource) } - return (flagValue, source) } } extension FlagStore: TypeIdentifying { } extension Dictionary where Key == LDFlagKey, Value == FeatureFlag { - var allFlagValues: [LDFlagKey: Any] { - return compactMapValues { (featureFlag) in - return featureFlag.value - } - } + var allFlagValues: [LDFlagKey: Any] { compactMapValues { $0.value } } } diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/FlagSynchronizer.swift b/LaunchDarkly/LaunchDarkly/Service Objects/FlagSynchronizer.swift index e9f4d6f4..520f628e 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/FlagSynchronizer.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/FlagSynchronizer.swift @@ -2,7 +2,6 @@ // FlagSynchronizer.swift // LaunchDarkly // -// Created by Mark Pokorny on 7/24/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -31,9 +30,7 @@ enum SynchronizingError: Error { switch self { case .response(let urlResponse): guard let httpResponse = urlResponse as? HTTPURLResponse - else { - return false - } + else { return false } return httpResponse.statusCode == HTTPURLResponse.StatusCodes.unauthorized case .event(let event): return event?.isUnauthorized ?? false @@ -56,7 +53,7 @@ extension DarklyEventSource.LDEvent { } var eventType: EventType? { - return EventType(rawValue: event ?? "") + EventType(rawValue: event ?? "") } var isUnauthorized: Bool { @@ -77,7 +74,7 @@ class FlagSynchronizer: LDFlagSynchronizing { var onSyncComplete: FlagSyncCompleteClosure? let streamingMode: LDStreamingMode - + var isOnline: Bool = false { didSet { Log.debug(typeName(and: #function, appending: ": ") + "\(isOnline)") @@ -88,12 +85,8 @@ class FlagSynchronizer: LDFlagSynchronizing { let pollingInterval: TimeInterval let useReport: Bool - var streamingActive: Bool { - return eventSource != nil - } - var pollingActive: Bool { - return flagRequestTimer != nil - } + var streamingActive: Bool { eventSource != nil } + var pollingActive: Bool { flagRequestTimer != nil } private var syncQueue = DispatchQueue(label: Constants.queueName, qos: .utility) init(streamingMode: LDStreamingMode, pollingInterval: TimeInterval, useReport: Bool, service: DarklyServiceProvider, onSyncComplete: FlagSyncCompleteClosure?) { @@ -106,7 +99,7 @@ class FlagSynchronizer: LDFlagSynchronizing { configureCommunications() } - + private func configureCommunications() { if isOnline { switch streamingMode { @@ -122,9 +115,9 @@ class FlagSynchronizer: LDFlagSynchronizing { stopPolling() } } - + // MARK: Streaming - + private func startEventSource() { guard isOnline, streamingMode == .streaming, @@ -143,12 +136,12 @@ class FlagSynchronizer: LDFlagSynchronizing { Log.debug(typeName(and: #function) + "aborted. " + reason) return } - + Log.debug(typeName(and: #function)) eventSource = service.createEventSource(useReport: useReport) //The LDConfig.connectionTimeout should NOT be set here. Heartbeat is sent every 3m. ES default timeout is 5m. This is an async operation. //LDEventSource reacts to connection errors by closing the connection and establishing a new one after an exponentially increasing wait. That makes it self healing. //While we could keep the LDEventSource state, there's not much we can do to help it connect. If it can't connect, it's likely we won't be able to poll the server either...so it seems best to just do nothing and let it heal itself. - eventSource?.onReadyStateChangedEvent { [self] (event) in + eventSource?.onReadyStateChangedEvent { [self] event in guard let event = event else { Log.debug(self.typeName(and: #function) + "onReadyStateChangedEvent handler aborted. No streaming event.") @@ -159,15 +152,11 @@ class FlagSynchronizer: LDFlagSynchronizing { NotificationCenter.default.post(name: Notification.Name(FlagSynchronizer.Constants.didCloseEventSourceName), object: nil) } } - eventSource?.onMessageEvent { [weak self] (event) in - self?.process(event) - } - eventSource?.onErrorEvent { [weak self] (event) in - self?.process(event) - } + eventSource?.onMessageEvent { [weak self] in self?.process($0) } + eventSource?.onErrorEvent { [weak self] in self?.process($0) } eventSource?.open() } - + private func stopEventSource() { guard streamingActive else { Log.debug(typeName(and: #function) + "aborted. Clientstream is not connected.") @@ -177,7 +166,7 @@ class FlagSynchronizer: LDFlagSynchronizing { eventSource?.close() //This is an async operation. eventSource = nil } - + private func process(_ event: DarklyEventSource.LDEvent?) { //Because this method is called asynchronously by the LDEventSource, need to check these conditions prior to processing the event. if !isOnline { @@ -231,9 +220,9 @@ class FlagSynchronizer: LDFlagSynchronizing { } reportSuccess(flagDictionary: flagDictionary, eventType: eventType) } - + // MARK: Polling - + private func startPolling() { guard isOnline, streamingMode == .polling, @@ -254,7 +243,7 @@ class FlagSynchronizer: LDFlagSynchronizing { flagRequestTimer = LDTimer(withTimeInterval: pollingInterval, repeats: true, fireQueue: syncQueue, execute: processTimer) makeFlagRequest() } - + private func stopPolling() { guard pollingActive else { Log.debug(typeName(and: #function) + "aborted. Polling already inactive.") @@ -264,13 +253,13 @@ class FlagSynchronizer: LDFlagSynchronizing { flagRequestTimer?.cancel() flagRequestTimer = nil } - + @objc private func processTimer() { makeFlagRequest() } - + // MARK: Flag Request - + private func makeFlagRequest() { guard isOnline else { @@ -281,10 +270,10 @@ class FlagSynchronizer: LDFlagSynchronizing { Log.debug(typeName(and: #function, appending: " - ") + "starting") let context = (useReport: useReport, logPrefix: typeName(and: #function, appending: " - ")) - service.getFeatureFlags(useReport: useReport, completion: { [weak self] (serviceResponse) in + service.getFeatureFlags(useReport: useReport, completion: { [weak self] serviceResponse in if FlagSynchronizer.shouldRetryFlagRequest(useReport: context.useReport, statusCode: (serviceResponse.urlResponse as? HTTPURLResponse)?.statusCode) { Log.debug(context.logPrefix + "retrying via GET") - self?.service.getFeatureFlags(useReport: false, completion: { (retryServiceResponse) in + self?.service.getFeatureFlags(useReport: false, completion: { retryServiceResponse in self?.processFlagResponse(serviceResponse: retryServiceResponse) }) } else { @@ -327,7 +316,7 @@ class FlagSynchronizer: LDFlagSynchronizing { Log.debug(typeName(and: #function) + "flagDictionary: \(flagDictionary)" + (eventType == nil ? "" : ", eventType: \(String(describing: eventType))")) reportSyncComplete(.success(flagDictionary, streamingActive ? eventType : nil)) } - + private func reportDataError(_ data: Data?) { Log.debug(typeName(and: #function) + "data: \(String(describing: data))") reportSyncComplete(.error(.data(data))) @@ -335,9 +324,7 @@ class FlagSynchronizer: LDFlagSynchronizing { private func reportSyncComplete(_ result: FlagSyncResult) { guard let onSyncComplete = onSyncComplete - else { - return - } + else { return } DispatchQueue.main.async { onSyncComplete(result) } @@ -356,12 +343,8 @@ extension FlagSynchronizer: TypeIdentifying { } #if DEBUG extension FlagSynchronizer { var testEventSource: DarklyStreamingProvider? { - set { - eventSource = newValue - } - get { - return eventSource - } + get { eventSource } + set { eventSource = newValue } } func testMakeFlagRequest() { diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/LDTimer.swift b/LaunchDarkly/LaunchDarkly/Service Objects/LDTimer.swift index ef774d47..b88268e2 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/LDTimer.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/LDTimer.swift @@ -2,7 +2,6 @@ // LDTimer.swift // LaunchDarkly // -// Created by Mark Pokorny on 1/17/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -18,14 +17,12 @@ protocol TimeResponding { final class LDTimer: TimeResponding { - weak private (set) var timer: Timer? + private (set) weak var timer: Timer? private let fireQueue: DispatchQueue private let execute: () -> Void private (set) var isRepeating: Bool private (set) var isCancelled: Bool = false - var fireDate: Date? { - return timer?.fireDate - } + var fireDate: Date? { timer?.fireDate } init(withTimeInterval timeInterval: TimeInterval, repeats: Bool, fireQueue: DispatchQueue = DispatchQueue.main, execute: @escaping () -> Void) { isRepeating = repeats @@ -45,9 +42,7 @@ final class LDTimer: TimeResponding { @objc private func timerFired() { fireQueue.async { [weak self] in guard (self?.isCancelled ?? true) == false - else { - return - } + else { return } self?.execute() } } @@ -60,8 +55,6 @@ final class LDTimer: TimeResponding { #if DEBUG extension LDTimer { - var testFireQueue: DispatchQueue { - return fireQueue - } + var testFireQueue: DispatchQueue { fireQueue } } #endif diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/Log.swift b/LaunchDarkly/LaunchDarkly/Service Objects/Log.swift index 8d556ee2..260de35e 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/Log.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/Log.swift @@ -2,7 +2,6 @@ // Log.swift // LaunchDarkly // -// Created by Mark Pokorny on 3/14/18. +JMJ // Copyright © 2018 Catamorphic Co. All rights reserved. // @@ -47,15 +46,15 @@ protocol TypeIdentifying { } extension TypeIdentifying { var typeName: String { - return String(describing: type(of: self)) + String(describing: type(of: self)) } func typeName(and method: String, appending suffix: String = " ") -> String { - return typeName + "." + method + suffix + typeName + "." + method + suffix } static var typeName: String { - return String(describing: self) + String(describing: self) } static func typeName(and method: String, appending suffix: String = " ") -> String { - return typeName + "." + method + suffix + typeName + "." + method + suffix } } diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/NetworkReporter.swift b/LaunchDarkly/LaunchDarkly/Service Objects/NetworkReporter.swift index 9f2e3f6d..0b4613bf 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/NetworkReporter.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/NetworkReporter.swift @@ -2,7 +2,6 @@ // File.swift // LaunchDarkly // -// Created by Joe Cieslik on 8/27/19. // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -18,27 +17,25 @@ class NetworkReporter { var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress)) zeroAddress.sin_family = sa_family_t(AF_INET) - + let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress) } } - + var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0) if SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) == false { return false } - + let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0 let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0 let reachability = (isReachable && !needsConnection) - + return reachability } #else - static func isConnectedToNetwork() -> Bool { - return true - } + static func isConnectedToNetwork() -> Bool { true } #endif } diff --git a/LaunchDarkly/LaunchDarkly/Service Objects/Throttler.swift b/LaunchDarkly/LaunchDarkly/Service Objects/Throttler.swift index f26aed09..dded0707 100644 --- a/LaunchDarkly/LaunchDarkly/Service Objects/Throttler.swift +++ b/LaunchDarkly/LaunchDarkly/Service Objects/Throttler.swift @@ -2,7 +2,6 @@ // Throttler.swift // LaunchDarkly // -// Created by Mark Pokorny on 5/14/18. +JMJ // Copyright © 2018 Catamorphic Co. All rights reserved. // @@ -26,13 +25,13 @@ final class Throttler: Throttling { } class func maxAttempts(forDelay delayInterval: TimeInterval) -> Int { - return Int(ceil(log2(delayInterval))) + Int(ceil(log2(delayInterval))) } let throttlingEnabled: Bool private (set) var maxDelay: TimeInterval var maxAttempts: Int { - return Throttler.maxAttempts(forDelay: maxDelay) + Throttler.maxAttempts(forDelay: maxDelay) } private (set) var runAttempts: Int private (set) var delay: TimeInterval @@ -94,23 +93,17 @@ final class Throttler: Throttling { private func delayForAttempt(_ attempt: Int) -> TimeInterval { guard throttlingEnabled - else { - return 0.0 - } + else { return 0.0 } guard Double(attempt) <= log2(maxDelay) - else { - return maxDelay - } + else { return maxDelay } let exponentialBackoff = min(maxDelay, pow(2, attempt).timeInterval) //pow(x, y) returns x^y let jitterBackoff = Double(arc4random_uniform(UInt32(exponentialBackoff))) // arc4random_uniform(upperBound) returns an Int uniformly randomized between 0.. TimeResponding? { guard throttlingEnabled - else { - return nil - } + else { return nil } timerStart = timerStart ?? Date() let timer = LDTimer(withTimeInterval: delay, repeats: false, fireQueue: runQueue, execute: timerFired) return timer @@ -136,25 +129,19 @@ extension Throttler: TypeIdentifying { } extension Decimal { var timeInterval: TimeInterval { - return NSDecimalNumber(decimal: self).doubleValue + NSDecimalNumber(decimal: self).doubleValue } } #if DEBUG extension Throttler { - var runClosureForTesting: RunClosure? { - return runClosure - } + var runClosureForTesting: RunClosure? { runClosure } var timerFiredCallback: RunClosure? { - set { - runPostTimer = newValue - } - get { - return runPostTimer - } + get { runPostTimer } + set { runPostTimer = newValue } } func test_delayForAttempt(_ attempt: Int) -> TimeInterval { - return delayForAttempt(attempt) + delayForAttempt(attempt) } } #endif diff --git a/LaunchDarkly/LaunchDarkly/Support/LaunchDarkly.h b/LaunchDarkly/LaunchDarkly/Support/LaunchDarkly.h index 1958669e..3e607d20 100644 --- a/LaunchDarkly/LaunchDarkly/Support/LaunchDarkly.h +++ b/LaunchDarkly/LaunchDarkly/Support/LaunchDarkly.h @@ -2,7 +2,6 @@ // LaunchDarkly.h // LaunchDarkly // -// Created by Mark Pokorny on 7/21/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // diff --git a/LaunchDarkly/LaunchDarklyTests/Extensions/ThreadSpec.swift b/LaunchDarkly/LaunchDarklyTests/Extensions/ThreadSpec.swift index 1309fdc2..1e5ebebb 100644 --- a/LaunchDarkly/LaunchDarklyTests/Extensions/ThreadSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Extensions/ThreadSpec.swift @@ -2,7 +2,6 @@ // ThreadSpec.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 3/5/19. // Copyright © 2019 Catamorphic Co. All rights reserved. // diff --git a/LaunchDarkly/LaunchDarklyTests/Extensions/TimeIntervalSpec.swift b/LaunchDarkly/LaunchDarklyTests/Extensions/TimeIntervalSpec.swift index 6eb11f31..9b868318 100644 --- a/LaunchDarkly/LaunchDarklyTests/Extensions/TimeIntervalSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Extensions/TimeIntervalSpec.swift @@ -2,7 +2,6 @@ // TimeIntervalSpec.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 3/27/19. // Copyright © 2019 Catamorphic Co. All rights reserved. // diff --git a/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift b/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift index 34688ac6..16c5077a 100644 --- a/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift @@ -461,7 +461,7 @@ final class LDClientSpec: QuickSpec { expect(testContext.recordedEvent?.key) == newUser.key } it("converts cached data") { - expect(testContext.cacheConvertingMock.convertCacheDataCallCount) == 2 + expect(testContext.cacheConvertingMock.convertCacheDataCallCount) == 1 //only called once because CacheConverter is replaced during start expect(testContext.cacheConvertingMock.convertCacheDataReceivedArguments?.user) == newUser expect(testContext.cacheConvertingMock.convertCacheDataReceivedArguments?.config) == newConfig } @@ -514,7 +514,7 @@ final class LDClientSpec: QuickSpec { expect(testContext.recordedEvent?.key) == newUser.key } it("converts cached data") { - expect(testContext.cacheConvertingMock.convertCacheDataCallCount) == 2 + expect(testContext.cacheConvertingMock.convertCacheDataCallCount) == 1 //only called once because CacheConverter is replaced during start expect(testContext.cacheConvertingMock.convertCacheDataReceivedArguments?.user) == newUser expect(testContext.cacheConvertingMock.convertCacheDataReceivedArguments?.config) == newConfig } @@ -558,7 +558,7 @@ final class LDClientSpec: QuickSpec { expect(testContext.recordedEvent?.key) == testContext.user.key } it("converts cached data") { - expect(testContext.cacheConvertingMock.convertCacheDataCallCount) == 2 + expect(testContext.cacheConvertingMock.convertCacheDataCallCount) == 1 //only called once because CacheConverter is replaced during start expect(testContext.cacheConvertingMock.convertCacheDataReceivedArguments?.user) == testContext.user expect(testContext.cacheConvertingMock.convertCacheDataReceivedArguments?.config) == testContext.config } diff --git a/LaunchDarkly/LaunchDarklyTests/Matcher/Match.swift b/LaunchDarkly/LaunchDarklyTests/Matcher/Match.swift index fa17ec39..c0207c8f 100644 --- a/LaunchDarkly/LaunchDarklyTests/Matcher/Match.swift +++ b/LaunchDarkly/LaunchDarklyTests/Matcher/Match.swift @@ -2,7 +2,6 @@ // Match.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 10/4/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -25,7 +24,7 @@ public enum ToMatchResult { Return `.failed` with a failure reason when the validation fails. */ public func match() -> Predicate<() -> ToMatchResult> { - return Predicate.define { actualExpression in + Predicate.define { actualExpression in let optActual = try actualExpression.evaluate() guard let actual = optActual else { diff --git a/LaunchDarkly/LaunchDarklyTests/Mocks/ClientServiceMockFactory.swift b/LaunchDarkly/LaunchDarklyTests/Mocks/ClientServiceMockFactory.swift index 312cf61e..ebfa88db 100644 --- a/LaunchDarkly/LaunchDarklyTests/Mocks/ClientServiceMockFactory.swift +++ b/LaunchDarkly/LaunchDarklyTests/Mocks/ClientServiceMockFactory.swift @@ -16,12 +16,12 @@ final class ClientServiceMockFactory: ClientServiceCreating { var makeFeatureFlagCacheReturnValue = FeatureFlagCachingMock() var makeFeatureFlagCacheCallCount = 0 - func makeFeatureFlagCache() -> FeatureFlagCaching { + func makeFeatureFlagCache(maxCachedUsers: Int = 5) -> FeatureFlagCaching { makeFeatureFlagCacheCallCount += 1 return makeFeatureFlagCacheReturnValue } - func makeCacheConverter() -> CacheConverting { + func makeCacheConverter(maxCachedUsers: Int = 5) -> CacheConverting { return CacheConvertingMock() } diff --git a/LaunchDarkly/LaunchDarklyTests/Mocks/EnvironmentReportingMock.swift b/LaunchDarkly/LaunchDarklyTests/Mocks/EnvironmentReportingMock.swift index 34c486a3..ad168cd5 100644 --- a/LaunchDarkly/LaunchDarklyTests/Mocks/EnvironmentReportingMock.swift +++ b/LaunchDarkly/LaunchDarklyTests/Mocks/EnvironmentReportingMock.swift @@ -2,7 +2,6 @@ // EnvironmentReportingMock.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 3/27/18. +JMJ // Copyright © 2018 Catamorphic Co. All rights reserved. // diff --git a/LaunchDarkly/LaunchDarklyTests/Mocks/FlagChangeNotifyingMock.swift b/LaunchDarkly/LaunchDarklyTests/Mocks/FlagChangeNotifyingMock.swift index fcf5a160..28375064 100644 --- a/LaunchDarkly/LaunchDarklyTests/Mocks/FlagChangeNotifyingMock.swift +++ b/LaunchDarkly/LaunchDarklyTests/Mocks/FlagChangeNotifyingMock.swift @@ -2,7 +2,6 @@ // FlagChangeNotifyingMock.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 12/18/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // diff --git a/LaunchDarkly/LaunchDarklyTests/Mocks/FlagMaintainingMock.swift b/LaunchDarkly/LaunchDarklyTests/Mocks/FlagMaintainingMock.swift index 4e6881cb..6423e815 100644 --- a/LaunchDarkly/LaunchDarklyTests/Mocks/FlagMaintainingMock.swift +++ b/LaunchDarkly/LaunchDarklyTests/Mocks/FlagMaintainingMock.swift @@ -2,7 +2,6 @@ // FlagMaintainingMock.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 11/20/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -22,7 +21,7 @@ extension FlagMaintainingMock { } func featureFlag(for flagKey: LDFlagKey) -> FeatureFlag? { - return featureFlags[flagKey] + featureFlags[flagKey] } func featureFlagAndSource(for flagKey: LDFlagKey) -> (FeatureFlag?, LDFlagValueSource?) { @@ -31,7 +30,7 @@ extension FlagMaintainingMock { } func variation(forKey key: String, fallback: T) -> T { - return featureFlags[key]?.value as? T ?? fallback + featureFlags[key]?.value as? T ?? fallback } func variationAndSource(forKey key: String, fallback: T) -> (T, LDFlagValueSource) { diff --git a/LaunchDarkly/LaunchDarklyTests/Mocks/LDConfigStub.swift b/LaunchDarkly/LaunchDarklyTests/Mocks/LDConfigStub.swift index 884f2283..40887c01 100644 --- a/LaunchDarkly/LaunchDarklyTests/Mocks/LDConfigStub.swift +++ b/LaunchDarkly/LaunchDarklyTests/Mocks/LDConfigStub.swift @@ -2,7 +2,6 @@ // LDConfigStub.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 9/29/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -16,7 +15,7 @@ extension LDConfig { } static var stub: LDConfig { - return stub(mobileKey: Constants.mockMobileKey, environmentReporter: EnvironmentReportingMock()) + stub(mobileKey: Constants.mockMobileKey, environmentReporter: EnvironmentReportingMock()) } static func stub(mobileKey: String, environmentReporter: EnvironmentReportingMock) -> LDConfig { @@ -27,8 +26,6 @@ extension LDConfig { config.flagPollingInterval = 1.0 - config.enableBackgroundUpdates = true - return config } } diff --git a/LaunchDarkly/LaunchDarklyTests/Mocks/LDEventSourceMock.swift b/LaunchDarkly/LaunchDarklyTests/Mocks/LDEventSourceMock.swift index 40988a75..13171d61 100644 --- a/LaunchDarkly/LaunchDarklyTests/Mocks/LDEventSourceMock.swift +++ b/LaunchDarkly/LaunchDarklyTests/Mocks/LDEventSourceMock.swift @@ -2,7 +2,6 @@ // LDEventSourceMock.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 9/28/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -57,7 +56,7 @@ extension DarklyEventSource.LDEvent { event.readyState = kEventStateOpen return event } - + class func stubHeartbeatEvent() -> DarklyEventSource.LDEvent { let event = DarklyEventSource.LDEvent() event.event = EventType.heartbeat.rawValue diff --git a/LaunchDarkly/LaunchDarklyTests/Mocks/LDUserStub.swift b/LaunchDarkly/LaunchDarklyTests/Mocks/LDUserStub.swift index 842350d4..de9de550 100644 --- a/LaunchDarkly/LaunchDarklyTests/Mocks/LDUserStub.swift +++ b/LaunchDarkly/LaunchDarklyTests/Mocks/LDUserStub.swift @@ -2,7 +2,6 @@ // LDUserStub.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 10/19/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -74,10 +73,6 @@ extension LDUser { } static func stubUsers(_ count: Int) -> [LDUser] { - var userStubs = [LDUser]() - while userStubs.count < count { - userStubs.append(LDUser.stub()) - } - return userStubs + (0.. CacheableEnvironmentFlags? in - return CacheableEnvironmentFlags(object: cacheableEnvironmentFlagsDictionary) - }) + let environmentFlagsDictionary = self[CacheableUserEnvironmentFlags.CodingKeys.environmentFlags.rawValue] as? [MobileKey: Any] + return environmentFlagsDictionary?.compactMapValues({ CacheableEnvironmentFlags(object: $0) }) } } @@ -467,33 +461,25 @@ extension Int { static let userKeyPrefix = "User." static let mobileKeyPrefix = "MobileKey." - var userKey: String { - return "\(Int.userKeyPrefix)\(self)" - } - var mobileKey: String { - return "\(Int.mobileKeyPrefix)\(self)" - } + var userKey: String { "\(Int.userKeyPrefix)\(self)" } + var mobileKey: String { "\(Int.mobileKeyPrefix)\(self)" } } extension CacheableUserEnvironmentFlags { struct Constants { static let environmentCount = 3 - static let userCount = UserEnvironmentFlagCache.Constants.maxCachedUsers + static let userCount = LDConfig.Defaults.maxCachedUsers } static func stubCollection(environmentCount: Int = Constants.environmentCount, userCount: Int = Constants.userCount) -> (users: [LDUser], collection: [UserKey: CacheableUserEnvironmentFlags], mobileKeys: [MobileKey]) { var pastSeconds = 0.0 - var users = [LDUser]() - while users.count < userCount { - let user = LDUser.stub(key: (users.count + 1).userKey) - users.append(user) - } + let users = (0.. 0 { diff --git a/LaunchDarkly/LaunchDarklyTests/Models/ErrorObserverSpec.swift b/LaunchDarkly/LaunchDarklyTests/Models/ErrorObserverSpec.swift index 22b0104e..51358246 100644 --- a/LaunchDarkly/LaunchDarklyTests/Models/ErrorObserverSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Models/ErrorObserverSpec.swift @@ -2,7 +2,6 @@ // ErrorObserverSpec.swift // DarklyTests // -// Created by Mark Pokorny on 2/6/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -20,13 +19,7 @@ final class ErrorOwnerMock { extension ErrorObserver { static func createObservers(count: Int, using owner: ErrorOwnerMock = ErrorOwnerMock()) -> [ErrorObserver] { - var errorObservers = [ErrorObserver]() - let errorObserverOwner = owner - while errorObservers.count < count { - let errorObserver = ErrorObserver(owner: errorObserverOwner, errorHandler: errorObserverOwner.handle) - errorObservers.append(errorObserver) - } - return errorObservers + (0.. Void)] = + [("key", false, "dummy", { u, v in u.key = v as! String }), + ("name", true, "dummy", { u, v in u.name = v as! String? }), + ("firstName", true, "dummy", { u, v in u.firstName = v as! String? }), + ("lastName", true, "dummy", { u, v in u.lastName = v as! String? }), + ("country", true, "dummy", { u, v in u.country = v as! String? }), + ("ipAddress", true, "dummy", { u, v in u.ipAddress = v as! String? }), + ("email address", true, "dummy", { u, v in u.email = v as! String? }), + ("avatar", true, "dummy", { u, v in u.avatar = v as! String? }), + ("custom", true, ["dummy": true], { u, v in u.custom = v as! [String: Any]? }), + ("isAnonymous", false, true, { u, v in u.isAnonymous = v as! Bool }), + ("device", true, "dummy", { u, v in u.device = v as! String? }), + ("operatingSystem", true, "dummy", { u, v in u.operatingSystem = v as! String? }), + ("privateAttributes", false, ["dummy"], { u, v in u.privateAttributes = v as! [String]? })] + testFields.forEach { name, isOptional, otherVal, setter in + context("\(name) differs") { beforeEach { user = LDUser.stub() otherUser = user - otherUser.privateAttributes = ["dummy"] - } - it("returns false") { - expect(user.isEqual(to: otherUser)) == false } - } - context("other privateAttributes does not exist") { - beforeEach { - user = LDUser.stub() - otherUser = user - otherUser.privateAttributes = nil + context("and both exist") { + it("returns false") { + setter(&otherUser, otherVal) + expect(user.isEqual(to: otherUser)) == false + expect(otherUser.isEqual(to: user)) == false + } } - it("returns false") { - expect(user.isEqual(to: otherUser)) == false + if isOptional { + context("self \(name) nil") { + it("returns false") { + setter(&user, nil) + expect(user.isEqual(to: otherUser)) == false + } + } + context("other \(name) nil") { + it("returns false") { + setter(&otherUser, nil) + expect(user.isEqual(to: otherUser)) == false + } + } } } } @@ -1506,14 +901,14 @@ final class LDUserSpec: QuickSpec { extension LDUser { static var requiredAttributes: [String] { - return [CodingKeys.key.rawValue, CodingKeys.isAnonymous.rawValue] + [CodingKeys.key.rawValue, CodingKeys.isAnonymous.rawValue] } static var optionalAttributes: [String] { - return [CodingKeys.name.rawValue, CodingKeys.firstName.rawValue, CodingKeys.lastName.rawValue, CodingKeys.country.rawValue, CodingKeys.ipAddress.rawValue, CodingKeys.email.rawValue, - CodingKeys.avatar.rawValue] + [CodingKeys.name.rawValue, CodingKeys.firstName.rawValue, CodingKeys.lastName.rawValue, CodingKeys.country.rawValue, CodingKeys.ipAddress.rawValue, CodingKeys.email.rawValue, + CodingKeys.avatar.rawValue] } var customAttributes: [String]? { - return custom?.keys.filter { (key) in !LDUser.sdkSetAttributes.contains(key) } + custom?.keys.filter { key in !LDUser.sdkSetAttributes.contains(key) } } struct MatcherMessages { @@ -1524,115 +919,72 @@ extension LDUser { static let attributeListShouldContain = "private attributes list does not contain attribute " } - fileprivate func requiredAttributeKeyValuePairsMatch(userDictionary: [String: Any]) -> ToMatchResult { - var messages = [String]() - - LDUser.requiredAttributes.forEach { (attribute) in - if let message = messageIfMissingValue(in: userDictionary, for: attribute) { - messages.append(message) - } - } + private func failsToMatch(fails: [String]) -> ToMatchResult { + fails.isEmpty ? .matched : .failed(reason: fails.joined(separator: ", ")) + } - return messages.isEmpty ? .matched : .failed(reason: messages.joined(separator: ", ")) + fileprivate func requiredAttributeKeyValuePairsMatch(userDictionary: [String: Any]) -> ToMatchResult { + failsToMatch(fails: LDUser.requiredAttributes.compactMap { attribute in + messageIfMissingValue(in: userDictionary, for: attribute) + }) } fileprivate func optionalAttributePublicKeyValuePairsMatch(userDictionary: [String: Any], privateAttributes: [String]) -> ToMatchResult { - var messages = [String]() - - LDUser.optionalAttributes.forEach { (attribute) in - if !privateAttributes.contains(attribute) { - if let message = messageIfValueDoesntMatch(value: value(forAttribute: attribute), in: userDictionary, for: attribute) { - messages.append(message) - } - } - } - - return messages.isEmpty ? .matched : .failed(reason: messages.joined(separator: ", ")) + failsToMatch(fails: LDUser.optionalAttributes.compactMap { attribute in + privateAttributes.contains(attribute) ? nil : messageIfValueDoesntMatch(value: value(forAttribute: attribute), in: userDictionary, for: attribute) + }) } fileprivate func optionalAttributePrivateKeysDontExist(userDictionary: [String: Any], privateAttributes: [String]) -> ToMatchResult { - var messages = [String]() - - LDUser.optionalAttributes.forEach { (attribute) in - if privateAttributes.contains(attribute) { - if let message = messageIfAttributeExists(in: userDictionary, for: attribute) { - messages.append(message) - } - } - } - - return messages.isEmpty ? .matched : .failed(reason: messages.joined(separator: ", ")) + failsToMatch(fails: LDUser.optionalAttributes.compactMap { attribute in + !privateAttributes.contains(attribute) ? nil : messageIfAttributeExists(in: userDictionary, for: attribute) + }) } fileprivate func optionalAttributeMissingValueKeysDontExist(userDictionary: [String: Any]) -> ToMatchResult { - var messages = [String]() - - LDUser.optionalAttributes.forEach { (attribute) in - if value(forAttribute: attribute) == nil { - if let message = messageIfAttributeExists(in: userDictionary, for: attribute) { - messages.append(message) - } - } - } - - return messages.isEmpty ? .matched : .failed(reason: messages.joined(separator: ", ")) + failsToMatch(fails: LDUser.optionalAttributes.compactMap { attribute in + value(forAttribute: attribute) != nil ? nil : messageIfAttributeExists(in: userDictionary, for: attribute) + }) } fileprivate func optionalAttributePrivateKeysAppearInPrivateAttrsWhenRedacted(userDictionary: [String: Any], privateAttributes: [String]) -> ToMatchResult { - var messages = [String]() - let redactedAttributes = userDictionary.redactedAttributes - - LDUser.optionalAttributes.forEach { (attribute) in + let messages: [String] = LDUser.optionalAttributes.compactMap { attribute in if value(forAttribute: attribute) != nil && privateAttributes.contains(attribute) { - if let message = messageIfRedactedAttributeDoesNotExist(in: redactedAttributes, for: attribute) { - messages.append(message) - } + return messageIfRedactedAttributeDoesNotExist(in: redactedAttributes, for: attribute) } + return nil } - - return messages.isEmpty ? .matched : .failed(reason: messages.joined(separator: ", ")) + return failsToMatch(fails: messages) } fileprivate func optionalAttributeKeysDontAppearInPrivateAttrs(userDictionary: [String: Any]) -> ToMatchResult { - var messages = [String]() - let redactedAttributes = userDictionary.redactedAttributes - - LDUser.optionalAttributes.forEach { (attribute) in - if let message = messageIfAttributeExists(in: redactedAttributes, for: attribute) { - messages.append(message) - } - } - - return messages.isEmpty ? .matched : .failed(reason: messages.joined(separator: ", ")) + return failsToMatch(fails: LDUser.optionalAttributes.compactMap { attribute in + messageIfAttributeExists(in: redactedAttributes, for: attribute) + }) } fileprivate func optionalAttributePublicOrMissingKeysDontAppearInPrivateAttrs(userDictionary: [String: Any], privateAttributes: [String]) -> ToMatchResult { - var messages = [String]() - let redactedAttributes = userDictionary.redactedAttributes - - LDUser.optionalAttributes.forEach { (attribute) in + let messages: [String] = LDUser.optionalAttributes.compactMap { attribute in if value(forAttribute: attribute) == nil || !privateAttributes.contains(attribute) { - if let message = messageIfPublicOrMissingAttributeIsRedacted(in: redactedAttributes, for: attribute) { - messages.append(message) - } + return messageIfPublicOrMissingAttributeIsRedacted(in: redactedAttributes, for: attribute) } + return nil } - - return messages.isEmpty ? .matched : .failed(reason: messages.joined(separator: ", ")) + return failsToMatch(fails: messages) } fileprivate func sdkSetAttributesKeyValuePairsMatch(userDictionary: [String: Any]) -> ToMatchResult { guard let customDictionary = userDictionary.customDictionary(includeSdkSetAttributes: true) - else { - return .failed(reason: MatcherMessages.dictionaryShouldContain + CodingKeys.custom.rawValue) + else { + return .failed(reason: MatcherMessages.dictionaryShouldContain + CodingKeys.custom.rawValue) } var messages = [String]() - LDUser.sdkSetAttributes.forEach { (attribute) in + LDUser.sdkSetAttributes.forEach { attribute in if let message = messageIfMissingValue(in: customDictionary, for: attribute) { messages.append(message) } @@ -1641,7 +993,7 @@ extension LDUser { } } - return messages.isEmpty ? .matched : .failed(reason: messages.joined(separator: ", ")) + return failsToMatch(fails: messages) } fileprivate func sdkSetAttributesDontExist(userDictionary: [String: Any]) -> ToMatchResult { @@ -1649,21 +1001,17 @@ extension LDUser { return .matched } - var messages = [String]() - - LDUser.sdkSetAttributes.forEach { (attribute) in - if let message = messageIfAttributeExists(in: customDictionary, for: attribute) { - messages.append(message) - } + let messages = LDUser.sdkSetAttributes.compactMap { attribute in + messageIfAttributeExists(in: customDictionary, for: attribute) } - return messages.isEmpty ? .matched : .failed(reason: messages.joined(separator: ", ")) + return failsToMatch(fails: messages) } fileprivate func customDictionaryContainsOnlySdkSetAttributes(userDictionary: [String: Any]) -> ToMatchResult { guard let customDictionary = userDictionary.customDictionary(includeSdkSetAttributes: false) - else { - return .failed(reason: MatcherMessages.dictionaryShouldContain + CodingKeys.custom.rawValue) + else { + return .failed(reason: MatcherMessages.dictionaryShouldContain + CodingKeys.custom.rawValue) } if !customDictionary.isEmpty { @@ -1674,35 +1022,27 @@ extension LDUser { } fileprivate func privateAttrsContainsCustom(userDictionary: [String: Any]) -> Bool { - guard let redactedAttributes = userDictionary.redactedAttributes - else { - return false - } - return redactedAttributes.contains(CodingKeys.custom.rawValue) + userDictionary.redactedAttributes?.contains(CodingKeys.custom.rawValue) == true } fileprivate func privateAttrsContainsOnlyCustom(userDictionary: [String: Any]) -> Bool { - guard let redactedAttributes = userDictionary.redactedAttributes, redactedAttributes.contains(CodingKeys.custom.rawValue) - else { - return false - } - return redactedAttributes.count == 1 + privateAttrsContainsCustom(userDictionary: userDictionary) && userDictionary.redactedAttributes?.count == 1 } fileprivate func customDictionaryPublicKeyValuePairsMatch(userDictionary: [String: Any], privateAttributes: [String]) -> ToMatchResult { guard let custom = custom - else { - return userDictionary.customDictionary(includeSdkSetAttributes: false).isNilOrEmpty ? .matched - : .failed(reason: MatcherMessages.dictionaryShouldNotContain + CodingKeys.custom.rawValue) + else { + return userDictionary.customDictionary(includeSdkSetAttributes: false).isNilOrEmpty ? .matched + : .failed(reason: MatcherMessages.dictionaryShouldNotContain + CodingKeys.custom.rawValue) } guard let customDictionary = userDictionary.customDictionary(includeSdkSetAttributes: false) - else { - return .failed(reason: MatcherMessages.dictionaryShouldContain + CodingKeys.custom.rawValue) + else { + return .failed(reason: MatcherMessages.dictionaryShouldContain + CodingKeys.custom.rawValue) } var messages = [String]() - customAttributes?.forEach { (customAttribute) in + customAttributes?.forEach { customAttribute in if !privateAttributes.contains(customAttribute) { if let message = messageIfMissingValue(in: customDictionary, for: customAttribute) { messages.append(message) @@ -1713,134 +1053,93 @@ extension LDUser { } } - return messages.isEmpty ? .matched : .failed(reason: messages.joined(separator: ", ")) + return failsToMatch(fails: messages) } fileprivate func customDictionaryPrivateKeysDontExist(userDictionary: [String: Any], privateAttributes: [String]) -> ToMatchResult { guard let customDictionary = userDictionary.customDictionary(includeSdkSetAttributes: false) - else { - return .failed(reason: MatcherMessages.dictionaryShouldContain + CodingKeys.custom.rawValue) + else { + return .failed(reason: MatcherMessages.dictionaryShouldContain + CodingKeys.custom.rawValue) } - var messages = [String]() - - customAttributes?.forEach { (customAttribute) in + let messages = customAttributes?.compactMap { customAttribute in if privateAttributes.contains(customAttribute) { - if let message = messageIfAttributeExists(in: customDictionary, for: customAttribute) { - messages.append(message) - } + return messageIfAttributeExists(in: customDictionary, for: customAttribute) } - } + return nil + } ?? [String]() - return messages.isEmpty ? .matched : .failed(reason: messages.joined(separator: ", ")) + return failsToMatch(fails: messages) } fileprivate func customPrivateKeysAppearInPrivateAttrsWhenRedacted(userDictionary: [String: Any], privateAttributes: [String]) -> ToMatchResult { guard let custom = custom - else { - return userDictionary.customDictionary(includeSdkSetAttributes: false).isNilOrEmpty ? .matched - : .failed(reason: MatcherMessages.dictionaryShouldNotContain + CodingKeys.custom.rawValue) + else { + return userDictionary.customDictionary(includeSdkSetAttributes: false).isNilOrEmpty ? .matched + : .failed(reason: MatcherMessages.dictionaryShouldNotContain + CodingKeys.custom.rawValue) } - var messages = [String]() - - customAttributes?.forEach { (customAttribute) in + return failsToMatch(fails: customAttributes?.compactMap { customAttribute in if privateAttributes.contains(customAttribute) && custom[customAttribute] != nil { - if let message = messageIfRedactedAttributeDoesNotExist(in: userDictionary.redactedAttributes, for: customAttribute) { - messages.append(message) - } + return messageIfRedactedAttributeDoesNotExist(in: userDictionary.redactedAttributes, for: customAttribute) } - } - - return messages.isEmpty ? .matched : .failed(reason: messages.joined(separator: ", ")) + return nil + } ?? [String]()) } fileprivate func customPublicOrMissingKeysDontAppearInPrivateAttrs(userDictionary: [String: Any], privateAttributes: [String]) -> ToMatchResult { guard let custom = custom - else { - return userDictionary.customDictionary(includeSdkSetAttributes: false).isNilOrEmpty ? .matched - : .failed(reason: MatcherMessages.dictionaryShouldNotContain + CodingKeys.custom.rawValue) + else { + return userDictionary.customDictionary(includeSdkSetAttributes: false).isNilOrEmpty ? .matched + : .failed(reason: MatcherMessages.dictionaryShouldNotContain + CodingKeys.custom.rawValue) } - var messages = [String]() - - customAttributes?.forEach { (customAttribute) in + return failsToMatch(fails: customAttributes?.compactMap { customAttribute in if !privateAttributes.contains(customAttribute) || custom[customAttribute] == nil { - if let message = messageIfPublicOrMissingAttributeIsRedacted(in: userDictionary.redactedAttributes, for: customAttribute) { - messages.append(message) - } + return messageIfPublicOrMissingAttributeIsRedacted(in: userDictionary.redactedAttributes, for: customAttribute) } - } - - return messages.isEmpty ? .matched : .failed(reason: messages.joined(separator: ", ")) + return nil + } ?? [String]()) } fileprivate func flagConfigMatches(userDictionary: [String: Any]) -> ToMatchResult { let flagConfig = flagStore.featureFlags guard let flagConfigDictionary = userDictionary.flagConfig - else { - return .failed(reason: MatcherMessages.dictionaryShouldContain + CodingKeys.config.rawValue) + else { + return .failed(reason: MatcherMessages.dictionaryShouldContain + CodingKeys.config.rawValue) } if flagConfig != flagConfigDictionary { return .failed(reason: MatcherMessages.valuesDontMatch + CodingKeys.config.rawValue) - } return .matched } private func messageIfMissingValue(in dictionary: [String: Any], for attribute: String) -> String? { - guard dictionary[attribute] != nil - else { - return MatcherMessages.dictionaryShouldContain + attribute - } - return nil + dictionary[attribute] != nil ? nil : MatcherMessages.dictionaryShouldContain + attribute } private func messageIfValueDoesntMatch(value: Any?, in dictionary: [String: Any], for attribute: String) -> String? { - if !AnyComparer.isEqual(value, to: dictionary[attribute]) { - return MatcherMessages.valuesDontMatch + attribute - } - return nil + AnyComparer.isEqual(value, to: dictionary[attribute]) ? nil : MatcherMessages.valuesDontMatch + attribute } private func messageIfAttributeExists(in dictionary: [String: Any], for attribute: String) -> String? { - if dictionary[attribute] != nil { - return MatcherMessages.dictionaryShouldNotContain + attribute - } - return nil + dictionary[attribute] == nil ? nil : MatcherMessages.dictionaryShouldNotContain + attribute } private func messageIfRedactedAttributeDoesNotExist(in redactedAttributes: [String]?, for attribute: String) -> String? { guard let redactedAttributes = redactedAttributes - else { - return MatcherMessages.dictionaryShouldContain + CodingKeys.privateAttributes.rawValue - } - if !redactedAttributes.contains(attribute) { - return MatcherMessages.attributeListShouldContain + attribute + else { + return MatcherMessages.dictionaryShouldContain + CodingKeys.privateAttributes.rawValue } - return nil + return redactedAttributes.contains(attribute) ? nil : MatcherMessages.attributeListShouldContain + attribute } private func messageIfAttributeExists(in redactedAttributes: [String]?, for attribute: String) -> String? { - guard let redactedAttributes = redactedAttributes - else { - return nil - } - if redactedAttributes.contains(attribute) { - return MatcherMessages.attributeListShouldNotContain + attribute - } - return nil + redactedAttributes?.contains(attribute) != true ? nil : MatcherMessages.attributeListShouldNotContain + attribute } private func messageIfPublicOrMissingAttributeIsRedacted(in redactedAttributes: [String]?, for attribute: String) -> String? { - guard let redactedAttributes = redactedAttributes - else { - return nil - } - if redactedAttributes.contains(attribute) { - return MatcherMessages.attributeListShouldNotContain + attribute - } - return nil + redactedAttributes?.contains(attribute) != true ? nil : MatcherMessages.attributeListShouldNotContain + attribute } public func dictionaryValueWithAllAttributes(includeFlagConfig: Bool) -> [String: Any] { @@ -1850,20 +1149,24 @@ extension LDUser { } } +extension Optional where Wrapped: Collection { + var isNilOrEmpty: Bool { self?.isEmpty ?? true } +} + extension Dictionary where Key == String, Value == Any { fileprivate var redactedAttributes: [String]? { - return self[LDUser.CodingKeys.privateAttributes.rawValue] as? [String] + self[LDUser.CodingKeys.privateAttributes.rawValue] as? [String] } fileprivate func customDictionary(includeSdkSetAttributes: Bool) -> [String: Any]? { var customDictionary = self[LDUser.CodingKeys.custom.rawValue] as? [String: Any] if !includeSdkSetAttributes { - customDictionary = customDictionary?.filter { (key, _) in + customDictionary = customDictionary?.filter { key, _ in !LDUser.sdkSetAttributes.contains(key) } } return customDictionary } fileprivate var flagConfig: [String: Any]? { - return self[LDUser.CodingKeys.config.rawValue] as? [LDFlagKey: Any] + self[LDUser.CodingKeys.config.rawValue] as? [LDFlagKey: Any] } } diff --git a/LaunchDarkly/LaunchDarklyTests/Networking/HTTPURLResponse.swift b/LaunchDarkly/LaunchDarklyTests/Networking/HTTPURLResponse.swift index 4e8c84bf..204100f4 100644 --- a/LaunchDarkly/LaunchDarklyTests/Networking/HTTPURLResponse.swift +++ b/LaunchDarkly/LaunchDarklyTests/Networking/HTTPURLResponse.swift @@ -2,7 +2,6 @@ // HTTPURLResponse.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 2/13/18. +JMJ // Copyright © 2018 Catamorphic Co. All rights reserved. // @@ -12,7 +11,7 @@ import Foundation extension HTTPURLResponse.StatusCodes { static let all = [ok, accepted, badRequest, unauthorized, methodNotAllowed, internalServerError, notImplemented] static let retry = LDConfig.reportRetryStatusCodes - static let nonRetry = all.filter { (statusCode) in + static let nonRetry = all.filter { statusCode in !LDConfig.reportRetryStatusCodes.contains(statusCode) && statusCode != ok } } diff --git a/LaunchDarkly/LaunchDarklyTests/Networking/URLRequestSpec.swift b/LaunchDarkly/LaunchDarklyTests/Networking/URLRequestSpec.swift index 3a83594a..75644d05 100644 --- a/LaunchDarkly/LaunchDarklyTests/Networking/URLRequestSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Networking/URLRequestSpec.swift @@ -2,7 +2,6 @@ // URLRequestSpec.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 9/28/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -27,7 +26,7 @@ final class URLRequestSpec: QuickSpec { context("no new headers exist in original") { var targetHeaders = Constants.startingHeaders beforeEach { - Constants.newHeaders.forEach { (key, value) in + Constants.newHeaders.forEach { key, value in targetHeaders[key] = value } @@ -48,7 +47,7 @@ final class URLRequestSpec: QuickSpec { startingHeaders["headerA"] = "wrongValue" targetHeaders = startingHeaders - Constants.newHeaders.forEach { (key, value) in + Constants.newHeaders.forEach { key, value in targetHeaders[key] = value } diff --git a/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/CacheConverterSpec.swift b/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/CacheConverterSpec.swift index 36d95179..fd8ff8cc 100644 --- a/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/CacheConverterSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/CacheConverterSpec.swift @@ -2,7 +2,6 @@ // CacheConverterSpec.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 3/26/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -24,16 +23,16 @@ final class CacheConverterSpec: QuickSpec { var user: LDUser var config: LDConfig var featureFlagCachingMock: FeatureFlagCachingMock { - return clientServiceFactoryMock.makeFeatureFlagCacheReturnValue + clientServiceFactoryMock.makeFeatureFlagCacheReturnValue } var expiredCacheThreshold: Date var modelsToSearch = [DeprecatedCacheModel]() init(maxAge: TimeInterval? = nil, createCacheData: Bool = false, deprecatedCacheData: DeprecatedCacheModel? = nil) { if let maxAge = maxAge { - cacheConverter = CacheConverter(serviceFactory: clientServiceFactoryMock, maxAge: maxAge) + cacheConverter = CacheConverter(serviceFactory: clientServiceFactoryMock, maxCachedUsers: LDConfig.Defaults.maxCachedUsers, maxAge: maxAge) } else { - cacheConverter = CacheConverter(serviceFactory: clientServiceFactoryMock) + cacheConverter = CacheConverter(serviceFactory: clientServiceFactoryMock, maxCachedUsers: LDConfig.Defaults.maxCachedUsers) } expiredCacheThreshold = Date().addingTimeInterval(maxAge ?? CacheConverter.Constants.maxAge) if createCacheData { @@ -46,7 +45,7 @@ final class CacheConverterSpec: QuickSpec { config = LDConfig.stub featureFlagCachingMock.retrieveFeatureFlagsReturnValue = nil } - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in deprecatedCacheMock(for: model).retrieveFlagsReturnValue = (nil, nil) } if let deprecatedCacheData = deprecatedCacheData { @@ -71,11 +70,11 @@ final class CacheConverterSpec: QuickSpec { } func deprecatedCacheMock(for version: DeprecatedCacheModel) -> DeprecatedCacheMock { - return cacheConverter.deprecatedCaches[version] as! DeprecatedCacheMock + cacheConverter.deprecatedCaches[version] as! DeprecatedCacheMock } func expectedretrieveFlagsCallCount(for model: DeprecatedCacheModel) -> Int { - return (modelsToSearch.contains(model) ? 1 : 0) + modelsToSearch.contains(model) ? 1 : 0 } } @@ -95,7 +94,7 @@ final class CacheConverterSpec: QuickSpec { expect(testContext.cacheConverter.maxAge) == Constants.maxAgeAlternate expect(testContext.clientServiceFactoryMock.makeFeatureFlagCacheCallCount) == 1 expect(testContext.cacheConverter.currentCache) === testContext.clientServiceFactoryMock.makeFeatureFlagCacheReturnValue - DeprecatedCacheModel.allCases.forEach { (deprecatedCacheModel) in + DeprecatedCacheModel.allCases.forEach { deprecatedCacheModel in expect(testContext.cacheConverter.deprecatedCaches[deprecatedCacheModel]).toNot(beNil()) expect(testContext.clientServiceFactoryMock.makeDeprecatedCacheModelReceivedModels.contains(deprecatedCacheModel)) == true } @@ -109,7 +108,7 @@ final class CacheConverterSpec: QuickSpec { expect(testContext.cacheConverter.maxAge) == CacheConverter.Constants.maxAge expect(testContext.clientServiceFactoryMock.makeFeatureFlagCacheCallCount) == 1 expect(testContext.cacheConverter.currentCache) === testContext.clientServiceFactoryMock.makeFeatureFlagCacheReturnValue - DeprecatedCacheModel.allCases.forEach { (deprecatedCacheModel) in + DeprecatedCacheModel.allCases.forEach { deprecatedCacheModel in expect(testContext.cacheConverter.deprecatedCaches[deprecatedCacheModel]).toNot(beNil()) expect(testContext.clientServiceFactoryMock.makeDeprecatedCacheModelReceivedModels.contains(deprecatedCacheModel)) == true } @@ -129,7 +128,7 @@ final class CacheConverterSpec: QuickSpec { testContext.cacheConverter.convertCacheData(for: testContext.user, and: testContext.config) } it("does not look in the deprecated caches for data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).retrieveFlagsCallCount) == 0 } } @@ -137,7 +136,7 @@ final class CacheConverterSpec: QuickSpec { expect(testContext.featureFlagCachingMock.storeFeatureFlagsCallCount) == 0 } it("removes expired deprecated cache data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).removeDataCallCount) == 1 expect(testContext.deprecatedCacheMock(for: model).removeDataReceivedExpirationDate? .isWithin(Constants.acceptableInterval, of: testContext.expiredCacheThreshold)) == true @@ -151,7 +150,7 @@ final class CacheConverterSpec: QuickSpec { testContext.cacheConverter.convertCacheData(for: testContext.user, and: testContext.config) } it("does not look in the deprecated caches for data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).retrieveFlagsCallCount) == 0 } } @@ -159,7 +158,7 @@ final class CacheConverterSpec: QuickSpec { expect(testContext.featureFlagCachingMock.storeFeatureFlagsCallCount) == 0 } it("removes expired deprecated cache data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).removeDataCallCount) == 1 expect(testContext.deprecatedCacheMock(for: model).removeDataReceivedExpirationDate? .isWithin(Constants.acceptableInterval, of: testContext.expiredCacheThreshold)) == true @@ -173,7 +172,7 @@ final class CacheConverterSpec: QuickSpec { testContext.cacheConverter.convertCacheData(for: testContext.user, and: testContext.config) } it("does not look in the deprecated caches for data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).retrieveFlagsCallCount) == 0 } } @@ -181,7 +180,7 @@ final class CacheConverterSpec: QuickSpec { expect(testContext.featureFlagCachingMock.storeFeatureFlagsCallCount) == 0 } it("removes expired deprecated cache data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).removeDataCallCount) == 1 expect(testContext.deprecatedCacheMock(for: model).removeDataReceivedExpirationDate? .isWithin(Constants.acceptableInterval, of: testContext.expiredCacheThreshold)) == true @@ -195,7 +194,7 @@ final class CacheConverterSpec: QuickSpec { testContext.cacheConverter.convertCacheData(for: testContext.user, and: testContext.config) } it("does not look in the deprecated caches for data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).retrieveFlagsCallCount) == 0 } } @@ -203,7 +202,7 @@ final class CacheConverterSpec: QuickSpec { expect(testContext.featureFlagCachingMock.storeFeatureFlagsCallCount) == 0 } it("removes expired deprecated cache data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).removeDataCallCount) == 1 expect(testContext.deprecatedCacheMock(for: model).removeDataReceivedExpirationDate? .isWithin(Constants.acceptableInterval, of: testContext.expiredCacheThreshold)) == true @@ -218,7 +217,7 @@ final class CacheConverterSpec: QuickSpec { testContext.cacheConverter.convertCacheData(for: testContext.user, and: testContext.config) } it("does not look in the deprecated caches for data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).retrieveFlagsCallCount) == 0 } } @@ -226,7 +225,7 @@ final class CacheConverterSpec: QuickSpec { expect(testContext.featureFlagCachingMock.storeFeatureFlagsCallCount) == 0 } it("removes expired deprecated cache data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).removeDataCallCount) == 1 expect(testContext.deprecatedCacheMock(for: model).removeDataReceivedExpirationDate? .isWithin(Constants.acceptableInterval, of: testContext.expiredCacheThreshold)) == true @@ -242,7 +241,7 @@ final class CacheConverterSpec: QuickSpec { testContext.cacheConverter.convertCacheData(for: testContext.user, and: testContext.config) } it("looks in the deprecated caches for data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).retrieveFlagsCallCount) == 1 } } @@ -250,7 +249,7 @@ final class CacheConverterSpec: QuickSpec { expect(testContext.featureFlagCachingMock.storeFeatureFlagsCallCount) == 0 } it("removes expired deprecated cache data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).removeDataCallCount) == 1 expect(testContext.deprecatedCacheMock(for: model).removeDataReceivedExpirationDate? .isWithin(Constants.acceptableInterval, of: testContext.expiredCacheThreshold)) == true @@ -260,11 +259,11 @@ final class CacheConverterSpec: QuickSpec { context("deprecated version 6 cache data exists") { beforeEach { testContext = TestContext(maxAge: Constants.maxAgeAlternate, createCacheData: false, deprecatedCacheData: .version6) - + testContext.cacheConverter.convertCacheData(for: testContext.user, and: testContext.config) } it("looks in the version 6 cache for data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).retrieveFlagsCallCount) == testContext.expectedretrieveFlagsCallCount(for: model) } } @@ -279,7 +278,7 @@ final class CacheConverterSpec: QuickSpec { expect(testContext.featureFlagCachingMock.storeFeatureFlagsReceivedArguments?.storeMode) == .sync } it("removes expired deprecated cache data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).removeDataCallCount) == 1 expect(testContext.deprecatedCacheMock(for: model).removeDataReceivedExpirationDate? .isWithin(Constants.acceptableInterval, of: testContext.expiredCacheThreshold)) == true @@ -293,7 +292,7 @@ final class CacheConverterSpec: QuickSpec { testContext.cacheConverter.convertCacheData(for: testContext.user, and: testContext.config) } it("looks in the version 5 cache for data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).retrieveFlagsCallCount) == testContext.expectedretrieveFlagsCallCount(for: model) } } @@ -308,7 +307,7 @@ final class CacheConverterSpec: QuickSpec { expect(testContext.featureFlagCachingMock.storeFeatureFlagsReceivedArguments?.storeMode) == .sync } it("removes expired deprecated cache data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).removeDataCallCount) == 1 expect(testContext.deprecatedCacheMock(for: model).removeDataReceivedExpirationDate? .isWithin(Constants.acceptableInterval, of: testContext.expiredCacheThreshold)) == true @@ -322,7 +321,7 @@ final class CacheConverterSpec: QuickSpec { testContext.cacheConverter.convertCacheData(for: testContext.user, and: testContext.config) } it("looks in the versions 5 and 4 caches for data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).retrieveFlagsCallCount) == testContext.expectedretrieveFlagsCallCount(for: model) } } @@ -337,7 +336,7 @@ final class CacheConverterSpec: QuickSpec { expect(testContext.featureFlagCachingMock.storeFeatureFlagsReceivedArguments?.storeMode) == .sync } it("removes expired deprecated cache data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).removeDataCallCount) == 1 expect(testContext.deprecatedCacheMock(for: model).removeDataReceivedExpirationDate? .isWithin(Constants.acceptableInterval, of: testContext.expiredCacheThreshold)) == true @@ -351,7 +350,7 @@ final class CacheConverterSpec: QuickSpec { testContext.cacheConverter.convertCacheData(for: testContext.user, and: testContext.config) } it("looks in the versions 5, 4, and 3 caches for data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).retrieveFlagsCallCount) == testContext.expectedretrieveFlagsCallCount(for: model) } } @@ -366,7 +365,7 @@ final class CacheConverterSpec: QuickSpec { expect(testContext.featureFlagCachingMock.storeFeatureFlagsReceivedArguments?.storeMode) == .sync } it("removes expired deprecated cache data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).removeDataCallCount) == 1 expect(testContext.deprecatedCacheMock(for: model).removeDataReceivedExpirationDate? .isWithin(Constants.acceptableInterval, of: testContext.expiredCacheThreshold)) == true @@ -380,7 +379,7 @@ final class CacheConverterSpec: QuickSpec { testContext.cacheConverter.convertCacheData(for: testContext.user, and: testContext.config) } it("looks in the deprecated caches for data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).retrieveFlagsCallCount) == 1 } } @@ -395,7 +394,7 @@ final class CacheConverterSpec: QuickSpec { expect(testContext.featureFlagCachingMock.storeFeatureFlagsReceivedArguments?.storeMode) == .sync } it("removes expired deprecated cache data") { - DeprecatedCacheModel.allCases.forEach { (model) in + DeprecatedCacheModel.allCases.forEach { model in expect(testContext.deprecatedCacheMock(for: model).removeDataCallCount) == 1 expect(testContext.deprecatedCacheMock(for: model).removeDataReceivedExpirationDate? .isWithin(Constants.acceptableInterval, of: testContext.expiredCacheThreshold)) == true diff --git a/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV2Spec.swift b/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV2Spec.swift index e53fa427..84bcee51 100644 --- a/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV2Spec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV2Spec.swift @@ -2,7 +2,6 @@ // DeprecatedCacheModelV2Spec.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 4/1/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -26,26 +25,13 @@ final class DeprecatedCacheModelV2Spec: QuickSpec { var uncachedUser: LDUser var mobileKeys: [MobileKey] var uncachedMobileKey: MobileKey - var firstMobileKey: MobileKey { - return mobileKeys.first! - } - var lastUpdatedDates: [UserKey: Date] { - return userEnvironmentsCollection.compactMapValues { (cacheableUserFlags) in - return cacheableUserFlags.lastUpdated - } - } + var firstMobileKey: MobileKey { mobileKeys.first! } var sortedLastUpdatedDates: [(userKey: UserKey, lastUpdated: Date)] { - return lastUpdatedDates.map { (userKey, lastUpdated) in - return (userKey, lastUpdated) - }.sorted { (tuple1, tuple2) in - return tuple1.lastUpdated.isEarlierThan(tuple2.lastUpdated) - } - } - var userKeys: [UserKey] { - return users.map { (user) in - return user.key + userEnvironmentsCollection.map { ($0, $1.lastUpdated) }.sorted { tuple1, tuple2 in + tuple1.lastUpdated.isEarlierThan(tuple2.lastUpdated) } } + var userKeys: [UserKey] { users.map { $0.key } } init(userCount: Int = 0) { keyedValueCacheMock = clientServiceFactoryMock.makeKeyedValueCache() as! KeyedValueCachingMock @@ -60,12 +46,7 @@ final class DeprecatedCacheModelV2Spec: QuickSpec { } func featureFlags(for userKey: UserKey) -> [LDFlagKey: FeatureFlag]? { - guard !mobileKeys.isEmpty - else { - return nil - } - - return userEnvironmentsCollection[userKey]?.environmentFlags[firstMobileKey]?.featureFlags.modelV2flagCollection + userEnvironmentsCollection[userKey]?.environmentFlags[firstMobileKey]?.featureFlags.modelV2flagCollection } func modelV2Dictionary(for users: [LDUser], and userEnvironmentsCollection: [UserKey: CacheableUserEnvironmentFlags], storingMobileKey: MobileKey?) -> [UserKey: Any]? { @@ -74,7 +55,7 @@ final class DeprecatedCacheModelV2Spec: QuickSpec { return nil } - return Dictionary(uniqueKeysWithValues: users.map { (user) in + return Dictionary(uniqueKeysWithValues: users.map { user in let featureFlags = userEnvironmentsCollection[user.key]?.environmentFlags[mobileKey]?.featureFlags let lastUpdated = userEnvironmentsCollection[user.key]?.lastUpdated return (user.key, user.modelV2DictionaryValue(including: featureFlags!, using: lastUpdated)) @@ -82,12 +63,8 @@ final class DeprecatedCacheModelV2Spec: QuickSpec { } func expiredUserKeys(for expirationDate: Date) -> [UserKey] { - return sortedLastUpdatedDates.compactMap { (tuple) in - guard tuple.lastUpdated.isEarlierThan(expirationDate) - else { - return nil - } - return tuple.userKey + sortedLastUpdatedDates.compactMap { tuple in + tuple.lastUpdated.isEarlierThan(expirationDate) ? tuple.userKey : nil } } } @@ -130,13 +107,13 @@ final class DeprecatedCacheModelV2Spec: QuickSpec { context("when cached data exists") { context("and a cached user is requested") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) } it("retrieves the cached data") { - testContext.users.forEach { (user) in + testContext.users.forEach { user in let expectedFlags = testContext.featureFlags(for: user.key) let expectedLastUpdated = testContext.userEnvironmentsCollection.lastUpdated(forKey: user.key)?.stringEquivalentDate - testContext.mobileKeys.forEach { (mobileKey) in + testContext.mobileKeys.forEach { mobileKey in cachedData = testContext.modelV2cache.retrieveFlags(for: user.key, and: mobileKey) expect(cachedData.featureFlags) == expectedFlags expect(cachedData.lastUpdated) == expectedLastUpdated @@ -146,7 +123,7 @@ final class DeprecatedCacheModelV2Spec: QuickSpec { } context("and an uncached user is requested") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) cachedData = testContext.modelV2cache.retrieveFlags(for: testContext.uncachedUser.key, and: testContext.uncachedMobileKey) } @@ -165,7 +142,7 @@ final class DeprecatedCacheModelV2Spec: QuickSpec { describe("removeData") { context("no modelV2 cached data expired") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let oldestLastUpdatedDate = testContext.sortedLastUpdatedDates.first! expirationDate = oldestLastUpdatedDate.lastUpdated.addingTimeInterval(-Constants.offsetInterval) @@ -177,7 +154,7 @@ final class DeprecatedCacheModelV2Spec: QuickSpec { } context("some modelV2 cached data expired") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let selectedLastUpdatedDate = testContext.sortedLastUpdatedDates[testContext.users.count / 2] expirationDate = selectedLastUpdatedDate.lastUpdated.addingTimeInterval(-Constants.offsetInterval) @@ -188,14 +165,14 @@ final class DeprecatedCacheModelV2Spec: QuickSpec { expect(testContext.keyedValueCacheMock.setReceivedArguments?.forKey) == CacheConverter.CacheKeys.ldUserModelDictionary let recachedData = testContext.keyedValueCacheMock.setReceivedArguments?.value as? [String: Any] let expiredUserKeys = testContext.expiredUserKeys(for: expirationDate) - testContext.userKeys.forEach { (userKey) in + testContext.userKeys.forEach { userKey in expect(recachedData?.keys.contains(userKey)) == !expiredUserKeys.contains(userKey) } } } context("all modelV2 cached data expired") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let newestLastUpdatedDate = testContext.sortedLastUpdatedDates.last! expirationDate = newestLastUpdatedDate.lastUpdated.addingTimeInterval(Constants.offsetInterval) @@ -208,7 +185,7 @@ final class DeprecatedCacheModelV2Spec: QuickSpec { } context("no modelV2 cached data exists") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let newestLastUpdatedDate = testContext.sortedLastUpdatedDates.last! expirationDate = newestLastUpdatedDate.lastUpdated.addingTimeInterval(Constants.offsetInterval) testContext.keyedValueCacheMock.dictionaryReturnValue = nil //mock simulates no modelV2 cached data @@ -221,26 +198,17 @@ final class DeprecatedCacheModelV2Spec: QuickSpec { } } } - } // MARK: Expected value from conversion extension Dictionary where Key == LDFlagKey, Value == FeatureFlag { - var modelV2flagCollection: [LDFlagKey: FeatureFlag] { - return compactMapValues { (originalFeatureFlag) in - guard originalFeatureFlag.value != nil - else { - return nil - } - return originalFeatureFlag.modelV2FeatureFlag - } - } + var modelV2flagCollection: [LDFlagKey: FeatureFlag] { compactMapValues { $0.value != nil ? $0.modelV2FeatureFlag : nil } } } extension FeatureFlag { var modelV2FeatureFlag: FeatureFlag { - return FeatureFlag(flagKey: flagKey, value: value, variation: nil, version: nil, flagVersion: nil, eventTrackingContext: nil, reason: nil, trackReason: nil) + FeatureFlag(flagKey: flagKey, value: value, variation: nil, version: nil, flagVersion: nil, eventTrackingContext: nil, reason: nil, trackReason: nil) } } diff --git a/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV3Spec.swift b/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV3Spec.swift index 4b6fea05..5ee59f1f 100644 --- a/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV3Spec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV3Spec.swift @@ -2,7 +2,6 @@ // DeprecatedCacheModelV3Spec.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 4/2/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -26,26 +25,13 @@ final class DeprecatedCacheModelV3Spec: QuickSpec { var uncachedUser: LDUser var mobileKeys: [MobileKey] var uncachedMobileKey: MobileKey - var firstMobileKey: MobileKey { - return mobileKeys.first! - } - var lastUpdatedDates: [UserKey: Date] { - return userEnvironmentsCollection.compactMapValues { (cacheableUserFlags) in - return cacheableUserFlags.lastUpdated - } - } + var firstMobileKey: MobileKey { mobileKeys.first! } var sortedLastUpdatedDates: [(userKey: UserKey, lastUpdated: Date)] { - return lastUpdatedDates.map { (userKey, lastUpdated) in - return (userKey, lastUpdated) - }.sorted { (tuple1, tuple2) in - return tuple1.lastUpdated.isEarlierThan(tuple2.lastUpdated) - } - } - var userKeys: [UserKey] { - return users.map { (user) in - return user.key + userEnvironmentsCollection.map { ($0, $1.lastUpdated) }.sorted { tuple1, tuple2 in + tuple1.lastUpdated.isEarlierThan(tuple2.lastUpdated) } } + var userKeys: [UserKey] { users.map { $0.key } } init(userCount: Int = 0) { keyedValueCacheMock = clientServiceFactoryMock.makeKeyedValueCache() as! KeyedValueCachingMock @@ -60,12 +46,7 @@ final class DeprecatedCacheModelV3Spec: QuickSpec { } func featureFlags(for userKey: UserKey) -> [LDFlagKey: FeatureFlag]? { - guard !mobileKeys.isEmpty - else { - return nil - } - - return userEnvironmentsCollection[userKey]?.environmentFlags[firstMobileKey]?.featureFlags.modelV3flagCollection + userEnvironmentsCollection[userKey]?.environmentFlags[firstMobileKey]?.featureFlags.modelV3flagCollection } func modelV3Dictionary(for users: [LDUser], and userEnvironmentsCollection: [UserKey: CacheableUserEnvironmentFlags], storingMobileKey: MobileKey?) -> [UserKey: Any]? { @@ -74,7 +55,7 @@ final class DeprecatedCacheModelV3Spec: QuickSpec { return nil } - return Dictionary(uniqueKeysWithValues: users.map { (user) in + return Dictionary(uniqueKeysWithValues: users.map { user in let featureFlags = userEnvironmentsCollection[user.key]?.environmentFlags[mobileKey]?.featureFlags let lastUpdated = userEnvironmentsCollection[user.key]?.lastUpdated return (user.key, user.modelV3DictionaryValue(including: featureFlags!, using: lastUpdated)) @@ -82,12 +63,8 @@ final class DeprecatedCacheModelV3Spec: QuickSpec { } func expiredUserKeys(for expirationDate: Date) -> [UserKey] { - return sortedLastUpdatedDates.compactMap { (tuple) in - guard tuple.lastUpdated.isEarlierThan(expirationDate) - else { - return nil - } - return tuple.userKey + sortedLastUpdatedDates.compactMap { tuple in + tuple.lastUpdated.isEarlierThan(expirationDate) ? tuple.userKey : nil } } } @@ -130,13 +107,13 @@ final class DeprecatedCacheModelV3Spec: QuickSpec { context("when cached data exists") { context("and a cached user is requested") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) } it("retrieves the cached data") { - testContext.users.forEach { (user) in + testContext.users.forEach { user in let expectedFlags = testContext.featureFlags(for: user.key) let expectedLastUpdated = testContext.userEnvironmentsCollection.lastUpdated(forKey: user.key)?.stringEquivalentDate - testContext.mobileKeys.forEach { (mobileKey) in + testContext.mobileKeys.forEach { mobileKey in cachedData = testContext.modelV3cache.retrieveFlags(for: user.key, and: mobileKey) expect(cachedData.featureFlags) == expectedFlags expect(cachedData.lastUpdated) == expectedLastUpdated @@ -146,7 +123,7 @@ final class DeprecatedCacheModelV3Spec: QuickSpec { } context("and an uncached user is requested") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) cachedData = testContext.modelV3cache.retrieveFlags(for: testContext.uncachedUser.key, and: testContext.uncachedMobileKey) } @@ -165,7 +142,7 @@ final class DeprecatedCacheModelV3Spec: QuickSpec { describe("removeData") { context("no modelV3 cached data expired") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let oldestLastUpdatedDate = testContext.sortedLastUpdatedDates.first! expirationDate = oldestLastUpdatedDate.lastUpdated.addingTimeInterval(-Constants.offsetInterval) @@ -177,7 +154,7 @@ final class DeprecatedCacheModelV3Spec: QuickSpec { } context("some modelV3 cached data expired") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let selectedLastUpdatedDate = testContext.sortedLastUpdatedDates[testContext.users.count / 2] expirationDate = selectedLastUpdatedDate.lastUpdated.addingTimeInterval(-Constants.offsetInterval) @@ -188,14 +165,14 @@ final class DeprecatedCacheModelV3Spec: QuickSpec { expect(testContext.keyedValueCacheMock.setReceivedArguments?.forKey) == CacheConverter.CacheKeys.ldUserModelDictionary let recachedData = testContext.keyedValueCacheMock.setReceivedArguments?.value as? [String: Any] let expiredUserKeys = testContext.expiredUserKeys(for: expirationDate) - testContext.userKeys.forEach { (userKey) in + testContext.userKeys.forEach { userKey in expect(recachedData?.keys.contains(userKey)) == !expiredUserKeys.contains(userKey) } } } context("all modelV3 cached data expired") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let newestLastUpdatedDate = testContext.sortedLastUpdatedDates.last! expirationDate = newestLastUpdatedDate.lastUpdated.addingTimeInterval(Constants.offsetInterval) @@ -208,7 +185,7 @@ final class DeprecatedCacheModelV3Spec: QuickSpec { } context("no modelV3 cached data exists") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let newestLastUpdatedDate = testContext.sortedLastUpdatedDates.last! expirationDate = newestLastUpdatedDate.lastUpdated.addingTimeInterval(Constants.offsetInterval) testContext.keyedValueCacheMock.dictionaryReturnValue = nil //mock simulates no modelV3 cached data @@ -221,26 +198,17 @@ final class DeprecatedCacheModelV3Spec: QuickSpec { } } } - } // MARK: Expected value from conversion extension Dictionary where Key == LDFlagKey, Value == FeatureFlag { - var modelV3flagCollection: [LDFlagKey: FeatureFlag] { - return compactMapValues { (originalFeatureFlag) in - guard originalFeatureFlag.value != nil - else { - return nil - } - return originalFeatureFlag.modelV3FeatureFlag - } - } + var modelV3flagCollection: [LDFlagKey: FeatureFlag] { compactMapValues { $0.value != nil ? $0.modelV3FeatureFlag : nil } } } extension FeatureFlag { var modelV3FeatureFlag: FeatureFlag { - return FeatureFlag(flagKey: flagKey, value: value, variation: nil, version: version, flagVersion: nil, eventTrackingContext: nil, reason: nil, trackReason: nil) + FeatureFlag(flagKey: flagKey, value: value, variation: nil, version: version, flagVersion: nil, eventTrackingContext: nil, reason: nil, trackReason: nil) } } @@ -250,9 +218,7 @@ extension LDUser { func modelV3DictionaryValue(including featureFlags: [LDFlagKey: FeatureFlag], using lastUpdated: Date?) -> [String: Any] { var userDictionary = dictionaryValueWithAllAttributes(includeFlagConfig: false) userDictionary.setLastUpdated(lastUpdated) - userDictionary[LDUser.CodingKeys.config.rawValue] = featureFlags.compactMapValues { (featureFlag) in - return featureFlag.modelV3dictionaryValue - } + userDictionary[LDUser.CodingKeys.config.rawValue] = featureFlags.compactMapValues { $0.modelV3dictionaryValue } return userDictionary } diff --git a/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV4Spec.swift b/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV4Spec.swift index a56dbae8..daf9a623 100644 --- a/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV4Spec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV4Spec.swift @@ -2,7 +2,6 @@ // DeprecatedCacheModelV4Spec.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 4/2/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -26,26 +25,13 @@ final class DeprecatedCacheModelV4Spec: QuickSpec { var uncachedUser: LDUser var mobileKeys: [MobileKey] var uncachedMobileKey: MobileKey - var firstMobileKey: MobileKey { - return mobileKeys.first! - } - var lastUpdatedDates: [UserKey: Date] { - return userEnvironmentsCollection.compactMapValues { (cacheableUserFlags) in - return cacheableUserFlags.lastUpdated - } - } + var firstMobileKey: MobileKey { mobileKeys.first! } var sortedLastUpdatedDates: [(userKey: UserKey, lastUpdated: Date)] { - return lastUpdatedDates.map { (userKey, lastUpdated) in - return (userKey, lastUpdated) - }.sorted { (tuple1, tuple2) in - return tuple1.lastUpdated.isEarlierThan(tuple2.lastUpdated) - } - } - var userKeys: [UserKey] { - return users.map { (user) in - return user.key + userEnvironmentsCollection.map { ($0, $1.lastUpdated) }.sorted { tuple1, tuple2 in + tuple1.lastUpdated.isEarlierThan(tuple2.lastUpdated) } } + var userKeys: [UserKey] { users.map { $0.key } } init(userCount: Int = 0) { keyedValueCacheMock = clientServiceFactoryMock.makeKeyedValueCache() as! KeyedValueCachingMock @@ -60,12 +46,7 @@ final class DeprecatedCacheModelV4Spec: QuickSpec { } func featureFlags(for userKey: UserKey) -> [LDFlagKey: FeatureFlag]? { - guard !mobileKeys.isEmpty - else { - return nil - } - - return userEnvironmentsCollection[userKey]?.environmentFlags[firstMobileKey]?.featureFlags.modelV4flagCollection + userEnvironmentsCollection[userKey]?.environmentFlags[firstMobileKey]?.featureFlags.modelV4flagCollection } func modelV4Dictionary(for users: [LDUser], and userEnvironmentsCollection: [UserKey: CacheableUserEnvironmentFlags], storingMobileKey: MobileKey?) -> [UserKey: Any]? { @@ -74,7 +55,7 @@ final class DeprecatedCacheModelV4Spec: QuickSpec { return nil } - return Dictionary(uniqueKeysWithValues: users.map { (user) in + return Dictionary(uniqueKeysWithValues: users.map { user in let featureFlags = userEnvironmentsCollection[user.key]?.environmentFlags[mobileKey]?.featureFlags let lastUpdated = userEnvironmentsCollection[user.key]?.lastUpdated return (user.key, user.modelV4DictionaryValue(including: featureFlags!, using: lastUpdated)) @@ -82,12 +63,8 @@ final class DeprecatedCacheModelV4Spec: QuickSpec { } func expiredUserKeys(for expirationDate: Date) -> [UserKey] { - return sortedLastUpdatedDates.compactMap { (tuple) in - guard tuple.lastUpdated.isEarlierThan(expirationDate) - else { - return nil - } - return tuple.userKey + sortedLastUpdatedDates.compactMap { tuple in + tuple.lastUpdated.isEarlierThan(expirationDate) ? tuple.userKey : nil } } } @@ -130,13 +107,13 @@ final class DeprecatedCacheModelV4Spec: QuickSpec { context("when cached data exists") { context("and a cached user is requested") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) } it("retrieves the cached data") { - testContext.users.forEach { (user) in + testContext.users.forEach { user in let expectedFlags = testContext.featureFlags(for: user.key) let expectedLastUpdated = testContext.userEnvironmentsCollection.lastUpdated(forKey: user.key)?.stringEquivalentDate - testContext.mobileKeys.forEach { (mobileKey) in + testContext.mobileKeys.forEach { mobileKey in cachedData = testContext.modelV4cache.retrieveFlags(for: user.key, and: mobileKey) expect(cachedData.featureFlags) == expectedFlags expect(cachedData.lastUpdated) == expectedLastUpdated @@ -146,7 +123,7 @@ final class DeprecatedCacheModelV4Spec: QuickSpec { } context("and an uncached user is requested") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) cachedData = testContext.modelV4cache.retrieveFlags(for: testContext.uncachedUser.key, and: testContext.uncachedMobileKey) } @@ -165,7 +142,7 @@ final class DeprecatedCacheModelV4Spec: QuickSpec { describe("removeData") { context("no modelV4 cached data expired") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let oldestLastUpdatedDate = testContext.sortedLastUpdatedDates.first! expirationDate = oldestLastUpdatedDate.lastUpdated.addingTimeInterval(-Constants.offsetInterval) @@ -177,7 +154,7 @@ final class DeprecatedCacheModelV4Spec: QuickSpec { } context("some modelV4 cached data expired") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let selectedLastUpdatedDate = testContext.sortedLastUpdatedDates[testContext.users.count / 2] expirationDate = selectedLastUpdatedDate.lastUpdated.addingTimeInterval(-Constants.offsetInterval) @@ -188,14 +165,14 @@ final class DeprecatedCacheModelV4Spec: QuickSpec { expect(testContext.keyedValueCacheMock.setReceivedArguments?.forKey) == CacheConverter.CacheKeys.ldUserModelDictionary let recachedData = testContext.keyedValueCacheMock.setReceivedArguments?.value as? [String: Any] let expiredUserKeys = testContext.expiredUserKeys(for: expirationDate) - testContext.userKeys.forEach { (userKey) in + testContext.userKeys.forEach { userKey in expect(recachedData?.keys.contains(userKey)) == !expiredUserKeys.contains(userKey) } } } context("all modelV4 cached data expired") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let newestLastUpdatedDate = testContext.sortedLastUpdatedDates.last! expirationDate = newestLastUpdatedDate.lastUpdated.addingTimeInterval(Constants.offsetInterval) @@ -208,7 +185,7 @@ final class DeprecatedCacheModelV4Spec: QuickSpec { } context("no modelV4 cached data exists") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let newestLastUpdatedDate = testContext.sortedLastUpdatedDates.last! expirationDate = newestLastUpdatedDate.lastUpdated.addingTimeInterval(Constants.offsetInterval) testContext.keyedValueCacheMock.dictionaryReturnValue = nil //mock simulates no modelV4 cached data @@ -221,26 +198,17 @@ final class DeprecatedCacheModelV4Spec: QuickSpec { } } } - } // MARK: Expected value from conversion extension Dictionary where Key == LDFlagKey, Value == FeatureFlag { - var modelV4flagCollection: [LDFlagKey: FeatureFlag] { - return compactMapValues { (originalFeatureFlag) in - guard originalFeatureFlag.value != nil - else { - return nil - } - return originalFeatureFlag.modelV4FeatureFlag - } - } + var modelV4flagCollection: [LDFlagKey: FeatureFlag] { compactMapValues { $0.value != nil ? $0.modelV4FeatureFlag : nil } } } extension FeatureFlag { var modelV4FeatureFlag: FeatureFlag { - return FeatureFlag(flagKey: flagKey, value: value, variation: variation, version: version, flagVersion: flagVersion, eventTrackingContext: eventTrackingContext, reason: nil, trackReason: nil) + FeatureFlag(flagKey: flagKey, value: value, variation: variation, version: version, flagVersion: flagVersion, eventTrackingContext: eventTrackingContext, reason: nil, trackReason: nil) } } @@ -250,9 +218,7 @@ extension LDUser { func modelV4DictionaryValue(including featureFlags: [LDFlagKey: FeatureFlag], using lastUpdated: Date?) -> [String: Any] { var userDictionary = dictionaryValueWithAllAttributes(includeFlagConfig: false) userDictionary.setLastUpdated(lastUpdated) - userDictionary[LDUser.CodingKeys.config.rawValue] = featureFlags.compactMapValues { (featureFlag) in - return featureFlag.modelV4dictionaryValue - } + userDictionary[LDUser.CodingKeys.config.rawValue] = featureFlags.compactMapValues { $0.modelV4dictionaryValue } return userDictionary } diff --git a/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV5Spec.swift b/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV5Spec.swift index e8f1583b..bf0a2b2b 100644 --- a/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV5Spec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV5Spec.swift @@ -2,7 +2,6 @@ // DeprecatedCacheModelV5Spec.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 4/2/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -26,23 +25,12 @@ final class DeprecatedCacheModelV5Spec: QuickSpec { var uncachedUser: LDUser var mobileKeys: [MobileKey] var uncachedMobileKey: MobileKey - var lastUpdatedDates: [UserKey: Date] { - return userEnvironmentsCollection.compactMapValues { (cacheableUserFlags) in - return cacheableUserFlags.lastUpdated - } - } var sortedLastUpdatedDates: [(userKey: UserKey, lastUpdated: Date)] { - return lastUpdatedDates.map { (userKey, lastUpdated) in - return (userKey, lastUpdated) - }.sorted { (tuple1, tuple2) in - return tuple1.lastUpdated.isEarlierThan(tuple2.lastUpdated) - } - } - var userKeys: [UserKey] { - return users.map { (user) in - return user.key + userEnvironmentsCollection.map { ($0, $1.lastUpdated) }.sorted { tuple1, tuple2 in + tuple1.lastUpdated.isEarlierThan(tuple2.lastUpdated) } } + var userKeys: [UserKey] { users.map { $0.key } } init(userCount: Int = 0) { keyedValueCacheMock = clientServiceFactoryMock.makeKeyedValueCache() as! KeyedValueCachingMock @@ -57,7 +45,7 @@ final class DeprecatedCacheModelV5Spec: QuickSpec { } func featureFlags(for userKey: UserKey, and mobileKey: MobileKey) -> [LDFlagKey: FeatureFlag]? { - return userEnvironmentsCollection[userKey]?.environmentFlags[mobileKey]?.featureFlags.modelV5flagCollection + userEnvironmentsCollection[userKey]?.environmentFlags[mobileKey]?.featureFlags.modelV5flagCollection } func modelV5Dictionary(for users: [LDUser], and userEnvironmentsCollection: [UserKey: CacheableUserEnvironmentFlags], mobileKeys: [MobileKey]) -> [UserKey: Any]? { @@ -67,14 +55,14 @@ final class DeprecatedCacheModelV5Spec: QuickSpec { } var cacheDictionary = [UserKey: [String: Any]]() - users.forEach { (user) in + users.forEach { user in guard let userEnvironment = userEnvironmentsCollection[user.key] else { return } var environmentsDictionary = [MobileKey: Any]() let lastUpdated = userEnvironmentsCollection[user.key]?.lastUpdated - mobileKeys.forEach { (mobileKey) in + mobileKeys.forEach { mobileKey in guard let featureFlags = userEnvironment.environmentFlags[mobileKey]?.featureFlags else { return @@ -88,12 +76,8 @@ final class DeprecatedCacheModelV5Spec: QuickSpec { } func expiredUserKeys(for expirationDate: Date) -> [UserKey] { - return sortedLastUpdatedDates.compactMap { (tuple) in - guard tuple.lastUpdated.isEarlierThan(expirationDate) - else { - return nil - } - return tuple.userKey + sortedLastUpdatedDates.compactMap { tuple in + tuple.lastUpdated.isEarlierThan(expirationDate) ? tuple.userKey : nil } } } @@ -136,12 +120,12 @@ final class DeprecatedCacheModelV5Spec: QuickSpec { context("when cached data exists") { context("and a cached user is requested") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) } it("retrieves the cached data") { - testContext.users.forEach { (user) in + testContext.users.forEach { user in let expectedLastUpdated = testContext.userEnvironmentsCollection.lastUpdated(forKey: user.key)?.stringEquivalentDate - testContext.mobileKeys.forEach { (mobileKey) in + testContext.mobileKeys.forEach { mobileKey in let expectedFlags = testContext.featureFlags(for: user.key, and: mobileKey) cachedData = testContext.modelV5cache.retrieveFlags(for: user.key, and: mobileKey) expect(cachedData.featureFlags) == expectedFlags @@ -152,7 +136,7 @@ final class DeprecatedCacheModelV5Spec: QuickSpec { } context("and an uncached mobileKey is requested") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) cachedData = testContext.modelV5cache.retrieveFlags(for: testContext.users.first!.key, and: testContext.uncachedMobileKey) } @@ -163,7 +147,7 @@ final class DeprecatedCacheModelV5Spec: QuickSpec { } context("and an uncached user is requested") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) cachedData = testContext.modelV5cache.retrieveFlags(for: testContext.uncachedUser.key, and: testContext.mobileKeys.first!) } @@ -182,7 +166,7 @@ final class DeprecatedCacheModelV5Spec: QuickSpec { describe("removeData") { context("no modelV5 cached data expired") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let oldestLastUpdatedDate = testContext.sortedLastUpdatedDates.first! expirationDate = oldestLastUpdatedDate.lastUpdated.addingTimeInterval(-Constants.offsetInterval) @@ -194,7 +178,7 @@ final class DeprecatedCacheModelV5Spec: QuickSpec { } context("some modelV5 cached data expired") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let selectedLastUpdatedDate = testContext.sortedLastUpdatedDates[testContext.users.count / 2] expirationDate = selectedLastUpdatedDate.lastUpdated.addingTimeInterval(-Constants.offsetInterval) @@ -205,14 +189,14 @@ final class DeprecatedCacheModelV5Spec: QuickSpec { expect(testContext.keyedValueCacheMock.setReceivedArguments?.forKey) == DeprecatedCacheModelV5.CacheKeys.userEnvironments let recachedData = testContext.keyedValueCacheMock.setReceivedArguments?.value as? [String: Any] let expiredUserKeys = testContext.expiredUserKeys(for: expirationDate) - testContext.userKeys.forEach { (userKey) in + testContext.userKeys.forEach { userKey in expect(recachedData?.keys.contains(userKey)) == !expiredUserKeys.contains(userKey) } } } context("all modelV5 cached data expired") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let newestLastUpdatedDate = testContext.sortedLastUpdatedDates.last! expirationDate = newestLastUpdatedDate.lastUpdated.addingTimeInterval(Constants.offsetInterval) @@ -225,7 +209,7 @@ final class DeprecatedCacheModelV5Spec: QuickSpec { } context("no modelV5 cached data exists") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let newestLastUpdatedDate = testContext.sortedLastUpdatedDates.last! expirationDate = newestLastUpdatedDate.lastUpdated.addingTimeInterval(Constants.offsetInterval) testContext.keyedValueCacheMock.dictionaryReturnValue = nil //mock simulates no modelV5 cached data @@ -238,26 +222,17 @@ final class DeprecatedCacheModelV5Spec: QuickSpec { } } } - } // MARK: Expected value from conversion extension Dictionary where Key == LDFlagKey, Value == FeatureFlag { - var modelV5flagCollection: [LDFlagKey: FeatureFlag] { - return compactMapValues { (originalFeatureFlag) in - guard originalFeatureFlag.value != nil - else { - return nil - } - return originalFeatureFlag.modelV5FeatureFlag - } - } + var modelV5flagCollection: [LDFlagKey: FeatureFlag] { compactMapValues { $0.value != nil ? $0.modelV5FeatureFlag : nil } } } extension FeatureFlag { var modelV5FeatureFlag: FeatureFlag { - return FeatureFlag(flagKey: flagKey, value: value, variation: variation, version: version, flagVersion: flagVersion, eventTrackingContext: eventTrackingContext, reason: nil, trackReason: nil) + FeatureFlag(flagKey: flagKey, value: value, variation: variation, version: version, flagVersion: flagVersion, eventTrackingContext: eventTrackingContext, reason: nil, trackReason: nil) } } @@ -267,9 +242,7 @@ extension LDUser { func modelV5DictionaryValue(including featureFlags: [LDFlagKey: FeatureFlag], using lastUpdated: Date?) -> [String: Any] { var userDictionary = dictionaryValueWithAllAttributes(includeFlagConfig: false) userDictionary.setLastUpdated(lastUpdated) - userDictionary[LDUser.CodingKeys.config.rawValue] = featureFlags.compactMapValues { (featureFlag) in - return featureFlag.modelV5dictionaryValue - } + userDictionary[LDUser.CodingKeys.config.rawValue] = featureFlags.compactMapValues { $0.modelV5dictionaryValue } return userDictionary } diff --git a/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV6Spec.swift b/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV6Spec.swift index db778179..8fc4e2ef 100644 --- a/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV6Spec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/DeprecatedCacheModelV6Spec.swift @@ -2,7 +2,6 @@ // DeprecatedCacheModelV6Spec.swift // LaunchDarklyTests // -// Created by Joe Cieslik on 11/25/19. // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -12,11 +11,11 @@ import Nimble @testable import LaunchDarkly final class DeprecatedCacheModelV6Spec: QuickSpec { - + struct Constants { static let offsetInterval: TimeInterval = 0.1 } - + struct TestContext { var clientServiceFactoryMock = ClientServiceMockFactory() var keyedValueCacheMock: KeyedValueCachingMock @@ -26,55 +25,44 @@ final class DeprecatedCacheModelV6Spec: QuickSpec { var uncachedUser: LDUser var mobileKeys: [MobileKey] var uncachedMobileKey: MobileKey - var lastUpdatedDates: [UserKey: Date] { - return userEnvironmentsCollection.compactMapValues { (cacheableUserFlags) in - return cacheableUserFlags.lastUpdated - } - } var sortedLastUpdatedDates: [(userKey: UserKey, lastUpdated: Date)] { - return lastUpdatedDates.map { (userKey, lastUpdated) in - return (userKey, lastUpdated) - }.sorted { (tuple1, tuple2) in - return tuple1.lastUpdated.isEarlierThan(tuple2.lastUpdated) - } - } - var userKeys: [UserKey] { - return users.map { (user) in - return user.key + userEnvironmentsCollection.map { ($0, $1.lastUpdated) }.sorted { tuple1, tuple2 in + tuple1.lastUpdated.isEarlierThan(tuple2.lastUpdated) } } - + var userKeys: [UserKey] { users.map { $0.key } } + init(userCount: Int = 0) { keyedValueCacheMock = clientServiceFactoryMock.makeKeyedValueCache() as! KeyedValueCachingMock modelV6cache = DeprecatedCacheModelV6(keyedValueCache: keyedValueCacheMock) - + (users, userEnvironmentsCollection, mobileKeys) = CacheableUserEnvironmentFlags.stubCollection(userCount: userCount) - + uncachedUser = LDUser.stub() uncachedMobileKey = UUID().uuidString - + keyedValueCacheMock.dictionaryReturnValue = modelV6Dictionary(for: users, and: userEnvironmentsCollection, mobileKeys: mobileKeys) } - + func featureFlags(for userKey: UserKey, and mobileKey: MobileKey) -> [LDFlagKey: FeatureFlag]? { - return userEnvironmentsCollection[userKey]?.environmentFlags[mobileKey]?.featureFlags.modelV6flagCollection + userEnvironmentsCollection[userKey]?.environmentFlags[mobileKey]?.featureFlags.modelV6flagCollection } - + func modelV6Dictionary(for users: [LDUser], and userEnvironmentsCollection: [UserKey: CacheableUserEnvironmentFlags], mobileKeys: [MobileKey]) -> [UserKey: Any]? { guard !users.isEmpty else { return nil } - + var cacheDictionary = [UserKey: [String: Any]]() - users.forEach { (user) in + users.forEach { user in guard let userEnvironment = userEnvironmentsCollection[user.key] else { return } var environmentsDictionary = [MobileKey: Any]() let lastUpdated = userEnvironmentsCollection[user.key]?.lastUpdated - mobileKeys.forEach { (mobileKey) in + mobileKeys.forEach { mobileKey in guard let featureFlags = userEnvironment.environmentFlags[mobileKey]?.featureFlags else { return @@ -86,24 +74,20 @@ final class DeprecatedCacheModelV6Spec: QuickSpec { } return cacheDictionary } - + func expiredUserKeys(for expirationDate: Date) -> [UserKey] { - return sortedLastUpdatedDates.compactMap { (tuple) in - guard tuple.lastUpdated.isEarlierThan(expirationDate) - else { - return nil - } - return tuple.userKey + sortedLastUpdatedDates.compactMap { tuple in + tuple.lastUpdated.isEarlierThan(expirationDate) ? tuple.userKey : nil } } } - + override func spec() { initSpec() retrieveFlagsSpec() removeDataSpec() } - + private func initSpec() { var testContext: TestContext! describe("init") { @@ -117,7 +101,7 @@ final class DeprecatedCacheModelV6Spec: QuickSpec { } } } - + private func retrieveFlagsSpec() { var testContext: TestContext! var cachedData: (featureFlags: [LDFlagKey: FeatureFlag]?, lastUpdated: Date?)! @@ -125,7 +109,7 @@ final class DeprecatedCacheModelV6Spec: QuickSpec { context("when no cached data exists") { beforeEach { testContext = TestContext() - + cachedData = testContext.modelV6cache.retrieveFlags(for: testContext.uncachedUser.key, and: testContext.uncachedMobileKey) } it("returns nil values") { @@ -136,12 +120,12 @@ final class DeprecatedCacheModelV6Spec: QuickSpec { context("when cached data exists") { context("and a cached user is requested") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) } it("retrieves the cached data") { - testContext.users.forEach { (user) in + testContext.users.forEach { user in let expectedLastUpdated = testContext.userEnvironmentsCollection.lastUpdated(forKey: user.key)?.stringEquivalentDate - testContext.mobileKeys.forEach { (mobileKey) in + testContext.mobileKeys.forEach { mobileKey in let expectedFlags = testContext.featureFlags(for: user.key, and: mobileKey) cachedData = testContext.modelV6cache.retrieveFlags(for: user.key, and: mobileKey) expect(cachedData.featureFlags) == expectedFlags @@ -152,8 +136,8 @@ final class DeprecatedCacheModelV6Spec: QuickSpec { } context("and an uncached mobileKey is requested") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) - + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) + cachedData = testContext.modelV6cache.retrieveFlags(for: testContext.users.first!.key, and: testContext.uncachedMobileKey) } it("returns nil values") { @@ -163,8 +147,8 @@ final class DeprecatedCacheModelV6Spec: QuickSpec { } context("and an uncached user is requested") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) - + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) + cachedData = testContext.modelV6cache.retrieveFlags(for: testContext.uncachedUser.key, and: testContext.mobileKeys.first!) } it("returns nil values") { @@ -175,17 +159,17 @@ final class DeprecatedCacheModelV6Spec: QuickSpec { } } } - + private func removeDataSpec() { var testContext: TestContext! var expirationDate: Date! describe("removeData") { context("no modelV6 cached data expired") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let oldestLastUpdatedDate = testContext.sortedLastUpdatedDates.first! expirationDate = oldestLastUpdatedDate.lastUpdated.addingTimeInterval(-Constants.offsetInterval) - + testContext.modelV6cache.removeData(olderThan: expirationDate) } it("does not remove any modelV6 cached data") { @@ -194,10 +178,10 @@ final class DeprecatedCacheModelV6Spec: QuickSpec { } context("some modelV6 cached data expired") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let selectedLastUpdatedDate = testContext.sortedLastUpdatedDates[testContext.users.count / 2] expirationDate = selectedLastUpdatedDate.lastUpdated.addingTimeInterval(-Constants.offsetInterval) - + testContext.modelV6cache.removeData(olderThan: expirationDate) } it("removes expired modelV6 cached data") { @@ -205,17 +189,17 @@ final class DeprecatedCacheModelV6Spec: QuickSpec { expect(testContext.keyedValueCacheMock.setReceivedArguments?.forKey) == DeprecatedCacheModelV6.CacheKeys.userEnvironments let recachedData = testContext.keyedValueCacheMock.setReceivedArguments?.value as? [String: Any] let expiredUserKeys = testContext.expiredUserKeys(for: expirationDate) - testContext.userKeys.forEach { (userKey) in + testContext.userKeys.forEach { userKey in expect(recachedData?.keys.contains(userKey)) == !expiredUserKeys.contains(userKey) } } } context("all modelV6 cached data expired") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let newestLastUpdatedDate = testContext.sortedLastUpdatedDates.last! expirationDate = newestLastUpdatedDate.lastUpdated.addingTimeInterval(Constants.offsetInterval) - + testContext.modelV6cache.removeData(olderThan: expirationDate) } it("removes all modelV6 cached data") { @@ -225,11 +209,11 @@ final class DeprecatedCacheModelV6Spec: QuickSpec { } context("no modelV6 cached data exists") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) let newestLastUpdatedDate = testContext.sortedLastUpdatedDates.last! expirationDate = newestLastUpdatedDate.lastUpdated.addingTimeInterval(Constants.offsetInterval) testContext.keyedValueCacheMock.dictionaryReturnValue = nil //mock simulates no modelV6 cached data - + testContext.modelV6cache.removeData(olderThan: expirationDate) } it("makes no cached data changes") { @@ -238,39 +222,22 @@ final class DeprecatedCacheModelV6Spec: QuickSpec { } } } - } extension Dictionary where Key == LDFlagKey, Value == FeatureFlag { - var modelV6flagCollection: [LDFlagKey: FeatureFlag] { - return compactMapValues { (originalFeatureFlag) in - guard originalFeatureFlag.value != nil - else { - return nil - } - return originalFeatureFlag - } - } + var modelV6flagCollection: [LDFlagKey: FeatureFlag] { compactMapValues { $0.value != nil ? $0 : nil } } } extension LDUser { func modelV6DictionaryValue(including featureFlags: [LDFlagKey: FeatureFlag], using lastUpdated: Date?) -> [String: Any] { var userDictionary = dictionaryValueWithAllAttributes(includeFlagConfig: false) userDictionary.setLastUpdated(lastUpdated) - userDictionary[LDUser.CodingKeys.config.rawValue] = featureFlags.compactMapValues { (featureFlag) in - return featureFlag.modelV6dictionaryValue - } - + userDictionary[LDUser.CodingKeys.config.rawValue] = featureFlags.compactMapValues { $0.modelV6dictionaryValue } + return userDictionary } } extension FeatureFlag { - var modelV6dictionaryValue: [String: Any]? { - guard value != nil - else { - return nil - } - return dictionaryValue - } + var modelV6dictionaryValue: [String: Any]? { value != nil ? dictionaryValue : nil } } diff --git a/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/KeyedValueCacheSpec.swift b/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/KeyedValueCacheSpec.swift index 73bfe934..43f48e48 100644 --- a/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/KeyedValueCacheSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/KeyedValueCacheSpec.swift @@ -2,7 +2,6 @@ // KeyedValueCacheSpec.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 12/7/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -51,11 +50,11 @@ final class KeyedValueCacheSpec: QuickSpec { } it("retrieves matching flags") { expect(retrievedUserEnvironmentCollectionDictionary).toNot(beNil()) - let retrievedUserEnvironmentsCollection = retrievedUserEnvironmentCollectionDictionary?.compactMapValues { (retrievedObject) in - return CacheableUserEnvironmentFlags(object: retrievedObject) + let retrievedUserEnvironmentsCollection = retrievedUserEnvironmentCollectionDictionary?.compactMapValues { retrievedObject in + CacheableUserEnvironmentFlags(object: retrievedObject) } expect(retrievedUserEnvironmentsCollection).toNot(beNil()) - testContext.userEnvironmentFlagsCollection.keys.forEach { (userKey) in + testContext.userEnvironmentFlagsCollection.keys.forEach { userKey in let cacheableUserEnvironments = testContext.userEnvironmentFlagsCollection[userKey]! let retrievedCacheableUserEnvironments = retrievedUserEnvironmentsCollection?[userKey] expect(retrievedCacheableUserEnvironments?.userKey) == userKey diff --git a/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/UserEnvironmentFlagCacheSpec.swift b/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/UserEnvironmentFlagCacheSpec.swift index 0c728f31..500677b5 100644 --- a/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/UserEnvironmentFlagCacheSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Service Objects/Cache/UserEnvironmentFlagCacheSpec.swift @@ -53,8 +53,8 @@ final class UserEnvironmentFlagCacheSpec: QuickSpec { }.first! } - init(userCount: Int = 1) { - userEnvironmentFlagCache = UserEnvironmentFlagCache(withKeyedValueCache: keyedValueCacheMock) + init(userCount: Int = 1, maxUsers: Int = 5) { + userEnvironmentFlagCache = UserEnvironmentFlagCache(withKeyedValueCache: keyedValueCacheMock, maxCachedUsers: maxUsers) let mobileKeys: [MobileKey] (users, userEnvironmentsCollection, mobileKeys) = CacheableUserEnvironmentFlags.stubCollection(userCount: userCount) self.mobileKeys.formUnion(Set(mobileKeys)) @@ -96,7 +96,9 @@ final class UserEnvironmentFlagCacheSpec: QuickSpec { override func spec() { initSpec() retrieveFeatureFlagsSpec() - storeFeatureFlagsSpec() + storeFeatureFlagsSpec(maxUsers: LDConfig.Defaults.maxCachedUsers) + storeFeatureFlagsSpec(maxUsers: 3) + storeUnlimitedUsersSpec(maxUsers: -1) } private func initSpec() { @@ -135,7 +137,7 @@ final class UserEnvironmentFlagCacheSpec: QuickSpec { context("the user is stored") { context("and the environment is stored") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) retrievingUser = testContext.selectedUser retrievingMobileKey = testContext.selectedMobileKey @@ -147,7 +149,7 @@ final class UserEnvironmentFlagCacheSpec: QuickSpec { } context("and the environment is not stored") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) retrievingUser = testContext.selectedUser retrievingMobileKey = (CacheableUserEnvironmentFlags.Constants.environmentCount + 1).mobileKey @@ -160,7 +162,7 @@ final class UserEnvironmentFlagCacheSpec: QuickSpec { } context("the user is not stored") { beforeEach { - testContext = TestContext(userCount: UserEnvironmentFlagCache.Constants.maxCachedUsers) + testContext = TestContext(userCount: LDConfig.Defaults.maxCachedUsers) retrievingUser = LDUser.stub() retrievingMobileKey = testContext.selectedMobileKey @@ -173,8 +175,80 @@ final class UserEnvironmentFlagCacheSpec: QuickSpec { } } } + + private func storeUnlimitedUsersSpec(maxUsers: Int) { + var testContext: TestContext! + var storingUser: LDUser! + var storingMobileKey: String! + var storingLastUpdated: Date! + var userCount: Int! + var newFeatureFlags: [LDFlagKey: FeatureFlag]! + + context("and an existing user adds a new environment") { + beforeEach { + userCount = 5 + testContext = TestContext(userCount: userCount, maxUsers: maxUsers) + storingUser = testContext.selectedUser + storingMobileKey = (CacheableUserEnvironmentFlags.Constants.environmentCount + 1).mobileKey + newFeatureFlags = [Constants.newFlagKey: FeatureFlag.stub(flagKey: Constants.newFlagKey, flagValue: Constants.newFlagValue)] + storingUser.flagStore = FlagMaintainingMock(flags: newFeatureFlags) + storingLastUpdated = Date() + } + it("stores the users flags") { + FlagCachingStoreMode.allCases.forEach { (storeMode) in + testContext.storeFlags(newFeatureFlags, + forUser: storingUser, + andMobileKey: storingMobileKey, + lastUpdated: storingLastUpdated, + storeMode: storeMode) + + expect(testContext.keyedValueCacheMock.setReceivedArguments?.forKey) == UserEnvironmentFlagCache.CacheKeys.cachedUserEnvironmentFlags + + let setCachedUserEnvironmentsCollection = testContext.keyedValueCacheMock.setReceivedArguments?.value as? [UserKey: [String: Any]] + expect(setCachedUserEnvironmentsCollection?.count) == userCount + testContext.users.forEach { (user) in + expect(setCachedUserEnvironmentsCollection?.keys.contains(user.key)) == true + + let setCachedUserEnvironments = setCachedUserEnvironmentsCollection?[user.key] + expect(setCachedUserEnvironments?.userKey) == user.key + if user.key == storingUser.key { + expect(setCachedUserEnvironments?.cacheableLastUpdated) == storingLastUpdated.stringEquivalentDate + } else { + expect(setCachedUserEnvironments?.cacheableLastUpdated) == testContext.userEnvironmentsCollection.lastUpdated(forKey: user.key)?.stringEquivalentDate + } + + let setCachedEnvironmentFlagsCollection = setCachedUserEnvironments?.environmentFlags + if user.key == storingUser.key { + expect(setCachedEnvironmentFlagsCollection?.count) == CacheableUserEnvironmentFlags.Constants.environmentCount + 1 + } else { + expect(setCachedEnvironmentFlagsCollection?.count) == CacheableUserEnvironmentFlags.Constants.environmentCount + } + + var mobileKeys = [MobileKey](testContext.mobileKeys) + mobileKeys.append(storingMobileKey) + mobileKeys.forEach { (mobileKey) in + guard mobileKey != storingMobileKey || user.key == storingUser.key + else { + return + } + expect(setCachedEnvironmentFlagsCollection?.keys.contains(mobileKey)) == true + + let setCachedEnvironmentFlags = setCachedEnvironmentFlagsCollection?[mobileKey] + expect(setCachedEnvironmentFlags?.userKey) == user.key + expect(setCachedEnvironmentFlags?.mobileKey) == mobileKey + if user.key == storingUser.key && mobileKey == storingMobileKey { + expect(setCachedEnvironmentFlags?.featureFlags) == newFeatureFlags + } else { + expect(setCachedEnvironmentFlags?.featureFlags) == testContext.featureFlags(forUserKey: user.key, andMobileKey: mobileKey) + } + } + } + } + } + } + } - private func storeFeatureFlagsSpec() { + private func storeFeatureFlagsSpec(maxUsers: Int) { var testContext: TestContext! var storingUser: LDUser! var storingMobileKey: String! @@ -186,7 +260,7 @@ final class UserEnvironmentFlagCacheSpec: QuickSpec { context("when no user flags are stored") { beforeEach { userCount = 0 - testContext = TestContext(userCount: userCount) + testContext = TestContext(userCount: userCount, maxUsers: maxUsers) storingUser = LDUser.stub() storingLastUpdated = Date().addingTimeInterval(TimeInterval(days: -1)) storingMobileKey = UUID().uuidString @@ -223,8 +297,8 @@ final class UserEnvironmentFlagCacheSpec: QuickSpec { context("when less than the max number of users flags are stored") { context("and an existing users flags are changed") { beforeEach { - userCount = UserEnvironmentFlagCache.Constants.maxCachedUsers - 1 - testContext = TestContext(userCount: userCount) + userCount = maxUsers - 1 + testContext = TestContext(userCount: userCount, maxUsers: maxUsers) storingUser = testContext.selectedUser storingMobileKey = testContext.selectedMobileKey newFeatureFlags = [Constants.newFlagKey: FeatureFlag.stub(flagKey: Constants.newFlagKey, flagValue: Constants.newFlagValue)] @@ -275,8 +349,8 @@ final class UserEnvironmentFlagCacheSpec: QuickSpec { } context("and an existing user adds a new environment") { beforeEach { - userCount = UserEnvironmentFlagCache.Constants.maxCachedUsers - 1 - testContext = TestContext(userCount: userCount) + userCount = maxUsers - 1 + testContext = TestContext(userCount: userCount, maxUsers: maxUsers) storingUser = testContext.selectedUser storingMobileKey = (CacheableUserEnvironmentFlags.Constants.environmentCount + 1).mobileKey newFeatureFlags = [Constants.newFlagKey: FeatureFlag.stub(flagKey: Constants.newFlagKey, flagValue: Constants.newFlagValue)] @@ -337,8 +411,8 @@ final class UserEnvironmentFlagCacheSpec: QuickSpec { } context("and a new users flags are stored") { beforeEach { - userCount = UserEnvironmentFlagCache.Constants.maxCachedUsers - 1 - testContext = TestContext(userCount: userCount) + userCount = maxUsers - 1 + testContext = TestContext(userCount: userCount, maxUsers: maxUsers) storingUser = LDUser.stub(key: (userCount + 1).userKey) storingMobileKey = 1.mobileKey newFeatureFlags = [Constants.newFlagKey: FeatureFlag.stub(flagKey: Constants.newFlagKey, flagValue: Constants.newFlagValue)] @@ -398,8 +472,8 @@ final class UserEnvironmentFlagCacheSpec: QuickSpec { context("when max number of users flags are stored") { context("and an existing users flags are changed") { beforeEach { - userCount = UserEnvironmentFlagCache.Constants.maxCachedUsers - testContext = TestContext(userCount: userCount) + userCount = maxUsers + testContext = TestContext(userCount: userCount, maxUsers: maxUsers) storingUser = testContext.selectedUser storingMobileKey = testContext.selectedMobileKey newFeatureFlags = [Constants.newFlagKey: FeatureFlag.stub(flagKey: Constants.newFlagKey, flagValue: Constants.newFlagValue)] @@ -450,8 +524,8 @@ final class UserEnvironmentFlagCacheSpec: QuickSpec { } context("and an existing user adds a new environment") { beforeEach { - userCount = UserEnvironmentFlagCache.Constants.maxCachedUsers - testContext = TestContext(userCount: userCount) + userCount = maxUsers + testContext = TestContext(userCount: userCount, maxUsers: maxUsers) storingUser = testContext.selectedUser storingMobileKey = (CacheableUserEnvironmentFlags.Constants.environmentCount + 1).mobileKey newFeatureFlags = [Constants.newFlagKey: FeatureFlag.stub(flagKey: Constants.newFlagKey, flagValue: Constants.newFlagValue)] @@ -512,8 +586,8 @@ final class UserEnvironmentFlagCacheSpec: QuickSpec { } context("and a new users flags are stored") { beforeEach { - userCount = UserEnvironmentFlagCache.Constants.maxCachedUsers - testContext = TestContext(userCount: userCount) + userCount = maxUsers + testContext = TestContext(userCount: userCount, maxUsers: maxUsers) storingUser = LDUser.stub(key: (userCount + 1).userKey) storingMobileKey = 1.mobileKey newFeatureFlags = [Constants.newFlagKey: FeatureFlag.stub(flagKey: Constants.newFlagKey, flagValue: Constants.newFlagValue)] diff --git a/LaunchDarkly/LaunchDarklyTests/Service Objects/EnvironmentReporterSpec.swift b/LaunchDarkly/LaunchDarklyTests/Service Objects/EnvironmentReporterSpec.swift index 6889e584..14421b6c 100644 --- a/LaunchDarkly/LaunchDarklyTests/Service Objects/EnvironmentReporterSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Service Objects/EnvironmentReporterSpec.swift @@ -2,7 +2,6 @@ // EnvironmentReporterSpec.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 1/10/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // diff --git a/LaunchDarkly/LaunchDarklyTests/Service Objects/ErrorNotifierSpec.swift b/LaunchDarkly/LaunchDarklyTests/Service Objects/ErrorNotifierSpec.swift index 0337b997..50d7df75 100644 --- a/LaunchDarkly/LaunchDarklyTests/Service Objects/ErrorNotifierSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Service Objects/ErrorNotifierSpec.swift @@ -2,7 +2,6 @@ // ErrorNotifyingSpec.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 2/18/19. // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -38,7 +37,7 @@ final class ErrorNotifierSpec: QuickSpec { init(observerCount: Int) { self.init() - errorObservers.append(contentsOf: ErrorObserver.createObservers(count: observerCount)) + errorObservers = ErrorObserver.createObservers(count: observerCount) errorNotifier = ErrorNotifier(observers: errorObservers) originalObserverCount = errorObservers.count observersPerOwner = observerCount @@ -46,15 +45,13 @@ final class ErrorNotifierSpec: QuickSpec { init(ownerCount: Int, observersPerOwner: Int) { self.init() - var owners = [ErrorOwnerMock]() - while owners.count < ownerCount { - owners.append(ErrorOwnerMock()) - } - for _ in 1...observersPerOwner { - owners.forEach { (owner) in - errorObservers.append(ErrorObserver(owner: owner, errorHandler: owner.handle)) - } + + // create ownerCount owners, then observersPerOwner observers for each owner + let owners = (0.. [ErrorObserver] { - return errorObservers.filter { (observer) in - return observer.owner === owner - } + errorObservers.filter { $0.owner === owner } } } diff --git a/LaunchDarkly/LaunchDarklyTests/Service Objects/EventReporterSpec.swift b/LaunchDarkly/LaunchDarklyTests/Service Objects/EventReporterSpec.swift index fb7078be..5b6e5f86 100644 --- a/LaunchDarkly/LaunchDarklyTests/Service Objects/EventReporterSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Service Objects/EventReporterSpec.swift @@ -2,7 +2,6 @@ // EventReporterSpec.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 9/26/17. // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -17,23 +16,15 @@ final class EventReporterSpec: QuickSpec { static let eventFlushIntervalHalfSecond: TimeInterval = 0.5 static let defaultValue = false } - + struct TestContext { var eventReporter: EventReporter! var config: LDConfig! var user: LDUser! var serviceMock: DarklyServiceMock! var events: [Event]! - var eventKeys: [String]! { - return events.compactMap { (event) in - event.key - } - } - var eventKinds: [Event.Kind]! { - return events.compactMap { (event) in - event.kind - } - } + var eventKeys: [String]! { events.compactMap { $0.key } } + var eventKinds: [Event.Kind]! { events.compactMap { $0.kind } } var lastEventResponseDate: Date? var flagKey: LDFlagKey! var eventTrackingContext: EventTrackingContext! @@ -42,9 +33,7 @@ final class EventReporterSpec: QuickSpec { var featureFlagWithReasonAndTrackReason: FeatureFlag! var eventStubResponseDate: Date? var flagRequestTracker: FlagRequestTracker? - var reportersTracker: FlagRequestTracker? { - return eventReporter.flagRequestTracker - } + var reportersTracker: FlagRequestTracker? { eventReporter.flagRequestTracker } var flagRequestCount: Int var syncResult: EventSyncResult? = nil @@ -71,11 +60,7 @@ final class EventReporterSpec: QuickSpec { serviceMock = DarklyServiceMock() serviceMock.stubEventResponse(success: stubResponseSuccess, responseOnly: stubResponseOnly, errorOnly: stubResponseErrorOnly, responseDate: self.eventStubResponseDate) - events = [] - while events.count < eventCount { - let event = Event.stub(Event.eventKind(for: events.count), with: user) - events.append(event) - } + events = (0.. FlagCounter? { - return reportersTracker?.flagCounters[key] + reportersTracker?.flagCounters[key] } func flagValueCounter(for key: LDFlagKey, and featureFlag: FeatureFlag?) -> FlagValueCounter? { - return flagCounter(for: key)?.flagValueCounters.flagValueCounter(for: featureFlag) + flagCounter(for: key)?.flagValueCounters.flagValueCounter(for: featureFlag) } } - + override func spec() { initSpec() isOnlineSpec() @@ -155,9 +137,7 @@ final class EventReporterSpec: QuickSpec { beforeEach { testContext = TestContext() - testContext.eventReporter = EventReporter(config: testContext.config, service: testContext.serviceMock) { (_) in - - } + testContext.eventReporter = EventReporter(config: testContext.config, service: testContext.serviceMock) { _ in } } it("starts offline without reporting events") { expect(testContext.eventReporter.config) == testContext.config @@ -335,7 +315,7 @@ final class EventReporterSpec: QuickSpec { beforeEach { //The EventReporter will try to report events if it's started online with events. By starting online without events, then adding them, we "beat the timer" by reporting them right away waitUntil { syncComplete in - testContext = TestContext(eventStubResponseDate: eventStubResponseDate, onSyncComplete: { (result) in + testContext = TestContext(eventStubResponseDate: eventStubResponseDate, onSyncComplete: { result in testContext.syncResult = result syncComplete() }) @@ -364,7 +344,7 @@ final class EventReporterSpec: QuickSpec { beforeEach { //The EventReporter will try to report events if it's started online with events. By starting online without events, then adding them, we "beat the timer" by reporting them right away waitUntil { syncComplete in - testContext = TestContext(eventStubResponseDate: eventStubResponseDate, onSyncComplete: { (result) in + testContext = TestContext(eventStubResponseDate: eventStubResponseDate, onSyncComplete: { result in testContext.syncResult = result syncComplete() }) @@ -391,7 +371,7 @@ final class EventReporterSpec: QuickSpec { beforeEach { //The EventReporter will try to report events if it's started online with events. By starting online without events, then adding them, we "beat the timer" by reporting them right away waitUntil { syncComplete in - testContext = TestContext(eventStubResponseDate: eventStubResponseDate, onSyncComplete: { (result) in + testContext = TestContext(eventStubResponseDate: eventStubResponseDate, onSyncComplete: { result in testContext.syncResult = result syncComplete() }) @@ -417,7 +397,7 @@ final class EventReporterSpec: QuickSpec { context("without events or tracked requests") { beforeEach { waitUntil { syncComplete in - testContext = TestContext(eventStubResponseDate: eventStubResponseDate, onSyncComplete: { (result) in + testContext = TestContext(eventStubResponseDate: eventStubResponseDate, onSyncComplete: { result in testContext.syncResult = result syncComplete() }) @@ -441,7 +421,7 @@ final class EventReporterSpec: QuickSpec { context("server error") { beforeEach { waitUntil(timeout: 10) { syncComplete in - testContext = TestContext(stubResponseSuccess: false, eventStubResponseDate: eventStubResponseDate, onSyncComplete: { (result) in + testContext = TestContext(stubResponseSuccess: false, eventStubResponseDate: eventStubResponseDate, onSyncComplete: { result in testContext.syncResult = result syncComplete() }) @@ -469,7 +449,7 @@ final class EventReporterSpec: QuickSpec { context("response only") { beforeEach { waitUntil { syncComplete in - testContext = TestContext(stubResponseSuccess: false, stubResponseOnly: true, eventStubResponseDate: eventStubResponseDate, onSyncComplete: { (result) in + testContext = TestContext(stubResponseSuccess: false, stubResponseOnly: true, eventStubResponseDate: eventStubResponseDate, onSyncComplete: { result in testContext.syncResult = result syncComplete() }) @@ -497,7 +477,7 @@ final class EventReporterSpec: QuickSpec { context("error only") { beforeEach { waitUntil(timeout: 10) { syncComplete in - testContext = TestContext(stubResponseSuccess: false, stubResponseErrorOnly: true, eventStubResponseDate: eventStubResponseDate, onSyncComplete: { (result) in + testContext = TestContext(stubResponseSuccess: false, stubResponseErrorOnly: true, eventStubResponseDate: eventStubResponseDate, onSyncComplete: { result in testContext.syncResult = result syncComplete() }) @@ -527,7 +507,7 @@ final class EventReporterSpec: QuickSpec { context("offline") { beforeEach { waitUntil { syncComplete in - testContext = TestContext(eventStubResponseDate: eventStubResponseDate, onSyncComplete: { (result) in + testContext = TestContext(eventStubResponseDate: eventStubResponseDate, onSyncComplete: { result in testContext.syncResult = result syncComplete() }) @@ -593,7 +573,7 @@ final class EventReporterSpec: QuickSpec { context("when a reason is present and reason is false but trackReason is true") { beforeEach { testContext = TestContext(trackEvents: true) - + waitUntil { done in testContext.eventReporter.recordFlagEvaluationEvents(flagKey: testContext.flagKey, value: testContext.featureFlag.value!, @@ -744,7 +724,7 @@ final class EventReporterSpec: QuickSpec { } it("records a feature and debug event") { expect(testContext.eventReporter.eventStore.count == 2).to(beTrue()) - expect(testContext.eventReporter.eventStoreKeys.filter { (eventKey) in + expect(testContext.eventReporter.eventStoreKeys.filter { eventKey in eventKey == testContext.flagKey }.count == 2).to(beTrue()) expect(Set(testContext.eventReporter.eventStore.eventKinds)).to(equal(Set([.feature, .debug]))) @@ -761,16 +741,16 @@ final class EventReporterSpec: QuickSpec { context("when both trackEvents is true, debugEventsUntilDate is later than lastEventResponseDate, reason is false, and track reason is true") { beforeEach { testContext = TestContext(lastEventResponseDate: Date(), trackEvents: true, debugEventsUntilDate: Date().addingTimeInterval(TimeInterval.oneSecond)) - + waitUntil { done in testContext.eventReporter.recordFlagEvaluationEvents(flagKey: testContext.flagKey, value: testContext.featureFlag.value!, defaultValue: Constants.defaultValue, featureFlag: testContext.featureFlagWithReasonAndTrackReason, user: testContext.user, includeReason: false, completion: done) } } it("records a feature and debug event") { expect(testContext.eventReporter.eventStore.count == 2).to(beTrue()) - expect(testContext.eventReporter.eventStore[0]["reason"] as? Dictionary == DarklyServiceMock.Constants.reason).to(beTrue()) - expect(testContext.eventReporter.eventStore[1]["reason"] as? Dictionary == DarklyServiceMock.Constants.reason).to(beTrue()) - expect(testContext.eventReporter.eventStoreKeys.filter { (eventKey) in + expect(testContext.eventReporter.eventStore[0]["reason"] as? [String: Any] == DarklyServiceMock.Constants.reason).to(beTrue()) + expect(testContext.eventReporter.eventStore[1]["reason"] as? [String: Any] == DarklyServiceMock.Constants.reason).to(beTrue()) + expect(testContext.eventReporter.eventStoreKeys.filter { eventKey in eventKey == testContext.flagKey }.count == 2).to(beTrue()) expect(Set(testContext.eventReporter.eventStore.eventKinds)).to(equal(Set([.feature, .debug]))) @@ -1010,7 +990,7 @@ final class EventReporterSpec: QuickSpec { testContext.eventReporter.isOnline = true } it("doesn't report events") { - waitUntil { (done) in + waitUntil { done in DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Constants.eventFlushIntervalHalfSecond) { expect(testContext.serviceMock.publishEventDictionariesCallCount) == 0 done() @@ -1034,7 +1014,7 @@ final class EventReporterSpec: QuickSpec { it("creates a list of keys that match the event keys") { expect(eventKeys.isEmpty).to(beFalse()) expect(eventKeys.components(separatedBy: ", ").count) == Event.Kind.allKinds.count - 1 //summary events don't have a key - testContext.eventKeys.forEach { (eventKey) in + testContext.eventKeys.forEach { eventKey in expect(eventKeys.contains(eventKey)).to(beTrue()) } } @@ -1054,16 +1034,8 @@ final class EventReporterSpec: QuickSpec { } extension EventReporter { - var eventStoreKeys: [String] { - return eventStore.compactMap { (eventDictionary) in - return eventDictionary.eventKey - } - } - var eventStoreKinds: [Event.Kind] { - return eventStore.compactMap { (eventDictionary) in - return eventDictionary.eventKind - } - } + var eventStoreKeys: [String] { eventStore.compactMap { $0.eventKey } } + var eventStoreKinds: [Event.Kind] { eventStore.compactMap { $0.eventKind } } } extension TimeInterval { @@ -1080,42 +1052,26 @@ private extension Date { extension Event.Kind { static var nonSummaryKinds: [Event.Kind] { - return [feature, debug, identify, custom] + [feature, debug, identify, custom] } } extension EventSyncResult: Equatable { public static func == (_ lhs: EventSyncResult, _ rhs: EventSyncResult) -> Bool { switch (lhs, rhs) { - case (.success(let left), .success(let right)): + case let (.success(left), .success(right)): return left == right - case (.error(let left), .error(let right)): + case let (.error(left), .error(right)): return left == right default: return false } } } -extension Optional where Wrapped == EventSyncResult { - public static func == (_ lhs: EventSyncResult?, _ rhs: EventSyncResult?) -> Bool { - switch (lhs, rhs) { - case (.some(let left), .some(let right)): - return left == right - case (.none, .none): - return true - default: return false - } - } -} - +// Performs set-wise equality, without ordering extension Array where Element == [String: Any] { static func == (_ lhs: [[String: Any]], _ rhs: [[String: Any]]) -> Bool { - guard lhs.count == rhs.count - else { - return false - } - return lhs.filter { (leftEvent) in - !rhs.contains(leftEvent) - }.isEmpty + // Same length and the left hand side does not contain any elements not in the right hand side + return lhs.count == rhs.count && !lhs.contains { !rhs.contains($0) } } } diff --git a/LaunchDarkly/LaunchDarklyTests/Service Objects/FlagChangeNotifierSpec.swift b/LaunchDarkly/LaunchDarklyTests/Service Objects/FlagChangeNotifierSpec.swift index fa8663a3..3d2a3353 100644 --- a/LaunchDarkly/LaunchDarklyTests/Service Objects/FlagChangeNotifierSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Service Objects/FlagChangeNotifierSpec.swift @@ -2,7 +2,6 @@ // FlagChangeNotifierSpec.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 12/14/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -29,7 +28,7 @@ final class FlagChangeNotifierSpec: QuickSpec { var featureFlags: [LDFlagKey: FeatureFlag] = DarklyServiceMock.Constants.stubFeatureFlags() var user: LDUser = LDUser.stub(key: Constants.userKey, includeNullValue: true) var flagStoreMock: FlagMaintainingMock! { - return (user.flagStore as! FlagMaintainingMock) + (user.flagStore as! FlagMaintainingMock) } let alternateFlagKeys = ["flag-key-1", "flag-key-2", "flag-key-3"] @@ -42,7 +41,7 @@ final class FlagChangeNotifierSpec: QuickSpec { } var flagChangeObservers = [FlagChangeObserver]() while flagChangeObservers.count < observerCount { - if observerType == .singleKey || (observerType == .any && flagChangeObservers.count % 2 == 0) { + if observerType == .singleKey || (observerType == .any && flagChangeObservers.count.isMultiple(of: 2)) { flagChangeObservers.append(FlagChangeObserver(key: DarklyServiceMock.FlagKeys.bool, owner: stubOwner(key: DarklyServiceMock.FlagKeys.bool), flagChangeHandler: flagChangeHandler)) @@ -59,7 +58,7 @@ final class FlagChangeNotifierSpec: QuickSpec { var flagsUnchangedObservers = [FlagsUnchangedObserver]() //use the flag change observer owners to own the flagsUnchangedObservers - flagChangeObservers.forEach { (flagChangeObserver) in + flagChangeObservers.forEach { flagChangeObserver in flagsUnchangedObservers.append(FlagsUnchangedObserver(owner: flagChangeObserver.owner!, flagsUnchangedHandler: flagsUnchangedHandler)) } subject = FlagChangeNotifier(flagChangeObservers: flagChangeObservers, flagsUnchangedObservers: flagsUnchangedObservers) @@ -75,7 +74,7 @@ final class FlagChangeNotifierSpec: QuickSpec { return } var flagChangeObservers = [FlagChangeObserver]() - keys.forEach { (key) in + keys.forEach { key in flagChangeObservers.append(FlagChangeObserver(key: key, owner: self.stubOwner(key: key), flagChangeHandler: flagChangeHandler)) @@ -83,7 +82,7 @@ final class FlagChangeNotifierSpec: QuickSpec { flagsUnchangedOwnerKey = flagChangeObservers.first!.flagKeys.observerKey var flagsUnchangedObservers = [FlagsUnchangedObserver]() //use the flag change observer owners to own the flagsUnchangedObservers - flagChangeObservers.forEach { (flagChangeObserver) in + flagChangeObservers.forEach { flagChangeObserver in flagsUnchangedObservers.append(FlagsUnchangedObserver(owner: flagChangeObserver.owner!, flagsUnchangedHandler: flagsUnchangedHandler)) } subject = FlagChangeNotifier(flagChangeObservers: flagChangeObservers, flagsUnchangedObservers: flagsUnchangedObservers) @@ -120,7 +119,7 @@ final class FlagChangeNotifierSpec: QuickSpec { } mutating func stubOwner(keys: [String]) -> FlagChangeHandlerOwnerMock { - return stubOwner(key: keys.observerKey) + stubOwner(key: keys.observerKey) } //Flag change handler stubs @@ -464,10 +463,10 @@ final class FlagChangeNotifierSpec: QuickSpec { context("that are active") { context("and different flags") { it("activates the change handler") { - DarklyServiceMock.FlagKeys.flagsWithAnAlternateValue.forEach { (key) in + DarklyServiceMock.FlagKeys.flagsWithAnAlternateValue.forEach { key in testContext = TestContext( keys: DarklyServiceMock.FlagKeys.knownFlags, - flagChangeHandler: { (changedFlag) in + flagChangeHandler: { changedFlag in testContext.flagChangeHandlerCallCount += 1 testContext.changedFlag = changedFlag }, @@ -492,10 +491,10 @@ final class FlagChangeNotifierSpec: QuickSpec { } } it("activates the change handler when the value changes but not the variation number") { - DarklyServiceMock.FlagKeys.flagsWithAnAlternateValue.forEach { (key) in + DarklyServiceMock.FlagKeys.flagsWithAnAlternateValue.forEach { key in testContext = TestContext( keys: DarklyServiceMock.FlagKeys.knownFlags, - flagChangeHandler: { (changedFlag) in + flagChangeHandler: { changedFlag in testContext.flagChangeHandlerCallCount += 1 testContext.changedFlag = changedFlag }, @@ -524,7 +523,7 @@ final class FlagChangeNotifierSpec: QuickSpec { beforeEach { testContext = TestContext( keys: DarklyServiceMock.FlagKeys.knownFlags, - flagChangeHandler: { (_) in + flagChangeHandler: { _ in testContext.flagChangeHandlerCallCount += 1 }, flagsUnchangedHandler: { @@ -545,10 +544,10 @@ final class FlagChangeNotifierSpec: QuickSpec { context("that are inactive") { context("and different flags") { it("does nothing") { - DarklyServiceMock.FlagKeys.flagsWithAnAlternateValue.forEach { (key) in + DarklyServiceMock.FlagKeys.flagsWithAnAlternateValue.forEach { key in testContext = TestContext( keys: DarklyServiceMock.FlagKeys.knownFlags, - flagChangeHandler: { (changedFlag) in + flagChangeHandler: { changedFlag in testContext.flagChangeHandlerCallCount += 1 testContext.changedFlag = changedFlag }, @@ -571,7 +570,7 @@ final class FlagChangeNotifierSpec: QuickSpec { beforeEach { testContext = TestContext( keys: DarklyServiceMock.FlagKeys.knownFlags, - flagChangeHandler: { (_) in + flagChangeHandler: { _ in testContext.flagChangeHandlerCallCount += 1 }, flagsUnchangedHandler: { @@ -602,10 +601,10 @@ final class FlagChangeNotifierSpec: QuickSpec { context("that are active") { context("and different single flags") { it("activates the change handler") { - DarklyServiceMock.FlagKeys.flagsWithAnAlternateValue.forEach { (key) in + DarklyServiceMock.FlagKeys.flagsWithAnAlternateValue.forEach { key in testContext = TestContext( keys: DarklyServiceMock.FlagKeys.knownFlags, - flagCollectionChangeHandler: { (changedFlags) in + flagCollectionChangeHandler: { changedFlags in testContext.flagCollectionChangeHandlerCallCount += 1 testContext.changedFlags = changedFlags }, @@ -629,7 +628,7 @@ final class FlagChangeNotifierSpec: QuickSpec { beforeEach { testContext = TestContext( keys: DarklyServiceMock.FlagKeys.knownFlags, - flagCollectionChangeHandler: { (changedFlags) in + flagCollectionChangeHandler: { changedFlags in testContext.flagCollectionChangeHandlerCallCount += 1 testContext.changedFlags = changedFlags }, @@ -638,8 +637,8 @@ final class FlagChangeNotifierSpec: QuickSpec { }) let changedFlagKeys = [DarklyServiceMock.FlagKeys.bool, DarklyServiceMock.FlagKeys.int, DarklyServiceMock.FlagKeys.double] oldFlags = DarklyServiceMock.Constants.stubFeatureFlags(alternateValuesForKeys: changedFlagKeys) - targetChangedFlags = [LDFlagKey: LDChangedFlag](uniqueKeysWithValues: changedFlagKeys.map { (flagKey) in - return (flagKey, LDChangedFlag.stub(key: flagKey, oldFlags: oldFlags, newFlags: testContext.user.flagStore.featureFlags)) + targetChangedFlags = [LDFlagKey: LDChangedFlag](uniqueKeysWithValues: changedFlagKeys.map { flagKey in + (flagKey, LDChangedFlag.stub(key: flagKey, oldFlags: oldFlags, newFlags: testContext.user.flagStore.featureFlags)) }) waitUntil { done in @@ -656,7 +655,7 @@ final class FlagChangeNotifierSpec: QuickSpec { beforeEach { testContext = TestContext( keys: DarklyServiceMock.FlagKeys.knownFlags, - flagCollectionChangeHandler: { (changedFlags) in + flagCollectionChangeHandler: { changedFlags in testContext.flagCollectionChangeHandlerCallCount += 1 testContext.changedFlags = changedFlags }, @@ -680,7 +679,7 @@ final class FlagChangeNotifierSpec: QuickSpec { beforeEach { testContext = TestContext( keys: DarklyServiceMock.FlagKeys.knownFlags, - flagCollectionChangeHandler: { (changedFlags) in + flagCollectionChangeHandler: { changedFlags in testContext.flagCollectionChangeHandlerCallCount += 1 testContext.changedFlags = changedFlags }, @@ -704,7 +703,7 @@ final class FlagChangeNotifierSpec: QuickSpec { beforeEach { testContext = TestContext( keys: DarklyServiceMock.FlagKeys.knownFlags, - flagCollectionChangeHandler: { (changedFlags) in + flagCollectionChangeHandler: { changedFlags in testContext.flagCollectionChangeHandlerCallCount += 1 testContext.changedFlags = changedFlags }, @@ -736,10 +735,10 @@ final class FlagChangeNotifierSpec: QuickSpec { context("that are active") { context("and different single flags") { it("activates the change handler") { - DarklyServiceMock.FlagKeys.flagsWithAnAlternateValue.forEach { (key) in + DarklyServiceMock.FlagKeys.flagsWithAnAlternateValue.forEach { key in testContext = TestContext( keys: LDFlagKey.anyKey, - flagCollectionChangeHandler: { (changedFlags) in + flagCollectionChangeHandler: { changedFlags in testContext.flagCollectionChangeHandlerCallCount += 1 testContext.changedFlags = changedFlags }, @@ -766,7 +765,7 @@ final class FlagChangeNotifierSpec: QuickSpec { beforeEach { testContext = TestContext( keys: LDFlagKey.anyKey, - flagCollectionChangeHandler: { (changedFlags) in + flagCollectionChangeHandler: { changedFlags in testContext.flagCollectionChangeHandlerCallCount += 1 testContext.changedFlags = changedFlags }, @@ -775,8 +774,8 @@ final class FlagChangeNotifierSpec: QuickSpec { }) let changedFlagKeys = [DarklyServiceMock.FlagKeys.bool, DarklyServiceMock.FlagKeys.int, DarklyServiceMock.FlagKeys.double] oldFlags = DarklyServiceMock.Constants.stubFeatureFlags(alternateValuesForKeys: changedFlagKeys) - targetChangedFlags = [LDFlagKey: LDChangedFlag](uniqueKeysWithValues: changedFlagKeys.map { (flagKey) in - return (flagKey, LDChangedFlag.stub(key: flagKey, oldFlags: oldFlags, newFlags: testContext.user.flagStore.featureFlags)) + targetChangedFlags = [LDFlagKey: LDChangedFlag](uniqueKeysWithValues: changedFlagKeys.map { flagKey in + (flagKey, LDChangedFlag.stub(key: flagKey, oldFlags: oldFlags, newFlags: testContext.user.flagStore.featureFlags)) }) targetChangedFlags![LDUser.StubConstants.userKey] = LDChangedFlag.stub(key: LDUser.StubConstants.userKey, oldFlags: oldFlags, @@ -796,7 +795,7 @@ final class FlagChangeNotifierSpec: QuickSpec { beforeEach { testContext = TestContext( keys: LDFlagKey.anyKey, - flagCollectionChangeHandler: { (changedFlags) in + flagCollectionChangeHandler: { changedFlags in testContext.flagCollectionChangeHandlerCallCount += 1 testContext.changedFlags = changedFlags }, @@ -820,7 +819,7 @@ final class FlagChangeNotifierSpec: QuickSpec { beforeEach { testContext = TestContext( keys: LDFlagKey.anyKey, - flagCollectionChangeHandler: { (changedFlags) in + flagCollectionChangeHandler: { changedFlags in testContext.flagCollectionChangeHandlerCallCount += 1 testContext.changedFlags = changedFlags }, @@ -844,7 +843,7 @@ final class FlagChangeNotifierSpec: QuickSpec { beforeEach { testContext = TestContext( keys: LDFlagKey.anyKey, - flagCollectionChangeHandler: { (changedFlags) in + flagCollectionChangeHandler: { changedFlags in testContext.flagCollectionChangeHandlerCallCount += 1 testContext.changedFlags = changedFlags }, @@ -878,13 +877,13 @@ fileprivate extension DarklyServiceMock.FlagValues { fileprivate extension LDChangedFlag { static func stub(key: LDFlagKey, oldFlags: [LDFlagKey: FeatureFlag], newFlags: [LDFlagKey: FeatureFlag]) -> LDChangedFlag { - return LDChangedFlag(key: key, oldValue: oldFlags[key]?.value, oldValueSource: .server, newValue: newFlags[key]?.value, newValueSource: .server) + LDChangedFlag(key: key, oldValue: oldFlags[key]?.value, oldValueSource: .server, newValue: newFlags[key]?.value, newValueSource: .server) } } extension LDChangedFlag: Equatable { public static func == (lhs: LDChangedFlag, rhs: LDChangedFlag) -> Bool { - return lhs.key == rhs.key + lhs.key == rhs.key && AnyComparer.isEqual(lhs.oldValue, to: rhs.oldValue) && lhs.oldValueSource == rhs.oldValueSource && AnyComparer.isEqual(lhs.newValue, to: rhs.newValue) @@ -893,5 +892,5 @@ extension LDChangedFlag: Equatable { } fileprivate extension Array where Element == LDFlagKey { - var observerKey: String { return joined(separator: ".") } + var observerKey: String { joined(separator: ".") } } diff --git a/LaunchDarkly/LaunchDarklyTests/Service Objects/FlagStoreSpec.swift b/LaunchDarkly/LaunchDarklyTests/Service Objects/FlagStoreSpec.swift index cdfd8552..d2ae28a9 100644 --- a/LaunchDarkly/LaunchDarklyTests/Service Objects/FlagStoreSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Service Objects/FlagStoreSpec.swift @@ -2,7 +2,6 @@ // FlagStoreSpec.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 10/26/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -56,7 +55,7 @@ final class FlagStoreSpec: QuickSpec { } it("has no feature flags") { expect(subject.featureFlags.isEmpty).to(beTrue()) - expect(subject.flagValueSource == .fallback).to(beTrue()) + expect(subject.flagValueSource) == .fallback } } context("with an initial flag store") { @@ -66,7 +65,7 @@ final class FlagStoreSpec: QuickSpec { } it("has matching feature flags") { expect(subject.featureFlags == featureFlags).to(beTrue()) - expect(subject.flagValueSource == .cache).to(beTrue()) + expect(subject.flagValueSource) == .cache } } context("with an initial flag store without elements") { @@ -76,7 +75,7 @@ final class FlagStoreSpec: QuickSpec { } it("has matching feature flags") { expect(subject.featureFlags == featureFlags).to(beTrue()) - expect(subject.flagValueSource == .cache).to(beTrue()) + expect(subject.flagValueSource) == .cache } } context("with an initial flag dictionary") { @@ -86,7 +85,7 @@ final class FlagStoreSpec: QuickSpec { } it("has the feature flags") { expect(subject.featureFlags == featureFlags).to(beTrue()) - expect(subject.flagValueSource == .cache).to(beTrue()) + expect(subject.flagValueSource) == .cache } } } @@ -105,8 +104,8 @@ final class FlagStoreSpec: QuickSpec { } } it("causes FlagStore to replace the flag values and source") { - expect(flagStore.featureFlags == featureFlags).to(beTrue()) - expect(flagStore.flagValueSource == .cache).to(beTrue()) + expect(flagStore.featureFlags) == featureFlags + expect(flagStore.flagValueSource) == .cache } } context("with new flag value dictionary") { @@ -118,8 +117,8 @@ final class FlagStoreSpec: QuickSpec { } } it("causes FlagStore to replace the flag values and source") { - expect(flagStore.featureFlags == featureFlags).to(beTrue()) - expect(flagStore.flagValueSource == .cache).to(beTrue()) + expect(flagStore.featureFlags) == featureFlags + expect(flagStore.flagValueSource) == .cache } } context("with nil flag values") { @@ -414,7 +413,7 @@ final class FlagStoreSpec: QuickSpec { } context("when flag key exists") { it("returns the feature flag") { - flagStore.featureFlags.forEach { (flagKey, featureFlag) in + flagStore.featureFlags.forEach { flagKey, featureFlag in expect(flagStore.featureFlag(for: flagKey)?.allPropertiesMatch(featureFlag)).to(beTrue()) } } @@ -439,7 +438,7 @@ final class FlagStoreSpec: QuickSpec { } context("when flag key exists") { it("returns the feature flag and source") { - testContext.featureFlags.forEach { (flagKey, featureFlag) in + testContext.featureFlags.forEach { flagKey, featureFlag in let (flagStoreFeatureFlag, flagStoreSource) = testContext.flagStore.featureFlagAndSource(for: flagKey) expect(flagStoreFeatureFlag) == featureFlag @@ -481,17 +480,17 @@ final class FlagStoreSpec: QuickSpec { let (arrayValue, arrayFlagSource) = subject.variationAndSource(forKey: DarklyServiceMock.FlagKeys.array, fallback: FallbackValues.array) expect(arrayValue == DarklyServiceMock.FlagValues.array).to(beTrue()) - expect(arrayFlagSource == LDFlagValueSource.server).to(beTrue()) + expect(arrayFlagSource) == LDFlagValueSource.server let (dictionaryValue, dictionaryFlagSource) = subject.variationAndSource(forKey: DarklyServiceMock.FlagKeys.dictionary, fallback: FallbackValues.dictionary) expect(dictionaryValue == DarklyServiceMock.FlagValues.dictionary).to(beTrue()) - expect(dictionaryFlagSource == LDFlagValueSource.server).to(beTrue()) + expect(dictionaryFlagSource) == LDFlagValueSource.server let (nullValue, nullFlagSource) = subject.variationAndSource(forKey: DarklyServiceMock.FlagKeys.null, fallback: FallbackValues.int) - expect(nullValue == FallbackValues.int).to(beTrue()) - expect(nullFlagSource == LDFlagValueSource.fallback).to(beTrue()) + expect(nullValue) == FallbackValues.int + expect(nullFlagSource) == LDFlagValueSource.fallback } } context("when flags do not exist") { @@ -510,13 +509,13 @@ final class FlagStoreSpec: QuickSpec { let (arrayValue, arrayFlagSource) = subject.variationAndSource(forKey: DarklyServiceMock.FlagKeys.array, fallback: FallbackValues.array) - expect(arrayValue == FallbackValues.array).to(beTrue()) - expect(arrayFlagSource == LDFlagValueSource.fallback).to(beTrue()) + expect(arrayValue) == FallbackValues.array + expect(arrayFlagSource) == LDFlagValueSource.fallback let (dictionaryValue, dictionaryFlagSource) = subject.variationAndSource(forKey: DarklyServiceMock.FlagKeys.dictionary, fallback: FallbackValues.dictionary) expect(dictionaryValue == FallbackValues.dictionary).to(beTrue()) - expect(dictionaryFlagSource == LDFlagValueSource.fallback).to(beTrue()) + expect(dictionaryFlagSource) == LDFlagValueSource.fallback } } } @@ -530,13 +529,13 @@ final class FlagStoreSpec: QuickSpec { subject = FlagStore(featureFlags: DarklyServiceMock.Constants.stubFeatureFlags(), flagValueSource: .cache) } it("causes the FlagStore to provide the flag value") { - expect(subject.variation(forKey: DarklyServiceMock.FlagKeys.bool, fallback: FallbackValues.bool) == DarklyServiceMock.FlagValues.bool).to(beTrue()) - expect(subject.variation(forKey: DarklyServiceMock.FlagKeys.int, fallback: FallbackValues.int) == DarklyServiceMock.FlagValues.int).to(beTrue()) - expect(subject.variation(forKey: DarklyServiceMock.FlagKeys.double, fallback: FallbackValues.double) == DarklyServiceMock.FlagValues.double).to(beTrue()) - expect(subject.variation(forKey: DarklyServiceMock.FlagKeys.string, fallback: FallbackValues.string) == DarklyServiceMock.FlagValues.string).to(beTrue()) + expect(subject.variation(forKey: DarklyServiceMock.FlagKeys.bool, fallback: FallbackValues.bool)) == DarklyServiceMock.FlagValues.bool + expect(subject.variation(forKey: DarklyServiceMock.FlagKeys.int, fallback: FallbackValues.int)) == DarklyServiceMock.FlagValues.int + expect(subject.variation(forKey: DarklyServiceMock.FlagKeys.double, fallback: FallbackValues.double)) == DarklyServiceMock.FlagValues.double + expect(subject.variation(forKey: DarklyServiceMock.FlagKeys.string, fallback: FallbackValues.string)) == DarklyServiceMock.FlagValues.string let arrayValue = subject.variation(forKey: DarklyServiceMock.FlagKeys.array, fallback: FallbackValues.array) - expect(arrayValue == DarklyServiceMock.FlagValues.array).to(beTrue()) + expect(arrayValue) == DarklyServiceMock.FlagValues.array let dictionaryValue = subject.variation(forKey: DarklyServiceMock.FlagKeys.dictionary, fallback: FallbackValues.dictionary) expect(dictionaryValue == DarklyServiceMock.FlagValues.dictionary).to(beTrue()) @@ -547,19 +546,19 @@ final class FlagStoreSpec: QuickSpec { subject = FlagStore() } it("causes the FlagStore to provide the fallback flag value") { - expect(subject.variation(forKey: DarklyServiceMock.FlagKeys.bool, fallback: FallbackValues.bool) == FallbackValues.bool).to(beTrue()) - expect(subject.variation(forKey: DarklyServiceMock.FlagKeys.int, fallback: FallbackValues.int) == FallbackValues.int).to(beTrue()) - expect(subject.variation(forKey: DarklyServiceMock.FlagKeys.double, fallback: FallbackValues.double) == FallbackValues.double).to(beTrue()) - expect(subject.variation(forKey: DarklyServiceMock.FlagKeys.string, fallback: FallbackValues.string) == FallbackValues.string).to(beTrue()) + expect(subject.variation(forKey: DarklyServiceMock.FlagKeys.bool, fallback: FallbackValues.bool)) == FallbackValues.bool + expect(subject.variation(forKey: DarklyServiceMock.FlagKeys.int, fallback: FallbackValues.int)) == FallbackValues.int + expect(subject.variation(forKey: DarklyServiceMock.FlagKeys.double, fallback: FallbackValues.double)) == FallbackValues.double + expect(subject.variation(forKey: DarklyServiceMock.FlagKeys.string, fallback: FallbackValues.string)) == FallbackValues.string let arrayValue = subject.variation(forKey: DarklyServiceMock.FlagKeys.array, fallback: FallbackValues.array) - expect(arrayValue == FallbackValues.array).to(beTrue()) + expect(arrayValue) == FallbackValues.array let dictionaryValue = subject.variation(forKey: DarklyServiceMock.FlagKeys.dictionary, fallback: FallbackValues.dictionary) expect(dictionaryValue == FallbackValues.dictionary).to(beTrue()) let nullValue = subject.variation(forKey: DarklyServiceMock.FlagKeys.null, fallback: FallbackValues.int) - expect(nullValue == FallbackValues.int).to(beTrue()) + expect(nullValue) == FallbackValues.int } } } diff --git a/LaunchDarkly/LaunchDarklyTests/Service Objects/FlagSynchronizerSpec.swift b/LaunchDarkly/LaunchDarklyTests/Service Objects/FlagSynchronizerSpec.swift index a256031c..b907c000 100644 --- a/LaunchDarkly/LaunchDarklyTests/Service Objects/FlagSynchronizerSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Service Objects/FlagSynchronizerSpec.swift @@ -2,7 +2,6 @@ // FlagSynchronizerSpec.swift // LaunchDarkly // -// Created by Mark Pokorny on 9/20/17. +JMJ // Copyright © 2017 Catamorphic Co. All rights reserved. // @@ -21,11 +20,11 @@ final class FlagSynchronizerSpec: QuickSpec { var config: LDConfig! var user: LDUser! var flagStoreMock: FlagMaintainingMock! { - return user.flagStore as? FlagMaintainingMock + user.flagStore as? FlagMaintainingMock } var serviceMock: DarklyServiceMock! var eventSourceMock: DarklyStreamingProviderMock? { - return serviceMock.createdEventSource + serviceMock.createdEventSource } var flagSynchronizer: FlagSynchronizer! var syncErrorEvent: DarklyEventSource.LDEvent? @@ -43,10 +42,10 @@ final class FlagSynchronizerSpec: QuickSpec { } private func isStreamingActive(online: Bool, streamingMode: LDStreamingMode) -> Bool { - return online && (streamingMode == .streaming) + online && (streamingMode == .streaming) } private func isPollingActive(online: Bool, streamingMode: LDStreamingMode) -> Bool { - return online && (streamingMode == .polling) + online && (streamingMode == .polling) } fileprivate func synchronizerState(synchronizerOnline isOnline: Bool, @@ -343,7 +342,7 @@ final class FlagSynchronizerSpec: QuickSpec { streamCreated: true, streamOpened: true, streamClosed: false) }).to(match()) - expect(newFlags == DarklyServiceMock.Constants.stubFeatureFlags(includeNullValue: false)).to(beTrue()) + expect(newFlags) == DarklyServiceMock.Constants.stubFeatureFlags(includeNullValue: false) expect(streamingEvent) == .ping } } @@ -351,7 +350,7 @@ final class FlagSynchronizerSpec: QuickSpec { var synchronizingError: SynchronizingError? beforeEach { waitUntil { done in - testContext = TestContext(streamingMode: .streaming, useReport: false, onSyncComplete: { (result) in + testContext = TestContext(streamingMode: .streaming, useReport: false, onSyncComplete: { result in if case let .error(syncError) = result { synchronizingError = syncError } @@ -370,14 +369,14 @@ final class FlagSynchronizerSpec: QuickSpec { streamCreated: true, streamOpened: true, streamClosed: false) }).to(match()) - expect(synchronizingError == .data(DarklyServiceMock.Constants.errorData)).to(beTrue()) + expect(synchronizingError) == .data(DarklyServiceMock.Constants.errorData) } } context("failure response") { var urlResponse: URLResponse? beforeEach { waitUntil { done in - testContext = TestContext(streamingMode: .streaming, useReport: false, onSyncComplete: { (result) in + testContext = TestContext(streamingMode: .streaming, useReport: false, onSyncComplete: { result in if case let .error(syncError) = result, case .response(let syncErrorResponse) = syncError { urlResponse = syncErrorResponse } @@ -406,7 +405,7 @@ final class FlagSynchronizerSpec: QuickSpec { var synchronizingError: SynchronizingError? beforeEach { waitUntil { done in - testContext = TestContext(streamingMode: .streaming, useReport: false, onSyncComplete: { (result) in + testContext = TestContext(streamingMode: .streaming, useReport: false, onSyncComplete: { result in if case let .error(syncError) = result { synchronizingError = syncError } @@ -425,7 +424,7 @@ final class FlagSynchronizerSpec: QuickSpec { streamCreated: true, streamOpened: true, streamClosed: false) }).to(match()) - expect(synchronizingError == .request(DarklyServiceMock.Constants.error)).to(beTrue()) + expect(synchronizingError) == .request(DarklyServiceMock.Constants.error) } } } @@ -462,7 +461,7 @@ final class FlagSynchronizerSpec: QuickSpec { streamCreated: true, streamOpened: true, streamClosed: false) }).to(match()) - expect(newFlags == DarklyServiceMock.Constants.stubFeatureFlags(includeNullValue: false)).to(beTrue()) + expect(newFlags) == DarklyServiceMock.Constants.stubFeatureFlags(includeNullValue: false) expect(streamingEvent) == .put } } @@ -488,7 +487,7 @@ final class FlagSynchronizerSpec: QuickSpec { streamCreated: true, streamOpened: true, streamClosed: false) }).to(match()) - expect(syncError == .data(DarklyServiceMock.Constants.errorData)).to(beTrue()) + expect(syncError) == .data(DarklyServiceMock.Constants.errorData) } } context("missing data") { @@ -513,7 +512,7 @@ final class FlagSynchronizerSpec: QuickSpec { streamCreated: true, streamOpened: true, streamClosed: false) }).to(match()) - expect(syncError == .data(nil)).to(beTrue()) + expect(syncError) == .data(nil) } } } @@ -579,7 +578,7 @@ final class FlagSynchronizerSpec: QuickSpec { streamCreated: true, streamOpened: true, streamClosed: false) }).to(match()) - expect(syncError == .data(DarklyServiceMock.Constants.errorData)).to(beTrue()) + expect(syncError) == .data(DarklyServiceMock.Constants.errorData) } } context("missing data") { @@ -604,7 +603,7 @@ final class FlagSynchronizerSpec: QuickSpec { streamCreated: true, streamOpened: true, streamClosed: false) }).to(match()) - expect(syncError == .data(nil)).to(beTrue()) + expect(syncError) == .data(nil) } } } @@ -667,7 +666,7 @@ final class FlagSynchronizerSpec: QuickSpec { streamCreated: true, streamOpened: true, streamClosed: false) }).to(match()) - expect(syncError == .data(DarklyServiceMock.Constants.errorData)).to(beTrue()) + expect(syncError) == .data(DarklyServiceMock.Constants.errorData) } } context("missing data") { @@ -692,7 +691,7 @@ final class FlagSynchronizerSpec: QuickSpec { streamCreated: true, streamOpened: true, streamClosed: false) }).to(match()) - expect(syncError == .data(nil)).to(beTrue()) + expect(syncError) == .data(nil) } } } @@ -703,14 +702,14 @@ final class FlagSynchronizerSpec: QuickSpec { var testContext: TestContext! beforeEach { - testContext = TestContext(streamingMode: .streaming, useReport: false, onSyncComplete: { (_) in + testContext = TestContext(streamingMode: .streaming, useReport: false, onSyncComplete: { _ in testContext.onSyncCompleteCallCount += 1 }) } context("error event") { beforeEach { waitUntil { done in - testContext = TestContext(streamingMode: .streaming, useReport: false, onSyncComplete: { (syncResult) in + testContext = TestContext(streamingMode: .streaming, useReport: false, onSyncComplete: { syncResult in if case .error(let errorResult) = syncResult, case .event(let errorEvent) = errorResult { testContext.syncErrorEvent = errorEvent } @@ -735,7 +734,7 @@ final class FlagSynchronizerSpec: QuickSpec { context("unauthorized error event") { beforeEach { waitUntil { done in - testContext = TestContext(streamingMode: .streaming, useReport: false, onSyncComplete: { (syncResult) in + testContext = TestContext(streamingMode: .streaming, useReport: false, onSyncComplete: { syncResult in if case .error(let errorResult) = syncResult, case .event(let errorEvent) = errorResult { testContext.syncErrorEvent = errorEvent } @@ -841,7 +840,7 @@ final class FlagSynchronizerSpec: QuickSpec { var syncErrorEvent: DarklyEventSource.LDEvent? beforeEach { waitUntil { done in - testContext = TestContext(streamingMode: .streaming, useReport: false, onSyncComplete: { (syncResult) in + testContext = TestContext(streamingMode: .streaming, useReport: false, onSyncComplete: { syncResult in if case .error(let errorResult) = syncResult, case .event(let errorEvent) = errorResult { syncErrorEvent = errorEvent @@ -958,7 +957,7 @@ final class FlagSynchronizerSpec: QuickSpec { waitUntil(timeout: 2) { done in //In polling mode, the flagSynchronizer makes a flag request when set online right away. To verify the timer this test waits the polling interval (1s) for a second flag request - testContext.flagSynchronizer.onSyncComplete = { (result) in + testContext.flagSynchronizer.onSyncComplete = { result in if case .success(let flags, let streamEvent) = result { (newFlags, streamingEvent) = (flags.flagCollection, streamEvent) } @@ -1108,7 +1107,7 @@ final class FlagSynchronizerSpec: QuickSpec { var synchronizingError: SynchronizingError? beforeEach { waitUntil { done in - testContext = TestContext(streamingMode: .streaming, useReport: false, onSyncComplete: { (result) in + testContext = TestContext(streamingMode: .streaming, useReport: false, onSyncComplete: { result in if case .error(let syncError) = result { synchronizingError = syncError } @@ -1152,15 +1151,3 @@ extension SynchronizingError: Equatable { } } } - -extension Optional where Wrapped == SynchronizingError { - static func == (lhs: SynchronizingError?, rhs: SynchronizingError?) -> Bool { - switch (lhs, rhs) { - case let (.some(left), .some(right)): - return left == right - case (.none, .none): - return true - default: return false - } - } -} diff --git a/LaunchDarkly/LaunchDarklyTests/Service Objects/LDTimerSpec.swift b/LaunchDarkly/LaunchDarklyTests/Service Objects/LDTimerSpec.swift index d66e01a7..1f008db0 100644 --- a/LaunchDarkly/LaunchDarklyTests/Service Objects/LDTimerSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Service Objects/LDTimerSpec.swift @@ -2,7 +2,6 @@ // LDTimerSpec.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 1/22/19. +JMJ // Copyright © 2019 Catamorphic Co. All rights reserved. // @@ -159,6 +158,6 @@ final class LDTimerSpec: QuickSpec { extension DispatchQueue { class var currentQueueLabel: String? { - return String(validatingUTF8: __dispatch_queue_get_label(nil)) //from https://gitlab.com/theswiftdev/swift/snippets/1741827/raw + String(validatingUTF8: __dispatch_queue_get_label(nil)) //from https://gitlab.com/theswiftdev/swift/snippets/1741827/raw } } diff --git a/LaunchDarkly/LaunchDarklyTests/Service Objects/SynchronizingErrorSpec.swift b/LaunchDarkly/LaunchDarklyTests/Service Objects/SynchronizingErrorSpec.swift index 19becd0f..f0016dfa 100644 --- a/LaunchDarkly/LaunchDarklyTests/Service Objects/SynchronizingErrorSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Service Objects/SynchronizingErrorSpec.swift @@ -2,7 +2,6 @@ // SynchronizingErrorSpec.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 2/27/18. +JMJ // Copyright © 2018 Catamorphic Co. All rights reserved. // diff --git a/LaunchDarkly/LaunchDarklyTests/Service Objects/ThrottlerSpec.swift b/LaunchDarkly/LaunchDarklyTests/Service Objects/ThrottlerSpec.swift index 53613988..27bb49c7 100644 --- a/LaunchDarkly/LaunchDarklyTests/Service Objects/ThrottlerSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Service Objects/ThrottlerSpec.swift @@ -2,7 +2,6 @@ // ThrottlerSpec.swift // LaunchDarklyTests // -// Created by Mark Pokorny on 5/14/18. +JMJ // Copyright © 2018 Catamorphic Co. All rights reserved. // @@ -122,9 +121,7 @@ final class ThrottlerSpec: QuickSpec { } //The upper bound on the max delay is always 2^runAttempt, exclusive. - func maxDelay(runAttempt: Int) -> TimeInterval { - return pow(2, runAttempt).timeInterval - } + func maxDelay(runAttempt: Int) -> TimeInterval { pow(2, runAttempt).timeInterval } func runSpec() { describe("runThrottled") { diff --git a/README.md b/README.md index 05dd0ae3..3b3206f0 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,10 @@ LaunchDarkly overview [![Twitter Follow](https://img.shields.io/twitter/follow/launchdarkly.svg?style=social&label=Follow&maxAge=2592000)](https://twitter.com/intent/follow?screen_name=launchdarkly) -Supported iOS versions +Supported iOS and Xcode versions ------------------------- -This version of the LaunchDarkly SDK has been tested with iOS 12 and across mobile, desktop, watch, and tv devices. +This version of the LaunchDarkly SDK has been tested with iOS 12 and across mobile, desktop, watch, and tv devices. The SDK is built with Xcode 11.4. Getting started ----------- @@ -35,7 +35,7 @@ $ gem install cocoapods ```ruby use_frameworks! target 'YourTargetName' do - pod 'LaunchDarkly', '4.5.0' + pod 'LaunchDarkly', '4.6.0' end ``` @@ -70,7 +70,7 @@ $ brew install carthage To integrate LaunchDarkly into your Xcode project using Carthage, specify it in your `Cartfile`: ```ogdl -github "launchdarkly/ios-client-sdk" "4.5.0" +github "launchdarkly/ios-client-sdk" "4.6.0" ``` Run `carthage update` to build the framework. Optionally, specify the `--platform` to build only the frameworks that support your platform(s).