Skip to content

Commit ac1a7f3

Browse files
authored
targetingFromCache and targetingClearCache APIs (#19)
- targeting API now also caches returned keyvalues to client storage - targetingFromCache will fetch previously cached keyvalues from client storage. Contrary to the targeting API, it does not block and is synchronous. - targetingClearCache will clear any previously cached keyvalues - Update demo-ios-{objc,swift} to show usage of targetingFromCache - Update demo-ios-{objc,swift} to show usage of targetingClearCache - Update README with targeting*Cache docs
1 parent 53b91e8 commit ac1a7f3

13 files changed

Lines changed: 226 additions & 37 deletions

File tree

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,25 @@ do {
144144

145145
On success, the resulting key values are typically sent as part of a subsequent ad call. Therefore we recommend that you either call `targeting()` before each ad call, or in parallel periodically, caching the resulting key values which you then provide in ad calls.
146146

147+
#### Caching Targeting Data
148+
149+
The `targeting` API will automatically cache resulting key value data in client storage on success. You can subsequently retrieve the cached key value data as follows:
150+
151+
```swift
152+
let cachedTargetingData = OPTABLE!.targetingFromCache()
153+
if (cachedTargetingData != nil) {
154+
// cachedTargetingData! is an NSDictionary which you can cast as! [String: Any]
155+
}
156+
```
157+
158+
You can also clear the locally cached targeting data:
159+
160+
```swift
161+
OPTABLE!.targetingClearCache()
162+
```
163+
164+
Note that both `targetingFromCache()` and `targetingClearCache()` are synchronous.
165+
147166
### Witness API
148167

149168
To send real-time event data from the user's device to the sandbox for eventual audience assembly, you can call the witness API as follows:
@@ -310,6 +329,30 @@ NSError *error = nil;
310329
[OPTABLE targetingAndReturnError:&error];
311330
```
312331
332+
#### Caching Targeting Data
333+
334+
The `targetingAndReturnError` method will automatically cache resulting key value data in client storage on success. You can subsequently retrieve the cached key value data as follows:
335+
336+
```objective-c
337+
@import OptableSDK;
338+
...
339+
NSDictionary *cachedTargetingData = nil;
340+
cachedTargetingData = [OPTABLE targetingFromCache];
341+
if (cachedTargetingData != nil) {
342+
// cachedTargetingData! is an NSDictionary
343+
}
344+
```
345+
346+
You can also clear the locally cached targeting data:
347+
348+
```objective-c
349+
@import OptableSDK;
350+
...
351+
[OPTABLE targetingClearCache];
352+
```
353+
354+
Note that both `targetingFromCache` and `targetingClearCache` are synchronous.
355+
313356
### Witness API
314357

315358
To send real-time event data from the user's device to the sandbox for eventual audience assembly, you can call the witness API as follows:

Source/Core/LocalStorage.swift

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@
1212
import Foundation
1313

1414
class LocalStorage: NSObject {
15-
let keyPfx: String = "OPTABLE_"
15+
let keyPfx: String = "OPTABLE"
1616
var passportKey: String
17+
var targetingKey: String
1718

1819
init(_ config: Config) {
19-
// The key used for storing the passport should be unique to the host+app that this instance was initialized with:
20-
let utf8str = (config.host + "/" + config.app).data(using: .utf8)
21-
self.passportKey = self.keyPfx + utf8str!.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0))
20+
// The key used for storage should be unique to the host+app that this instance was initialized with:
21+
let utf8str = (config.host + "/" + config.app).data(using: .utf8)!.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0))
22+
23+
self.passportKey = self.keyPfx + "_PASS_" + utf8str
24+
self.targetingKey = self.keyPfx + "_TGT_" + utf8str
2225
}
2326

2427
func getPassport() -> String? {
@@ -27,5 +30,17 @@ class LocalStorage: NSObject {
2730

2831
func setPassport(_ passport: String) -> Void {
2932
UserDefaults.standard.set(passport, forKey: passportKey)
30-
}
33+
}
34+
35+
func getTargeting() -> [String: Any]? {
36+
return UserDefaults.standard.dictionary(forKey: targetingKey)
37+
}
38+
39+
func setTargeting(_ keyvalues: [String: Any]) -> Void {
40+
UserDefaults.standard.setValue(keyvalues, forKey: targetingKey)
41+
}
42+
43+
func clearTargeting() -> Void {
44+
UserDefaults.standard.removeObject(forKey: targetingKey)
45+
}
3146
}

Source/Edge/Targeting.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
// Targeting.swift
33
// OptableSDK
44
//
5-
// Created by Bosko Milekic on 2020-08-26.
5+
// Copyright © 2020 Optable Technologies, Inc. All rights reserved.
6+
// See LICENSE for details.
67
//
78

89
import Foundation

Source/OptableSDK.swift

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ public class OptableSDK: NSObject {
170170
// The targeting method is asynchronous, and on completion it will call the specified completion handler,
171171
// passing it either the NSDictionary targeting data on success, or an OptableError on failure.
172172
//
173+
// On success, this method will also cache the resulting targeting data in client storage, which can
174+
// be access using targetingFromCache(), and cleared using targetingClearCache().
175+
//
173176
public func targeting(_ completion: @escaping (Result<NSDictionary,OptableError>) -> Void) throws -> Void {
174177
try Targeting(config: self.config, client: self.client) { (data, response, error) in
175178
guard let response = response as? HTTPURLResponse, error == nil else {
@@ -187,7 +190,12 @@ public class OptableSDK: NSObject {
187190
}
188191

189192
do {
190-
let result = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary
193+
let keyvalues = try JSONSerialization.jsonObject(with: data!, options: [])
194+
let result = keyvalues as? NSDictionary
195+
196+
// We cache the latest targeting result in client storage for targetingFromCache() users:
197+
self.client.storage.setTargeting(keyvalues as! [String: Any])
198+
191199
completion(.success(result!))
192200
} catch {
193201
completion(.failure(OptableError.targeting("Error parsing JSON response: \(error)")))
@@ -213,6 +221,26 @@ public class OptableSDK: NSObject {
213221
}
214222
}
215223

224+
//
225+
// targetingFromCache() returns the previously cached targeting data, if any.
226+
//
227+
@objc
228+
public func targetingFromCache() -> NSDictionary? {
229+
let keyvalues = self.client.storage.getTargeting()
230+
if (keyvalues == nil) {
231+
return nil
232+
}
233+
return (keyvalues! as NSDictionary)
234+
}
235+
236+
//
237+
// targetingClearCache() clears any previously cached targeting data.
238+
//
239+
@objc
240+
public func targetingClearCache() -> Void {
241+
self.client.storage.clearTargeting()
242+
}
243+
216244
//
217245
// witness(event, properties, completion) calls the Optable Sandbox "witness" API in order to log
218246
// a specified 'event' (e.g., "app.screenView", "ui.buttonPressed"), with the specified keyvalue

demo-ios-objc/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
PODS:
2-
- Google-Mobile-Ads-SDK (7.67.1):
2+
- Google-Mobile-Ads-SDK (7.68.0):
33
- GoogleAppMeasurement (~> 7.0)
44
- GoogleUserMessagingPlatform (~> 1.1)
55
- GoogleAppMeasurement (7.0.0):
@@ -52,7 +52,7 @@ EXTERNAL SOURCES:
5252
:path: "../"
5353

5454
SPEC CHECKSUMS:
55-
Google-Mobile-Ads-SDK: 4c11bba9a823ed16c42f14d42feebc7678fbfd6a
55+
Google-Mobile-Ads-SDK: 29bbdb182d69ff606cc0301da1590b40be8d2205
5656
GoogleAppMeasurement: 7790ef975d1d463c8614cd949a847e612edf087a
5757
GoogleUserMessagingPlatform: 1d4b6946710d18cec34742054092e2c2bddae61f
5858
GoogleUtilities: ffb2f4159f2c897c6e8992bd7fbcdef8a300589c

demo-ios-objc/demo-ios-objc/Base.lproj/Main.storyboard

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="9KB-Po-ioE">
33
<device id="retina6_1" orientation="portrait" appearance="light"/>
44
<dependencies>
5-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
5+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17126"/>
66
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
77
<capability name="System colors in document resources" minToolsVersion="11.0"/>
88
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@@ -43,7 +43,7 @@
4343
<rect key="frame" x="0.0" y="138" width="374" height="30"/>
4444
<state key="normal" title="Identify"/>
4545
<connections>
46-
<action selector="dispatchIdentify:" destination="BYZ-38-t0r" eventType="touchUpInside" id="Sae-rS-iHh"/>
46+
<action selector="dispatchIdentify:" destination="BYZ-38-t0r" eventType="touchUpInside" id="CNy-Db-Mwd"/>
4747
</connections>
4848
</button>
4949
</subviews>
@@ -92,30 +92,52 @@
9292
<subviews>
9393
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="x4k-D7-Mvh">
9494
<rect key="frame" x="50" y="94" width="314" height="30"/>
95-
<state key="normal" title="Load Banner"/>
95+
<state key="normal" title="Load Targeting and Banner"/>
9696
<connections>
97-
<action selector="loadBannerWithTargeting:" destination="qnL-ha-uwz" eventType="touchUpInside" id="hBs-SW-Fgz"/>
97+
<action selector="loadBannerWithTargeting:" destination="qnL-ha-uwz" eventType="touchUpInside" id="2sa-1R-eGT"/>
9898
</connections>
9999
</button>
100100
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" fixedFrame="YES" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="c00-PY-Cdm">
101-
<rect key="frame" x="50" y="157" width="314" height="370"/>
101+
<rect key="frame" x="50" y="210" width="314" height="401"/>
102102
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
103103
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
104104
<color key="textColor" systemColor="labelColor"/>
105105
<fontDescription key="fontDescription" type="system" pointSize="14"/>
106106
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
107107
</textView>
108+
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hXv-1T-mbc">
109+
<rect key="frame" x="50" y="126" width="314" height="30"/>
110+
<state key="normal" title="Cached Targeting and Banner"/>
111+
<connections>
112+
<action selector="loadBannerWithTargetingFromCache:" destination="qnL-ha-uwz" eventType="touchUpInside" id="dWK-eA-gis"/>
113+
</connections>
114+
</button>
115+
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="1Jw-Ws-T0T">
116+
<rect key="frame" x="50" y="158" width="314" height="30"/>
117+
<state key="normal" title="Clear Targeting Cache"/>
118+
<connections>
119+
<action selector="clearTargetingCache:" destination="qnL-ha-uwz" eventType="touchUpInside" id="Ll8-bx-lvM"/>
120+
</connections>
121+
</button>
108122
</subviews>
109123
<viewLayoutGuide key="safeArea" id="Cyy-8b-GCg"/>
110124
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
111125
<constraints>
126+
<constraint firstItem="1Jw-Ws-T0T" firstAttribute="top" secondItem="hXv-1T-mbc" secondAttribute="bottom" constant="2" id="C0X-HG-eYe"/>
127+
<constraint firstItem="hXv-1T-mbc" firstAttribute="top" secondItem="x4k-D7-Mvh" secondAttribute="bottom" constant="2" id="D1p-oM-m1D"/>
128+
<constraint firstItem="1Jw-Ws-T0T" firstAttribute="leading" secondItem="Cyy-8b-GCg" secondAttribute="leading" constant="50" id="Kbz-TZ-d9K"/>
129+
<constraint firstItem="Cyy-8b-GCg" firstAttribute="trailing" secondItem="hXv-1T-mbc" secondAttribute="trailing" constant="50" id="Qrd-np-QV1"/>
130+
<constraint firstItem="hXv-1T-mbc" firstAttribute="leading" secondItem="Cyy-8b-GCg" secondAttribute="leading" constant="50" id="XW9-qT-dp9"/>
112131
<constraint firstItem="x4k-D7-Mvh" firstAttribute="top" secondItem="Cyy-8b-GCg" secondAttribute="top" constant="50" id="djs-hu-hJD"/>
132+
<constraint firstItem="Cyy-8b-GCg" firstAttribute="trailing" secondItem="1Jw-Ws-T0T" secondAttribute="trailing" constant="50" id="haI-Rw-Lg7"/>
113133
<constraint firstItem="Cyy-8b-GCg" firstAttribute="trailing" secondItem="x4k-D7-Mvh" secondAttribute="trailing" constant="50" id="mJB-PI-zyh"/>
114134
<constraint firstItem="x4k-D7-Mvh" firstAttribute="leading" secondItem="Cyy-8b-GCg" secondAttribute="leading" constant="50" id="wPd-LN-GBn"/>
115135
</constraints>
116136
</view>
117137
<tabBarItem key="tabBarItem" title="GAM Banner" image="rectangle.3.offgrid.fill" catalog="system" selectedImage="rectangle.3.offgrid.fill" id="WaP-P0-dwN"/>
118138
<connections>
139+
<outlet property="cachedBannerButton" destination="hXv-1T-mbc" id="G6q-wE-nPC"/>
140+
<outlet property="clearTargetingCacheButton" destination="1Jw-Ws-T0T" id="Kk2-ip-LUl"/>
119141
<outlet property="loadBannerButton" destination="x4k-D7-Mvh" id="Yre-Zn-37w"/>
120142
<outlet property="targetingOutput" destination="c00-PY-Cdm" id="qBw-qG-iVR"/>
121143
</connections>

demo-ios-objc/demo-ios-objc/GAMBannerViewController.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010

1111
@interface GAMBannerViewController : UIViewController
1212
@property (weak, nonatomic) IBOutlet UIButton *loadBannerButton;
13+
@property (weak, nonatomic) IBOutlet UIButton *cachedBannerButton;
14+
@property (weak, nonatomic) IBOutlet UIButton *clearTargetingCacheButton;
1315
@property (weak, nonatomic) IBOutlet UITextView *targetingOutput;
1416

1517
- (IBAction)loadBannerWithTargeting:(id)sender;
1618
@end
17-

demo-ios-objc/demo-ios-objc/GAMBannerViewController.m

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ - (void)viewDidLoad {
3131
delegate.targetingOutput = self.targetingOutput;
3232
}
3333

34-
- (void)loadBannerWithTargeting:(id)sender {
34+
- (IBAction)loadBannerWithTargeting:(id)sender {
3535
NSError *error = nil;
3636

3737
[_targetingOutput setText:@"Calling /targeting API...\n\n"];
@@ -40,6 +40,33 @@ - (void)loadBannerWithTargeting:(id)sender {
4040
[OPTABLE witness:@"GAMBannerViewController.loadBannerClicked" properties:@{ @"example": @"value" } error:&error];
4141
}
4242

43+
- (IBAction)loadBannerWithTargetingFromCache:(id)sender {
44+
NSError *error = nil;
45+
DFPRequest *request = [DFPRequest request];
46+
NSDictionary *keyvals = nil;
47+
48+
[_targetingOutput setText:@"Checking local targeting cache...\n\n"];
49+
50+
keyvals = [OPTABLE targetingFromCache];
51+
52+
if (keyvals != nil) {
53+
request.customTargeting = keyvals;
54+
NSLog(@"[OptableSDK] Cached targeting values found: %@", keyvals);
55+
[_targetingOutput setText:[NSString stringWithFormat:@"%@\nFound cached data: %@\n", [_targetingOutput text], keyvals]];
56+
} else {
57+
[_targetingOutput setText:[NSString stringWithFormat:@"%@\nCache empty.\n",
58+
[_targetingOutput text]]];
59+
}
60+
61+
[self.bannerView loadRequest:request];
62+
[OPTABLE witness:@"GAMBannerViewController.loadBannerClicked" properties:@{ @"example": @"value" } error:&error];
63+
}
64+
65+
- (IBAction)clearTargetingCache:(id)sender {
66+
[_targetingOutput setText:@"Clearing local targeting cache.\n\n"];
67+
[OPTABLE targetingClearCache];
68+
}
69+
4370
- (void)addBannerViewToView:(UIView *)bannerView {
4471
bannerView.translatesAutoresizingMaskIntoConstraints = NO;
4572
[self.view addSubview:bannerView];

demo-ios-objc/demo-ios-objc/IdentifyViewController.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,3 @@
1616

1717
- (IBAction)dispatchIdentify:(id)sender;
1818
@end
19-

demo-ios-objc/demo-ios-objc/IdentifyViewController.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ - (void)viewDidLoad {
2323
delegate.identifyOutput = self.identifyOutput;
2424
}
2525

26-
- (void)dispatchIdentify:(id)sender {
26+
- (IBAction)dispatchIdentify:(id)sender {
2727
NSString *email = [_identifyInput text];
2828
bool aaid = [_identifyIDFA isOn];
2929
NSMutableString *output;

0 commit comments

Comments
 (0)