Skip to content

Commit 3a5a2e3

Browse files
(chore): cleanup (#173)
* cleanup * more cleanup * remove bang that could cause crash * cleanup evaluate to make it faster * fix condition holder tests and add back into project
1 parent eaf1705 commit 3a5a2e3

File tree

15 files changed

+117
-203
lines changed

15 files changed

+117
-203
lines changed

DemoSwiftApp/AppDelegate.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
5151
// - initialize immediately with the given JSON datafile or its cached copy
5252
// - no network delay, but the local copy is not guaranteed to be in sync with the server experiment settings
5353

54-
initializeOptimizelySDKAsynchronous()
54+
initializeOptimizelySDKWithCustomization()
5555
}
5656

5757
// MARK: - Initialization Examples

OptimizelySDK/Customization/DefaultLogger.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ open class DefaultLogger : OPTLogger {
2323
return _logLevel ?? .info
2424
}
2525
set (newLevel){
26-
if _logLevel == nil {
26+
guard let _ = _logLevel else {
2727
_logLevel = newLevel
28+
return
2829
}
2930
}
3031
}

OptimizelySDK/Data Model/Audience/Audience.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ struct Audience: Codable, Equatable {
6363
try container.encode(conditions, forKey: .conditions)
6464
}
6565

66-
func evaluate(project: ProjectProtocol?, attributes: OptimizelyAttributes?) throws -> Bool {
66+
func evaluate(project: Project?, attributes: OptimizelyAttributes?) throws -> Bool {
6767
return try conditions.evaluate(project: project, attributes: attributes)
6868
}
6969
}

OptimizelySDK/Data Model/Audience/ConditionHolder.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ enum ConditionHolder: Codable, Equatable {
6161
}
6262
}
6363

