Swift SDK for integrating with an Optable Data Connectivity Node (DCN) from an iOS application.
You can use the SDK functionality from either a Swift or Objective-C iOS application.
- Installing
- Using (Swift)
- Using (Objective-C)
- Identifying visitors arriving from Email newsletters
- Demo Applications
This SDK can be installed via the CocoaPods dependency manager. To install the latest release, you need to source the optable-cocoapods private repository as well as the OptableSDK
pod from your Podfile
:
platform :ios, '13.0'
source 'git@github.com:Optable/optable-cocoapods.git'
source 'https://cdn.cocoapods.org/'
...
target 'YourProject' do
use_frameworks!
pod 'OptableSDK'
...
end
You can then run pod install
to download all of your dependencies and prepare your project xcworkspace
.
If you would like to reference a specific release, simply append it to the referenced pod. For example:
pod 'OptableSDK', '0.8.2'
To configure an instance of the SDK integrating with an Optable DCN running at hostname dcn.customer.com
, from a configured Swift application origin identified by slug my-app
, you simply create an instance of the OptableSDK
class through which you can communicate to the DCN. For example, from your AppDelegate
:
import OptableSDK
import UIKit
...
var OPTABLE: OptableSDK?
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
OPTABLE = OptableSDK(host: "dcn.customer.com", app: "my-app")
...
return true
}
...
}
Note that while the OPTABLE
variable is global, we initialize it with an instance of OptableSDK
in the application()
method which runs at app launch, and not at the time it is declared. This is done because Swift's lazy-loading otherwise delays initialization to the first use of the variable. Both approaches work, though forcing early initialization allows the SDK to configure itself early. In particular, as part of its internal configuration the SDK will attempt to read the User-Agent string exposed by WebView and, since this is an asynchronous operation, it is best done as early as possible in the application lifecycle.
You can call various SDK APIs on the instance as shown in the examples below. It's also possible to configure multiple instances of OptableSDK
in order to connect to other (e.g., partner) DCNs and/or reference other configured application slug IDs.
Note that all SDK communication with Optable DCNs is done over TLS. The only exception to this is if you instantiate the OptableSDK
class with a third optional boolean parameter, insecure
, set to true
. For example:
OPTABLE = OptableSDK(host: "dcn.customer.com", app: "my-app", insecure: true)
Note that production DCNs only listen to TLS traffic. The insecure: true
option is meant to be used by Optable developers running the DCN locally for testing.
By default, the SDK detects the application user agent by sniffing navigator.userAgent
from a WKWebView
. The resulting user agent string is sent to your DCN for analytics purposes. To disable this behavior, you can provide an optional fourth string parameter, useragent
, which allows you to set whatever user agent string you would like to send instead. For example:
OPTABLE = OptableSDK(host: "dcn.customer.com", app: "my-app", insecure: false, useragent: "custom-ua")
The default value of nil
for the useragent
parameter enables the WKWebView
auto-detection behavior.
To associate a user device with an authenticated identifier such as an Email address, or with other known IDs such as the Apple ID for Advertising (IDFA), or even your own vendor or app level PPID
, you can call the identify
API as follows:
let emailString = "some.email@address.com"
let sendIDFA = true
do {
try OPTABLE!.identify(email: emailString, aaid: sendIDFA) { result in
switch (result) {
case .success(let response):
// identify API success, response.statusCode is HTTP response status 200
case .failure(let error):
// handle identify API failure in `error`
}
}
} catch {
// handle thrown exception in `error`
}
The SDK identify()
method will asynchronously connect to the configured DCN and send IDs for resolution. The provided callback can be used to understand successful completion or errors.
⚠️ Client-Side Email Hashing: The SDK will compute the SHA-256 hash of the Email address on the client-side and send the hashed value to the DCN. The Email address is not sent by the device in plain text.
Since the sendIDFA
value provided to identify()
via the aaid
(Apple Advertising ID or IDFA) boolean parameter is true
, the SDK will attempt to fetch and send the Apple IDFA in the call to identify
too, unless the user has turned on "Limit ad tracking" in their iOS device privacy settings.
⚠️ As of iOS 14.0, Apple has introduced additional restrictions on IDFA which will require prompting users to request permission to use IDFA. Therefore, if you intend to setaaid
totrue
in calls toidentify()
on iOS 14.0 or above, you should expect that the SDK will automatically trigger a user prompt via theAppTrackingTransparency
framework before it is permitted to send the IDFA value to your DCN. Additionally, we recommend that you ensure to configure the Privacy - Tracking Usage Description attribute string in your application'sInfo.plist
, as it enables you to customize some elements of the resulting user prompt.
The frequency of invocation of identify
is up to you, however for optimal identity resolution we recommended to call the identify()
method on your SDK instance every time you authenticate a user, as well as periodically, such as for example once every 15 to 60 minutes while the application is being actively used and an internet connection is available.
To associate key value traits with the device, for eventual audience assembly, you can call the profile API as follows:
do {
try OPTABLE!.profile(traits: ["gender": "F", "age": 38, "hasAccount": true]) { result in
switch (result) {
case .success(let response):
// profile API success, response.statusCode is HTTP response status 200
case .failure(let error):
// handle profile API failure in `error`
}
}
} catch {
// handle thrown exception in `error`
}
The specified traits are associated with the user's device and can be matched during audience assembly.
Note that the traits are of type NSDictionary
and should consist of key value pairs, where the keys are strings and the values are either strings, numbers, or booleans.
To get the targeting key values associated by the configured DCN with the device in real-time, you can call the targeting
API as follows:
do {
try OPTABLE!.targeting() { result in
switch result {
case .success(let keyvalues):
// keyvalues is an NSDictionary containing targeting key-values that can be
// passed on to ad servers or other decisioning systems
case .failure(let error):
// handle targeting API failure in `error`
}
}
} catch {
// handle thrown exception in `error`
}
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.
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:
let cachedTargetingData = OPTABLE!.targetingFromCache()
if (cachedTargetingData != nil) {
// cachedTargetingData! is an NSDictionary which you can cast as! [String: Any]
}
You can also clear the locally cached targeting data:
OPTABLE!.targetingClearCache()
Note that both targetingFromCache()
and targetingClearCache()
are synchronous.
To send real-time event data from the user's device to the DCN for eventual audience assembly, you can call the witness API as follows:
do {
try OPTABLE!.witness(event: "example.event.type",
properties: ["example": "value"]) { result in
switch (result) {
case .success(let response):
// witness API success, response.statusCode is HTTP response status 200
case .failure(let error):
// handle witness API failure in `error`
}
}
} catch {
// handle thrown exception in `error`
}
The specified event type and properties are associated with the logged event and which can be used for matching during audience assembly.
Note that event properties are of type NSDictionary
and should consist of key value pairs, where the keys are strings and the values are either strings, numbers, or booleans.
We can further extend the above targeting
example to show an integration with a Google Ad Manager 360 ad server account.
It's suggested to load the GAM banner view with an ad even when the call to your DCN targeting()
method results in failure:
import GoogleMobileAds
...
do {
try OPTABLE!.targeting() { result in
var tdata: NSDictionary = [:]
switch result {
case .success(let keyvalues):
// Save targeting data in `tdata`:
tdata = keyvalues
case .failure(let error):
// handle targeting API failure in `error`
}
// We assume bannerView is a DFPBannerView() instance that has already been
// initialized and added to our view:
bannerView.adUnitID = "/12345/some-ad-unit-id/in-your-gam360-account"
// Build GAM ad request with key values and load banner:
let req = DFPRequest()
req.customTargeting = tdata as! [String: Any]
bannerView.load(req)
}
} catch {
// handle thrown exception in `error`
}
A working example is available in the demo application.
Configuring an instance of the OptableSDK
from an Objective-C application is similar to the above Swift example, except that the caller should set up an OptableDelegate
protocol delegate. The first step is to implement the delegate itself, for example, in an OptableSDKDelegate.h
:
@import OptableSDK;
@interface OptableSDKDelegate: NSObject <OptableDelegate>
@end
And in the accompanying OptableSDKDelegate.m
follows a simple implementation of the delegate calling NSLog()
:
#import "OptableSDKDelegate.h"
@import OptableSDK;
@interface OptableSDKDelegate ()
@end
@implementation OptableSDKDelegate
- (void)identifyOk:(NSHTTPURLResponse *)result {
NSLog(@"Success on identify API call. HTTP Status Code: %ld", result.statusCode);
}
- (void)identifyErr:(NSError *)error {
NSLog(@"Error on identify API call: %@", [error localizedDescription]);
}
- (void)profileOk:(NSHTTPURLResponse *)result {
NSLog(@"Success on profile API call. HTTP Status Code: %ld", result.statusCode);
}
- (void)profileErr:(NSError *)error {
NSLog(@"Error on profile API call: %@", [error localizedDescription]);
}
- (void)targetingOk:(NSDictionary *)result {
NSLog(@"Success on targeting API call: %@", result);
}
- (void)targetingErr:(NSError *)error {
NSLog(@"Error on targeting API call: %@", [error localizedDescription]);
}
- (void)witnessOk:(NSHTTPURLResponse *)result {
NSLog(@"Success on witness API call. HTTP Status Code: %ld", result.statusCode);
}
- (void)witnessErr:(NSError *)error {
NSLog(@"Error on witness API call: %@", [error localizedDescription]);
}
@end
You can then configure an instance of the SDK integrating with an Optable DCN running at hostname dcn.customer.com
, from a configured origin identified by slug my-app
from your main AppDelegate.m
, and point it to your delegate implementation as in the following example:
#import "OptabletSDKDelegate.h"
@import OptableSDK;
OptableSDK *OPTABLE = nil;
...
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
OPTABLE = [[OptableSDK alloc] initWithHost: @"dcn.optable.co"
app: @"ios-sdk-demo"
insecure: NO
useragent: nil];
OptableSDKDelegate *delegate = [[OptableSDKDelegate alloc] init];
OPTABLE.delegate = delegate;
...
}
@end
You can call various SDK APIs on the instance as shown in the examples below. It's also possible to configure multiple instances of OptableSDK
in order to connect to other (e.g., partner) DCNs and/or reference other configured application slug IDs. Note that the insecure
flag should always be set to NO
unless you are testing a local instance of the DCN yourself.
You can disable user agent WKWebView
based auto-detection and provide your own value by setting the useragent
parameter to a string value, similar to the Swift example.
To associate a user device with an authenticated identifier such as an Email address, or with other known IDs such as the Apple ID for Advertising (IDFA), or even your own vendor or app level PPID
, you can call the identify
API as follows:
@import OptableSDK;
...
NSError *error = nil;
[OPTABLE identify :@"some.email@address.com" aaid:YES ppid:@"" error:&error];
Note that error
will be set only in case of an internal SDK exception. Otherwise, any configured delegate identifyOk
or identifyErr
will be invoked to signal success or failure, respectively. Providing an empty ppid
as in the above example simply will not send any ppid
.
⚠️ As of iOS 14.0, Apple has introduced additional restrictions on IDFA which will require prompting users to request permission to use IDFA. Therefore, if you intend to setaaid
toYES
in calls toidentify
on iOS 14.0 or above, you should expect that the SDK will automatically trigger a user prompt via theAppTrackingTransparency
framework before it is permitted to send the IDFA value to your DCN. Additionally, we recommend that you ensure to configure the Privacy - Tracking Usage Description attribute string in your application'sInfo.plist
, as it enables you to customize some elements of the resulting user prompt.
It's also possible to send only an Email ID hash or a custom PPID by using the lower-level identify
method which accepts a list of pre-constructed identifiers, for example:
@import OptableSDK;
...
NSError *error = nil;
[OPTABLE identify :@[[OPTABLE cid:@"xyz123abc"],
[OPTABLE eid:@"some.email@address.com" ]] error:&error];
To associate key value traits with the device, for eventual audience assembly, you can call the profile API as follows:
@import OptableSDK;
...
NSError *error = nil;
[OPTABLE profileWithTraits:@{ @"gender": @"F", @"age": @38, @"hasAccount": @YES } error:&error];
To get the targeting key values associated by the configured DCN with the device in real-time, you can call the targeting
API and expect that on success, the resulting keyvalues to be used for targeting will be sent in the targetingOk
message to your delegate (see the example delegate implementation above):
@import OptableSDK;
...
NSError *error = nil;
[OPTABLE targetingAndReturnError:&error];
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:
@import OptableSDK;
...
NSDictionary *cachedTargetingData = nil;
cachedTargetingData = [OPTABLE targetingFromCache];
if (cachedTargetingData != nil) {
// cachedTargetingData! is an NSDictionary
}
You can also clear the locally cached targeting data:
@import OptableSDK;
...
[OPTABLE targetingClearCache];
Note that both targetingFromCache
and targetingClearCache
are synchronous.
To send real-time event data from the user's device to the DCN for eventual audience assembly, you can call the witness API as follows:
@import OptableSDK;
...
NSError *error = nil;
[OPTABLE witness:@"example.event.type" properties:@{ @"example": @"value", @"example2": @123, @"example3": @NO } error:&error];
We can further extend the above targetingOk
example delegate implementation to show an integration with a Google Ad Manager 360 ad server account, which uses the Google Mobile Ads SDK's targeting capability.
We also extend the targetingErr
delegate handler to load a GAM ad without targeting data in case of targeting
API failure.
@implementation OptableSDKDelegate
...
- (void)targetingOk:(NSDictionary *)result {
// Update the GAM banner view with result targeting keyvalues:
DFPRequest *request = [DFPRequest request];
request.customTargeting = result;
[self.bannerView loadRequest:request];
}
- (void)targetingErr:(NSError *)error {
// Load GAM banner even in case of targeting API error:
DFPRequest *request = [DFPRequest request];
[self.bannerView loadRequest: request];
}
...
@end
It's assumed in the above code snippet that self.bannerView
is a pointer to a DFPBannerView
instance which resides in your delegate and which has already been initialized and configured by a view controller.
If you send Email newsletters that contain links to your application (e.g., universal links), then you may want to automatically identify visitors that have clicked on any such links via their Email address.
To enable automatic identification of visitors originating from your Email newsletter, you first need to include an oeid parameter in the query string of all links to your website in your Email newsletter template. The value of the oeid parameter should be set to the SHA256 hash of the lowercased Email address of the recipient. For example, if you are using Braze to send your newsletters, you can easily encode the SHA256 hash value of the recipient's Email address by setting the oeid parameter in the query string of any links to your application as follows:
oeid={{${email_address} | downcase | sha2}}
The above example uses various personalization tags as documented in Braze's user guide to dynamically insert the required data into an oeid parameter, all of which should make up a part of the destination URL in your template.
In order for your application to open on devices where it is installed when a link to your domain is clicked, you need to configure and prepare your application to handle universal links first.
When iOS launches your app after a user taps a universal link, you receive an NSUserActivity
object with an activityType
value of NSUserActivityTypeBrowsingWeb
. The activity object's webpageURL
property contains the URL that the user is accessing. You can then pass it to the SDK's tryIdentifyFromURL()
API which will automatically look for oeid
in the query string of the URL and call identify
with its value if found.
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
let url = userActivity.webpageURL!
try OPTABLE!.tryIdentifyFromURL(url)
}
...
}
-(BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {
if ([userActivity.activityType isEqualToString: NSUserActivityTypeBrowsingWeb]) {
NSURL *url = userActivity.webpageURL;
NSError *error = nil;
[OPTABLE tryIdentifyFromURL :url.absoluteString error:&error];
...
}
...
}
The Swift and Objective-C demo applications show a working example of identify
, targeting
, and witness
APIs, as well as an integration with the Google Ad Manager 360 ad server, enabling the targeting of ads served by GAM360 to audiences activated in the Optable DCN.
By default, the demo applications will connect to the Optable demo DCN at sandbox.optable.co
and reference application slug android-sdk-demo
. The demo apps depend on the GAM Mobile Ads SDK for iOS and load ads from a GAM360 account operated by Optable.
Cocoapods is required to build the demo-ios-swift
and demo-ios-objc
applications. After cloning the repo, simply cd
into either of the two demo app directories and run:
pod install
Then open the generated demo-ios-swift.xcworkspace
or demo-ios-objc.xcworkspace
in Xcode, and build and run from there.