Skip to content

Commit 3b3924c

Browse files
authored
Allow supplying data file handler. (#384)
As consumers of the SDK we have a need to support custom reading and writing of the data files within Optimizely. This PR makes changes to allow for this customization by supporting the injection of an OPTDatafileHandler that can implement these custom behaviors. There are three main areas of this PR: - Allow for injecting a OPTDatafileHandler during the construction of the OptimizelyClient. If not supplied then the standard DefaultDatafileHandler is utilized. - Open the existing implementation of DefaultDatafileHandler so that it can be extended by consumers of the SDK and injected via OptimizelyClient construction. - Open the existing implementation of DataStoreFile so that it can be easily extended to support custom reading and writing of Data, and utilized in extended versions of OPTDatafileHandler/ DefaultDatafileHandler.
1 parent 5301725 commit 3b3924c

File tree

13 files changed

+155
-120
lines changed

13 files changed

+155
-120
lines changed

.travis.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ jobs:
4242
- stage: 'Lint'
4343
language: swift
4444
os: osx
45-
osx_image: xcode11.3
45+
osx_image: xcode11.6
4646
install:
47-
- gem install cocoapods -v '1.8.0'
47+
- gem install cocoapods -v '1.9.3'
4848
script:
4949
- pod spec lint --quick
5050
after_script:
@@ -64,15 +64,15 @@ jobs:
6464
stage: 'Unit Tests'
6565
language: swift
6666
os: osx
67-
osx_image: xcode11.3
67+
osx_image: xcode11.6
6868
branches:
6969
only:
7070
- master
7171
env: SCHEME=OptimizelySwiftSDK-iOS TEST_SDK=iphonesimulator PLATFORM='iOS Simulator' OS=13.3 NAME='iPhone 11'
7272
name: PLATFORM='iOS Simulator' OS=13.3 NAME='iPhone 11'
7373
install:
7474
- gem install slather --no-document --quiet
75-
- gem install cocoapods -v '1.8.0'
75+
- gem install cocoapods -v '1.9.3'
7676
- pod repo update
7777
- pod install
7878
# install jq without cleaning up
@@ -104,14 +104,14 @@ jobs:
104104
name: Prepare for release
105105
language: swift
106106
os: osx
107-
osx_image: xcode11.3
107+
osx_image: xcode11.6
108108
env:
109109
- VERSION=3.7.0
110110
install:
111111
# install hub
112112
- wget https://github.com/github/hub/releases/download/v2.11.2/hub-darwin-amd64-2.11.2.tgz -O /tmp/hub-darwin-amd64-2.11.2.tgz && tar -xvf /tmp/hub-darwin-amd64-2.11.2.tgz -C /usr/local/opt && ln -s /usr/local/opt/hub-darwin-amd64-2.11.2/bin/hub /usr/local/bin/hub
113113
# upgrade cocoapods
114-
- gem install cocoapods -v '1.8.0'
114+
- gem install cocoapods -v '1.9.3'
115115
script:
116116
- Scripts/run_prep.sh
117117
after_failure:
@@ -121,13 +121,13 @@ jobs:
121121
name: Push to cocoapods.org
122122
language: minimal
123123
os: osx
124-
osx_image: xcode11.3
124+
osx_image: xcode11.6
125125
env:
126126
- VERSION=3.7.0
127127
install:
128128
# install hub
129129
- wget https://github.com/github/hub/releases/download/v2.11.2/hub-darwin-amd64-2.11.2.tgz -O /tmp/hub-darwin-amd64-2.11.2.tgz && tar -xvf /tmp/hub-darwin-amd64-2.11.2.tgz -C /usr/local/opt && ln -s /usr/local/opt/hub-darwin-amd64-2.11.2/bin/hub /usr/local/bin/hub
130130
# upgrade cocoapods
131-
- gem install cocoapods -v '1.8.0'
131+
- gem install cocoapods -v '1.9.3'
132132
script:
133133
- Scripts/run_release.sh

OptimizelySwiftSDK.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2101,6 +2101,7 @@
21012101
6E75165F22C520D400B2B157 /* DefaultLogger.swift */,
21022102
6E75166022C520D400B2B157 /* DefaultUserProfileService.swift */,
21032103
6E75166122C520D400B2B157 /* DefaultEventDispatcher.swift */,
2104+
6E75167D22C520D400B2B157 /* DefaultDatafileHandler.swift */,
21042105
6E75166222C520D400B2B157 /* Protocols */,
21052106
);
21062107
path = Customization;
@@ -2112,6 +2113,7 @@
21122113
6E75166322C520D400B2B157 /* OPTLogger.swift */,
21132114
6E75166422C520D400B2B157 /* OPTUserProfileService.swift */,
21142115
6E75166522C520D400B2B157 /* OPTEventDispatcher.swift */,
2116+
6E7516A422C520D400B2B157 /* OPTDatafileHandler.swift */,
21152117
);
21162118
path = Protocols;
21172119
sourceTree = "<group>";
@@ -2170,7 +2172,6 @@
21702172
6E75167C22C520D400B2B157 /* Implementation */ = {
21712173
isa = PBXGroup;
21722174
children = (
2173-
6E75167D22C520D400B2B157 /* DefaultDatafileHandler.swift */,
21742175
6E623F01253F9045000617D0 /* DecisionInfo.swift */,
21752176
6E75167E22C520D400B2B157 /* DefaultBucketer.swift */,
21762177
6E75167F22C520D400B2B157 /* DefaultNotificationCenter.swift */,
@@ -2253,7 +2254,6 @@
22532254
6E7516A122C520D400B2B157 /* DataStoreQueueStack.swift */,
22542255
6E7516A222C520D400B2B157 /* OPTDataStore.swift */,
22552256
6E7516A322C520D400B2B157 /* OPTDecisionService.swift */,
2256-
6E7516A422C520D400B2B157 /* OPTDatafileHandler.swift */,
22572257
6E7516A522C520D400B2B157 /* OPTBucketer.swift */,
22582258
);
22592259
path = Protocols;

Sources/Implementation/DefaultDatafileHandler.swift renamed to Sources/Customization/DefaultDatafileHandler.swift

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2019-2020, Optimizely, Inc. and contributors *
2+
* Copyright 2019-2021, Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -15,7 +15,7 @@
1515
***************************************************************************/
1616
import Foundation
1717

18-
class DefaultDatafileHandler: OPTDatafileHandler {
18+
open class DefaultDatafileHandler: OPTDatafileHandler {
1919
// endpoint used to get the datafile. This is settable after you create a OptimizelyClient instance.
2020
public var endPointStringFormat = "https://cdn.optimizely.com/datafiles/%@.json"
2121

@@ -29,12 +29,12 @@ class DefaultDatafileHandler: OPTDatafileHandler {
2929
var datafileCache = [String: OPTDataStore]()
3030
// and our download queue to speed things up.
3131
let downloadQueue = DispatchQueue(label: "DefaultDatafileHandlerQueue")
32-
33-
required init() {
34-
32+
33+
public required init() {
34+
3535
}
3636

37-
func setTimer(sdkKey: String, interval: Int) {
37+
public func setPeriodicInterval(sdkKey: String, interval: Int) {
3838
timers.performAtomic { (timers) in
3939
if timers[sdkKey] == nil {
4040
timers[sdkKey] = (nil, interval)
@@ -43,7 +43,18 @@ class DefaultDatafileHandler: OPTDatafileHandler {
4343
}
4444
}
4545

46-
func downloadDatafile(sdkKey: String) -> Data? {
46+
public func hasPeriodicInterval(sdkKey: String) -> Bool {
47+
var result = true
48+
self.timers.performAtomic(atomicOperation: { (timers) in
49+
if !timers.contains(where: { $0.key == sdkKey}) {
50+
result = false
51+
}
52+
})
53+
54+
return result
55+
}
56+
57+
public func downloadDatafile(sdkKey: String) -> Data? {
4758

4859
var datafile: Data?
4960
let group = DispatchGroup()
@@ -152,6 +163,10 @@ class DefaultDatafileHandler: OPTDatafileHandler {
152163
}
153164
}
154165

166+
open func createDataStore(sdkKey: String) -> OPTDataStore {
167+
return DataStoreFile<Data>(storeName: sdkKey)
168+
}
169+
155170
func startPeriodicUpdates(sdkKey: String, updateInterval: Int, datafileChangeNotification: ((Data) -> Void)?) {
156171

157172
let now = Date()
@@ -179,17 +194,6 @@ class DefaultDatafileHandler: OPTDatafileHandler {
179194
}
180195
}
181196

182-
func hasPeriodUpdates(sdkKey: String) -> Bool {
183-
var restart = true
184-
self.timers.performAtomic(atomicOperation: { (timers) in
185-
if !timers.contains(where: { $0.key == sdkKey}) {
186-
restart = false
187-
}
188-
})
189-
190-
return restart
191-
}
192-
193197
func performPerodicDownload(sdkKey: String,
194198
startTime: Date,
195199
updateInterval: Int,
@@ -206,7 +210,7 @@ class DefaultDatafileHandler: OPTDatafileHandler {
206210
self.logger.e(error.reason)
207211
}
208212

209-
if self.hasPeriodUpdates(sdkKey: sdkKey) {
213+
if self.hasPeriodicInterval(sdkKey: sdkKey) {
210214
// adjust the next fire time so that events will be fired at fixed interval regardless of the download latency
211215
// if latency is too big (or returning from background mode), fire the next event immediately once
212216

@@ -242,45 +246,46 @@ class DefaultDatafileHandler: OPTDatafileHandler {
242246

243247
}
244248

245-
func startUpdates(sdkKey: String, datafileChangeNotification: ((Data) -> Void)?) {
249+
public func startUpdates(sdkKey: String, datafileChangeNotification: ((Data) -> Void)?) {
246250
if let value = timers.property?[sdkKey], !(value.timer?.isValid ?? false) {
247251
startPeriodicUpdates(sdkKey: sdkKey, updateInterval: value.interval, datafileChangeNotification: datafileChangeNotification)
248252
}
249253
}
250254

251-
func stopUpdates(sdkKey: String) {
255+
public func stopUpdates(sdkKey: String) {
252256
stopPeriodicUpdates(sdkKey: sdkKey)
253257
}
254258

255-
func stopAllUpdates() {
259+
public func stopAllUpdates() {
256260
stopPeriodicUpdates()
257261
}
258262

259263
func getDatafileCache(sdkKey: String) -> OPTDataStore {
260264
if let cache = datafileCache[sdkKey] {
261265
return cache
262266
} else {
263-
let store = DataStoreFile<Data>(storeName: sdkKey)
267+
let store = createDataStore(sdkKey: sdkKey)
264268
datafileCache[sdkKey] = store
265269
return store
266270
}
267271
}
268272

269-
func saveDatafile(sdkKey: String, dataFile: Data) {
273+
public func saveDatafile(sdkKey: String, dataFile: Data) {
270274
getDatafileCache(sdkKey: sdkKey).saveItem(forKey: sdkKey, value: dataFile)
271275
}
272276

273-
func loadSavedDatafile(sdkKey: String) -> Data? {
277+
public func loadSavedDatafile(sdkKey: String) -> Data? {
274278
return getDatafileCache(sdkKey: sdkKey).getItem(forKey: sdkKey) as? Data
275279
}
276280

277-
func isDatafileSaved(sdkKey: String) -> Bool {
281+
public func isDatafileSaved(sdkKey: String) -> Bool {
278282
return getDatafileCache(sdkKey: sdkKey).getItem(forKey: sdkKey) as? Data != nil
279283
}
280284

281-
func removeSavedDatafile(sdkKey: String) {
285+
public func removeSavedDatafile(sdkKey: String) {
282286
getDatafileCache(sdkKey: sdkKey).removeItem(forKey: sdkKey)
283287
}
288+
284289
}
285290

286291
extension DataStoreUserDefaults {

Sources/Protocols/OPTDatafileHandler.swift renamed to Sources/Customization/Protocols/OPTDatafileHandler.swift

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2019-2020, Optimizely, Inc. and contributors *
2+
* Copyright 2019-2021, Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -25,20 +25,37 @@ public protocol OPTDatafileHandler {
2525
init()
2626

2727
var endPointStringFormat: String { get set }
28+
29+
/**
30+
Save an interval for periodic polling
31+
32+
- Parameter sdkKey: sdk key of the datafile to download
33+
- Parameter interval: interval in secs (> 0)
34+
*/
35+
func setPeriodicInterval(sdkKey: String, interval: Int)
36+
37+
/**
38+
Check if periodic polling has been set
39+
40+
- Parameter sdkKey: sdk key of the datafile to download
41+
- Returns: true if set
42+
*/
43+
func hasPeriodicInterval(sdkKey: String) -> Bool
44+
2845
/**
2946
Synchronous call to download the datafile.
3047

3148
- Parameter sdkKey: sdk key of the datafile to download
32-
- Parameter datafileConfig: DatafileConfig for the datafile
3349
- Returns: a valid datafile or null
3450
*/
3551
func downloadDatafile(sdkKey: String) -> Data?
3652

3753
/**
3854
Asynchronous download data file.
3955
- Parameter sdkKey: application context for download
56+
- Parameter returnCacheIfNoChange: include a cached datafile in result when datafile not changed.
4057
- Parameter resourceTimeoutInterval: timeout in seconds to wait for resource.
41-
- Parameter completionHhandler: listener to call when datafile download complete
58+
- Parameter completionHandler: listener to call when datafile download is completed.
4259
*/
4360
func downloadDatafile(sdkKey: String,
4461
returnCacheIfNoChange: Bool,

Sources/Extensions/OptimizelyClient+Extension.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,32 +51,34 @@ extension OptimizelyClient {
5151
/// - sdkKey: sdk key
5252
/// - logger: custom Logger
5353
/// - eventDispatcher: custom EventDispatcher (optional)
54+
/// - datafileHandler: custom datafile handler (optional)
5455
/// - userProfileService: custom UserProfileService (optional)
55-
/// - periodicDownloadInterval: custom interval for periodic background datafile download.
56+
/// - periodicDownloadInterval: interval in secs for periodic background datafile download.
5657
/// The recommended value is 10 * 60 secs (you can also set this to nil to use the recommended value).
5758
/// Set this to 0 to disable periodic downloading.
5859
/// - defaultLogLevel: default log level (optional. default = .info)
5960
/// - defaultDecisionOptions: default decision optiopns (optional)
6061
public convenience init(sdkKey: String,
6162
logger: OPTLogger? = nil,
6263
eventDispatcher: OPTEventDispatcher? = nil,
64+
datafileHandler: OPTDatafileHandler? = nil,
6365
userProfileService: OPTUserProfileService? = nil,
6466
periodicDownloadInterval: Int?,
6567
defaultLogLevel: OptimizelyLogLevel? = nil,
6668
defaultDecideOptions: [OptimizelyDecideOption]? = nil) {
67-
let interval = periodicDownloadInterval ?? 10 * 60
6869

6970
self.init(sdkKey: sdkKey,
7071
logger: logger,
7172
eventDispatcher: eventDispatcher,
73+
datafileHandler: datafileHandler,
7274
userProfileService: userProfileService,
7375
defaultLogLevel: defaultLogLevel,
7476
defaultDecideOptions: defaultDecideOptions)
7577

76-
if let handler = datafileHandler as? DefaultDatafileHandler, interval > 0 {
77-
handler.setTimer(sdkKey: sdkKey, interval: interval)
78+
let interval = periodicDownloadInterval ?? 10 * 60
79+
if interval > 0 {
80+
self.currentDatafileHandler?.setPeriodicInterval(sdkKey: sdkKey, interval: interval)
7881
}
79-
8082
}
8183

8284
}

Sources/Implementation/Datastore/DataStoreFile.swift

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2019-2020, Optimizely, Inc. and contributors *
2+
* Copyright 2019-2021, Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -18,14 +18,14 @@ import Foundation
1818

1919
/// Implementation of OPTDataStore as a generic for per type storeage in a flat file.
2020
/// This class should be used as a singleton per storeName and type (T)
21-
public class DataStoreFile<T>: OPTDataStore where T: Codable {
21+
open class DataStoreFile<T>: OPTDataStore where T: Codable {
2222
let dataStoreName: String
2323
let lock: DispatchQueue
2424
let async: Bool
2525
public let url: URL
2626
lazy var logger: OPTLogger? = OPTLoggerFactory.getLogger()
2727

28-
init(storeName: String, async: Bool = true) {
28+
public init(storeName: String, async: Bool = true) {
2929
self.async = async
3030
dataStoreName = storeName
3131
lock = DispatchQueue(label: storeName)
@@ -56,7 +56,7 @@ public class DataStoreFile<T>: OPTDataStore where T: Codable {
5656
return
5757
}
5858

59-
let contents = try Data(contentsOf: self.url)
59+
let contents = try self.readData()
6060

6161
if type(of: T.self) == type(of: Data.self) {
6262
returnItem = contents as? T
@@ -107,7 +107,7 @@ public class DataStoreFile<T>: OPTDataStore where T: Codable {
107107
}
108108

109109
if let data = data {
110-
try data.write(to: self.url, options: .atomic)
110+
try self.writeData(data)
111111
}
112112
}
113113
} catch let e {
@@ -126,4 +126,14 @@ public class DataStoreFile<T>: OPTDataStore where T: Codable {
126126
}
127127

128128
}
129+
130+
// Read Data contents from the URL
131+
open func readData() throws -> Data {
132+
return try Data(contentsOf: self.url)
133+
}
134+
135+
// Write Data to the URL
136+
open func writeData(_ data: Data) throws {
137+
try data.write(to: self.url, options: .atomic)
138+
}
129139
}

0 commit comments

Comments
 (0)