Skip to content

Commit 2960b94

Browse files
committed
cherry-pick for 3.8.1
1 parent a434d96 commit 2960b94

File tree

50 files changed

+772
-236
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+772
-236
lines changed

DemoObjCApp/DemoObjcApp.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@
435435
6EF7496721E40467008B22A0 /* Project object */ = {
436436
isa = PBXProject;
437437
attributes = {
438-
LastUpgradeCheck = 1240;
438+
LastUpgradeCheck = 1250;
439439
ORGANIZATIONNAME = Optimizely;
440440
TargetAttributes = {
441441
6EF7498D21E404BB008B22A0 = {

DemoObjCApp/DemoObjcApp.xcodeproj/xcshareddata/xcschemes/DemoObjciOS.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1240"
3+
LastUpgradeVersion = "1250"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"

DemoObjCApp/DemoObjcApp.xcodeproj/xcshareddata/xcschemes/DemoObjctvOS.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1230"
3+
LastUpgradeVersion = "1250"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"

DemoSwiftApp/DemoSwiftApp.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,7 @@
555555
isa = PBXProject;
556556
attributes = {
557557
LastSwiftUpdateCheck = 1230;
558-
LastUpgradeCheck = 1240;
558+
LastUpgradeCheck = 1250;
559559
ORGANIZATIONNAME = Optimizely;
560560
TargetAttributes = {
561561
252D7DEC21C8800800134A7A = {

DemoSwiftApp/DemoSwiftApp.xcodeproj/xcshareddata/xcschemes/DemoSwiftiOS.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1240"
3+
LastUpgradeVersion = "1250"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"

DemoSwiftApp/DemoSwiftApp.xcodeproj/xcshareddata/xcschemes/DemoSwifttvOS.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1240"
3+
LastUpgradeVersion = "1250"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"

DemoSwiftApp/DemoSwiftApp.xcodeproj/xcshareddata/xcschemes/DemoSwiftwatchOS.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1240"
3+
LastUpgradeVersion = "1250"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"

OptimizelySwiftSDK.xcodeproj/project.pbxproj

Lines changed: 62 additions & 1 deletion
Large diffs are not rendered by default.

OptimizelySwiftSDK.xcodeproj/xcshareddata/xcschemes/OptimizelySwiftSDK-iOS.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1240"
3+
LastUpgradeVersion = "1250"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"

OptimizelySwiftSDK.xcodeproj/xcshareddata/xcschemes/OptimizelySwiftSDK-macOS.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1240"
3+
LastUpgradeVersion = "1250"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"

OptimizelySwiftSDK.xcodeproj/xcshareddata/xcschemes/OptimizelySwiftSDK-tvOS.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1240"
3+
LastUpgradeVersion = "1250"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"

OptimizelySwiftSDK.xcodeproj/xcshareddata/xcschemes/OptimizelySwiftSDK-watchOS.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1240"
3+
LastUpgradeVersion = "1250"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"

Sources/Customization/DefaultDatafileHandler.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,12 @@ open class DefaultDatafileHandler: OPTDatafileHandler {
2020
// endpoint used to get the datafile. This is settable after you create a OptimizelyClient instance.
2121
public var endPointStringFormat = "https://cdn.optimizely.com/datafiles/%@.json"
2222

23-
// lazy load the logger from the logger factory.
24-
lazy var logger = OPTLoggerFactory.getLogger()
23+
// thread-safe lazy logger load (after HandlerRegisterService ready)
24+
private var loggerInstance: OPTLogger?
25+
var logger: OPTLogger {
26+
return OPTLoggerFactory.getLoggerThreadSafe(&loggerInstance)
27+
}
28+
2529
// the timers for all sdk keys are atomic to allow for thread access.
2630
var timers = AtomicProperty(property: [String: (timer: Timer?, interval: Int)]())
2731

Sources/Customization/DefaultEventDispatcher.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@ open class DefaultEventDispatcher: BackgroundingCallbacks, OPTEventDispatcher {
3737
static let maxFailureCount = 3
3838
}
3939

40-
lazy var logger = OPTLoggerFactory.getLogger()
40+
// thread-safe lazy logger load (after HandlerRegisterService ready)
41+
private var loggerInstance: OPTLogger?
42+
var logger: OPTLogger {
43+
return OPTLoggerFactory.getLoggerThreadSafe(&loggerInstance)
44+
}
45+
4146
// for dispatching events
4247
let queueLock = DispatchQueue(label: "DefaultEventDispatcherQueue")
4348
// using a datastore queue with a backing file

Sources/Customization/Protocols/OPTLogger.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,31 @@ extension OPTLogger {
7777
}
7878

7979
@objc public class OPTLoggerFactory: NSObject {
80+
8081
class func getLogger() -> OPTLogger {
8182
if let logger = HandlerRegistryService.shared.injectLogger() {
8283
return logger
8384
}
8485

8586
return DefaultLogger()
8687
}
88+
89+
// thread-safe lazy logger load (after HandlerRegisterService ready)
90+
// - "lazy var" is not thread-safe
91+
// - call this thread-safe version for types that can be initialized before (for customiziation) or at the same time with OptimizelyClient (so HandlerRegisterService is not ready yet).
92+
// - DefaultDatafileHandler, DefaultEventDispatcher, DefaultDecisionService, DefaultBucketer
93+
94+
static let lock = DispatchQueue(label: "logger")
95+
96+
class func getLoggerThreadSafe(_ instance: inout OPTLogger?) -> OPTLogger {
97+
var result: OPTLogger?
98+
lock.sync {
99+
if instance == nil {
100+
instance = OPTLoggerFactory.getLogger()
101+
}
102+
result = instance!
103+
}
104+
return result!
105+
}
106+
87107
}

Sources/Data Model/ProjectConfig.swift

Lines changed: 83 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -18,97 +18,111 @@ import Foundation
1818

1919
class ProjectConfig {
2020

21-
var project: Project!
22-
23-
lazy var logger = OPTLoggerFactory.getLogger()
21+
var project: Project! {
22+
didSet {
23+
updateProjectDependentProps()
24+
}
25+
}
26+
let logger = OPTLoggerFactory.getLogger()
2427

2528
// local runtime forcedVariations [UserId: [ExperimentId: VariationId]]
2629
// NOTE: experiment.forcedVariations use [ExperimentKey: VariationKey] instead of ids
27-
2830
var whitelistUsers = AtomicProperty(property: [String: [String: String]]())
2931

30-
lazy var experimentKeyMap: [String: Experiment] = {
31-
var map = [String: Experiment]()
32-
allExperiments.forEach { exp in
33-
map[exp.key] = exp
34-
}
35-
return map
36-
}()
37-
38-
lazy var experimentIdMap: [String: Experiment] = {
39-
var map = [String: Experiment]()
40-
allExperiments.forEach { map[$0.id] = $0 }
41-
return map
42-
}()
43-
44-
lazy var experimentFeatureMap: [String: [String]] = {
45-
var experimentFeatureMap = [String: [String]]()
46-
project.featureFlags.forEach { (ff) in
47-
ff.experimentIds.forEach {
48-
if var arr = experimentFeatureMap[$0] {
49-
arr.append(ff.id)
50-
experimentFeatureMap[$0] = arr
51-
} else {
52-
experimentFeatureMap[$0] = [ff.id]
53-
}
54-
}
55-
}
56-
return experimentFeatureMap
57-
}()
58-
59-
lazy var eventKeyMap: [String: Event] = {
60-
var eventKeyMap = [String: Event]()
61-
project.events.forEach { eventKeyMap[$0.key] = $0 }
62-
return eventKeyMap
63-
}()
64-
65-
lazy var attributeKeyMap: [String: Attribute] = {
66-
var map = [String: Attribute]()
67-
project.attributes.forEach { map[$0.key] = $0 }
68-
return map
69-
}()
70-
71-
lazy var featureFlagKeyMap: [String: FeatureFlag] = {
72-
var map = [String: FeatureFlag]()
73-
project.featureFlags.forEach { map[$0.key] = $0 }
74-
return map
75-
}()
76-
77-
lazy var featureFlagKeys: [String] = {
78-
return project.featureFlags.map { $0.key }
79-
}()
80-
81-
lazy var rolloutIdMap: [String: Rollout] = {
82-
var map = [String: Rollout]()
83-
project.rollouts.forEach { map[$0.id] = $0 }
84-
return map
85-
}()
86-
87-
lazy var allExperiments: [Experiment] = {
88-
return project.experiments + project.groups.map { $0.experiments }.flatMap({$0})
89-
}()
32+
var experimentKeyMap = [String: Experiment]()
33+
var experimentIdMap = [String: Experiment]()
34+
var experimentFeatureMap = [String: [String]]()
35+
var eventKeyMap = [String: Event]()
36+
var attributeKeyMap = [String: Attribute]()
37+
var featureFlagKeyMap = [String: FeatureFlag]()
38+
var featureFlagKeys = [String]()
39+
var rolloutIdMap = [String: Rollout]()
40+
var allExperiments = [Experiment]()
9041

9142
// MARK: - Init
9243

9344
init(datafile: Data) throws {
45+
var project: Project
9446
do {
95-
self.project = try JSONDecoder().decode(Project.self, from: datafile)
96-
97-
ProjectConfig.observer.update(project: project)
47+
project = try JSONDecoder().decode(Project.self, from: datafile)
9848
} catch {
9949
throw OptimizelyError.dataFileInvalid
10050
}
10151

102-
if !isValidVersion(version: self.project.version) {
103-
throw OptimizelyError.dataFileVersionInvalid(self.project.version)
52+
if !isValidVersion(version: project.version) {
53+
throw OptimizelyError.dataFileVersionInvalid(project.version)
10454
}
55+
56+
defer { self.project = project } // deferred-init will call "didSet"
57+
ProjectConfig.observer.update(project: project)
10558
}
10659

10760
convenience init(datafile: String) throws {
10861
try self.init(datafile: Data(datafile.utf8))
10962
}
11063

11164
init() {}
65+
66+
func updateProjectDependentProps() {
67+
self.allExperiments = project.experiments + project.groups.map { $0.experiments }.flatMap { $0 }
68+
69+
self.experimentKeyMap = {
70+
var map = [String: Experiment]()
71+
allExperiments.forEach { exp in
72+
map[exp.key] = exp
73+
}
74+
return map
75+
}()
76+
77+
self.experimentIdMap = {
78+
var map = [String: Experiment]()
79+
allExperiments.forEach { map[$0.id] = $0 }
80+
return map
81+
}()
82+
83+
self.experimentFeatureMap = {
84+
var experimentFeatureMap = [String: [String]]()
85+
project.featureFlags.forEach { (ff) in
86+
ff.experimentIds.forEach {
87+
if var arr = experimentFeatureMap[$0] {
88+
arr.append(ff.id)
89+
experimentFeatureMap[$0] = arr
90+
} else {
91+
experimentFeatureMap[$0] = [ff.id]
92+
}
93+
}
94+
}
95+
return experimentFeatureMap
96+
}()
97+
98+
self.eventKeyMap = {
99+
var eventKeyMap = [String: Event]()
100+
project.events.forEach { eventKeyMap[$0.key] = $0 }
101+
return eventKeyMap
102+
}()
103+
104+
self.attributeKeyMap = {
105+
var map = [String: Attribute]()
106+
project.attributes.forEach { map[$0.key] = $0 }
107+
return map
108+
}()
109+
110+
self.featureFlagKeyMap = {
111+
var map = [String: FeatureFlag]()
112+
project.featureFlags.forEach { map[$0.key] = $0 }
113+
return map
114+
}()
115+
116+
self.featureFlagKeys = {
117+
return project.featureFlags.map { $0.key }
118+
}()
119+
120+
self.rolloutIdMap = {
121+
var map = [String: Rollout]()
122+
project.rollouts.forEach { map[$0.id] = $0 }
123+
return map
124+
}()
125+
}
112126
}
113127

114128
// MARK: - Project Change Observer

Sources/Implementation/Datastore/DataStoreFile.swift

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@ open class DataStoreFile<T>: OPTDataStore where T: Codable {
2323
let lock: DispatchQueue
2424
let async: Bool
2525
public let url: URL
26-
lazy var logger: OPTLogger? = OPTLoggerFactory.getLogger()
27-
26+
27+
// thread-safe lazy logger load (after HandlerRegisterService ready)
28+
private var loggerInstance: OPTLogger?
29+
var logger: OPTLogger {
30+
return OPTLoggerFactory.getLoggerThreadSafe(&loggerInstance)
31+
}
32+
2833
public init(storeName: String, async: Bool = true) {
2934
self.async = async
3035
dataStoreName = storeName
@@ -71,7 +76,7 @@ open class DataStoreFile<T>: OPTDataStore where T: Codable {
7176
}
7277
} catch let e as NSError {
7378
if e.code != 260 {
74-
self.logger?.e(e.localizedDescription)
79+
self.logger.e(e.localizedDescription)
7580
}
7681
}
7782
}
@@ -111,7 +116,7 @@ open class DataStoreFile<T>: OPTDataStore where T: Codable {
111116
}
112117
}
113118
} catch let e {
114-
self.logger?.e(e.localizedDescription)
119+
self.logger.e(e.localizedDescription)
115120
}
116121
}
117122
}
@@ -121,7 +126,7 @@ open class DataStoreFile<T>: OPTDataStore where T: Codable {
121126
do {
122127
try FileManager.default.removeItem(at: self.url)
123128
} catch let e {
124-
self.logger?.e(e.localizedDescription)
129+
self.logger.e(e.localizedDescription)
125130
}
126131
}
127132

Sources/Implementation/Datastore/DataStoreMemory.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ public class DataStoreMemory<T>: BackgroundingCallbacks, OPTDataStore where T: C
2525
var data: T?
2626
var backupDataStore: OPTDataStore
2727
public enum BackingStore { case userDefaults, file }
28-
lazy var logger: OPTLogger? = OPTLoggerFactory.getLogger()
2928

3029
init(storeName: String, backupStore: BackingStore = .file) {
3130
dataStoreName = storeName

Sources/Implementation/Datastore/DataStoreUserDefaults.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,13 @@ public class DataStoreUserDefaults: OPTDataStore {
2626
static let MAX_DS_SIZE = 1000000
2727
#endif
2828
static let dispatchQueue = DispatchQueue(label: "OPTDataStoreQueueUserDefaults")
29-
lazy var logger: OPTLogger = OPTLoggerFactory.getLogger()
30-
29+
30+
// thread-safe lazy logger load (after HandlerRegisterService ready)
31+
private var loggerInstance: OPTLogger?
32+
var logger: OPTLogger {
33+
return OPTLoggerFactory.getLoggerThreadSafe(&loggerInstance)
34+
}
35+
3136
public func getItem(forKey: String) -> Any? {
3237

3338
return DataStoreUserDefaults.dispatchQueue.sync {

0 commit comments

Comments
 (0)