64-
func evaluate(project: ProjectProtocol?, attributes: OptimizelyAttributes?) throws -> Bool {
64+
func evaluate(project: Project?, attributes: OptimizelyAttributes?) throws -> Bool {
6565
switch self {
6666
case .logicalOp:
6767
throw OptimizelyError.conditionInvalidFormat("Logical operation not evaluated")
@@ -77,7 +77,7 @@ enum ConditionHolder: Codable, Equatable {
7777

7878
extension Array where Element == ConditionHolder {
7979

80-
func evaluate(project: ProjectProtocol?, attributes: OptimizelyAttributes?) throws -> Bool {
80+
func evaluate(project: Project?, attributes: OptimizelyAttributes?) throws -> Bool {
8181
guard let firstItem = self.first else {
8282
throw OptimizelyError.conditionInvalidFormat("Empty condition array")
8383
}
@@ -94,7 +94,7 @@ extension Array where Element == ConditionHolder {
9494
}
9595
}
9696

97-
func evaluate(op: LogicalOp, project: ProjectProtocol?, attributes: OptimizelyAttributes?) throws -> Bool {
97+
func evaluate(op: LogicalOp, project: Project?, attributes: OptimizelyAttributes?) throws -> Bool {
9898
guard self.count > 0 else {
9999
throw OptimizelyError.conditionInvalidFormat("Empty condition array")
100100
}

OptimizelySDK/Data Model/Audience/ConditionLeaf.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@ enum ConditionLeaf: Codable, Equatable {
5050
}
5151
}
5252

53-
func evaluate(project: ProjectProtocol?, attributes: OptimizelyAttributes?) throws -> Bool {
53+
func evaluate(project: Project?, attributes: OptimizelyAttributes?) throws -> Bool {
5454
switch self {
5555
case .audienceId(let id):
56-
guard let project = project else {
56+
guard var project = project else {
5757
throw OptimizelyError.conditionCannotBeEvaluated("audienceId: \(id)")
5858
}
5959

60-
return try project.evaluateAudience(audienceId: id, attributes: attributes)
60+
return try project.getAudience(id: id)?.evaluate(project: project, attributes: attributes) ?? false
6161
case .attribute(let userAttribute):
6262
return try userAttribute.evaluate(attributes: attributes)
6363
}

OptimizelySDK/Data Model/Group.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,27 @@ struct Group: Codable, Equatable {
2626
var policy: Policy
2727
var trafficAllocation: [TrafficAllocation]
2828
var experiments: [Experiment]
29+
30+
private enum CodingKeys: String, CodingKey {
31+
case id
32+
case policy
33+
case trafficAllocation
34+
case experiments
35+
}
36+
37+
lazy var experimentMap:[String:Experiment] = {
38+
var map:[String:Experiment] = [:]
39+
experiments.forEach({map[$0.id] = $0 })
40+
return map
41+
}()
2942
}
3043

3144
// MARK: - Utils
3245

3346
extension Group {
3447

35-
func getExperiemnt(id: String) -> Experiment? {
36-
return experiments.filter { $0.id == id }.first
48+
mutating func getExperiment(id: String) -> Experiment? {
49+
return self.experimentMap[id]
3750
}
3851

3952
}

OptimizelySDK/Data Model/Project.swift

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@
1616

1717
import Foundation
1818

19-
protocol ProjectProtocol {
20-
func evaluateAudience(audienceId: String, attributes: OptimizelyAttributes?) throws -> Bool
21-
}
22-
2319
//[REF]: datafile schema
2420
// https://github.com/optimizely/optimizely/blob/43454b726a2a8aab7dcd953999cf8e1902b09d4d/src/www/services/datafile_generator/schema.json
2521

@@ -42,27 +38,40 @@ struct Project: Codable, Equatable {
4238
var typedAudiences: [Audience]?
4339
var featureFlags: [FeatureFlag]
4440
var botFiltering: Bool?
45-
}
46-
47-
extension Project: ProjectProtocol {
4841

49-
func evaluateAudience(audienceId: String, attributes: OptimizelyAttributes?) throws -> Bool {
50-
guard let audience = getAudience(id: audienceId) else {
51-
throw OptimizelyError.conditionNoMatchingAudience(audienceId)
52-
}
53-
54-
return try audience.evaluate(project: self, attributes: attributes)
42+
private enum CodingKeys: String, CodingKey {
43+
case version
44+
case projectId
45+
case experiments
46+
case audiences
47+
case groups
48+
case attributes
49+
case accountId
50+
case events
51+
case revision
52+
case anonymizeIP
53+
case rollouts
54+
case typedAudiences
55+
case featureFlags
56+
case botFiltering
5557
}
5658

59+
lazy var audienceMap:[String:Audience] = {
60+
var map:[String:Audience] = [:]
61+
audiences.forEach({map[$0.id] = $0 })
62+
typedAudiences?.forEach({map[$0.id] = $0})
63+
return map
64+
}()
65+
66+
5767
}
5868

5969
// MARK: - Utils
6070

6171
extension Project {
6272

63-
func getAudience(id: String) -> Audience? {
64-
return typedAudiences?.filter{ $0.id == id }.first ??
65-
audiences.filter{ $0.id == id }.first
73+
mutating func getAudience(id: String) -> Audience? {
74+
return audienceMap[id]
6675
}
6776

6877
}

OptimizelySDK/Implementation/DefaultBucketer.swift

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,13 @@ class DefaultBucketer : OPTBucketer {
3333

3434
// check for mutex
3535

36-
let group = config.project.groups.filter{ $0.getExperiemnt(id: experiment.id) != nil }.first
36+
let group = config.project.groups.filter({
37+
var g = $0
38+
guard let _ = g.experimentMap[experiment.id] else {
39+
return false
40+
}
41+
return true
42+
}).first
3743

3844
if let group = group {
3945
switch group.policy {
@@ -79,17 +85,12 @@ class DefaultBucketer : OPTBucketer {
7985
return nil;
8086
}
8187

82-
for trafficAllocation in group.trafficAllocation {
83-
if bucketValue <= trafficAllocation.endOfRange {
84-
let experimentId = trafficAllocation.entityId;
85-
86-
// propagate errors and logs for unknown experiment
87-
if let experiment = config.getExperiment(id: experimentId) {
88-
return experiment
89-
} else {
90-
logger?.e(.userBucketedIntoInvalidExperiment(experimentId))
91-
return nil
92-
}
88+
for trafficAllocation in group.trafficAllocation where bucketValue <= trafficAllocation.endOfRange {
89+
if let experiment = config.getExperiment(id: trafficAllocation.entityId) {
90+
return experiment
91+
} else {
92+
logger?.e(.userBucketedIntoInvalidExperiment(trafficAllocation.entityId))
93+
return nil
9394
}
9495
}
9596

@@ -106,18 +107,14 @@ class DefaultBucketer : OPTBucketer {
106107
return nil
107108
}
108109

109-
for trafficAllocation in experiment.trafficAllocation {
110-
if (bucketValue <= trafficAllocation.endOfRange) {
111-
let variationId = trafficAllocation.entityId;
112-
110+
for trafficAllocation in experiment.trafficAllocation where (bucketValue <= trafficAllocation.endOfRange) {
113111
// propagate errors and logs for unknown variation
114-
if let variation = experiment.getVariation(id: variationId) {
112+
if let variation = experiment.getVariation(id: trafficAllocation.entityId) {
115113
return variation
116114
} else {
117-
logger?.e(.userBucketedIntoInvalidVariation(variationId))
115+
logger?.e(.userBucketedIntoInvalidVariation(trafficAllocation.entityId))
118116
return nil
119117
}
120-
}
121118
}
122119

123120
return nil;

OptimizelySDK/Implementation/DefaultDecisionService.swift

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -63,50 +63,50 @@ class DefaultDecisionService : OPTDecisionService {
6363
return variation
6464
}
6565

66-
var bucketedVariation:Variation?
6766
// ---- check if the user passes audience targeting before bucketing ----
68-
if isInExperiment(config: config, experiment:experiment, userId:userId, attributes:attributes) {
69-
// bucket user into a variation
70-
bucketedVariation = bucketer.bucketExperiment(config: config, experiment: experiment, bucketingId: bucketingId)
71-
72-
if let bucketedVariation = bucketedVariation {
67+
if isInExperiment(config: config, experiment:experiment, userId:userId, attributes:attributes), let bucketedVariation = bucketer.bucketExperiment(config: config, experiment: experiment, bucketingId: bucketingId) {
7368
// save to user profile
74-
self.saveProfile(userId: userId, experimentId: experimentId, variationId: bucketedVariation.id)
75-
}
69+
self.saveProfile(userId: userId, experimentId: experimentId, variationId: bucketedVariation.id)
70+
return bucketedVariation
7671
} else {
7772
logger?.i(.userNotInExperiment(userId, experiment.key))
7873
}
7974

80-
return bucketedVariation;
75+
return nil
8176
}
8277

8378
func isInExperiment(config:ProjectConfig, experiment:Experiment, userId:String, attributes: OptimizelyAttributes) -> Bool {
84-
79+
80+
func innerEvaluate(conditions:ConditionHolder) throws -> Bool {
81+
var result = false
82+
switch conditions {
83+
case .array(let arrConditions):
84+
if arrConditions.count > 0 {
85+
result = try conditions.evaluate(project: config.project, attributes: attributes)
86+
} else {
87+
// empty conditions (backward compatibility with "audienceIds" is ignored if exists even though empty
88+
result = true
89+
}
90+
case .leaf:
91+
result = try conditions.evaluate(project: config.project, attributes: attributes)
92+
default:
93+
result = true
94+
}
95+
return result
96+
}
8597
var result = true // success as default (no condition, etc)
8698

8799
do {
88100
if let conditions = experiment.audienceConditions {
89-
switch conditions {
90-
case .array(let arrConditions):
91-
if arrConditions.count > 0 {
92-
result = try conditions.evaluate(project: config.project, attributes: attributes)
93-
} else {
94-
// empty conditions (backward compatibility with "audienceIds" is ignored if exists even though empty
95-
result = true
96-
}
97-
case .leaf:
98-
result = try conditions.evaluate(project: config.project, attributes: attributes)
99-
default:
100-
result = true
101-
}
101+
result = try innerEvaluate(conditions: conditions)
102102
}
103103
// backward compatibility with audiencIds list
104104
else if experiment.audienceIds.count > 0 {
105105
var holder = [ConditionHolder]()
106106
holder.append(.logicalOp(.or))
107-
for id in experiment.audienceIds {
108-
holder.append(.leaf(.audienceId(id)))
109-
}
107+
experiment.audienceIds.forEach({
108+
holder.append(.leaf(.audienceId($0)))
109+
})
110110

111111
result = try holder.evaluate(project: config.project, attributes: attributes)
112112
}

0 commit comments

Comments
 (0)