Commit 52c14f88 by Demid Merzlyakov

Merge branch 'feature/IOS-127/tercept_sdk_integration' into develop

# Conflicts:
#	1Weather.xcodeproj/project.pbxproj
#	1Weather/AppDelegate.swift
#	1Weather/Network/Configuration/ConfigManager.swift
#	CoreDataStorage/CoreDataStorage.xcodeproj/project.pbxproj
parents ce28bbd2 f3a8409c
......@@ -28,6 +28,16 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F2D8A742269FFDC60060F36A"
BuildableName = "1WeatherTests.xctest"
BlueprintName = "1WeatherTests"
ReferencedContainer = "container:1Weather.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
......
......@@ -80,8 +80,16 @@ public class AdView: UIView {
guard let placementName = self.placementName else { return nil }
return configManager.adConfig.placement(named: placementName)
}
private lazy var terceptManager: TerceptSDKManager = {
return Dependency.shared.getDependency()
}()
private var adTracker : CustomTargetedAdLifeCycleTrackable? {
guard let adUnitID = placement?.adUnitId else { return nil }
return terceptManager.getTracker(with: adUnitID)
}
internal var configManager: AdConfigManager {
return AdConfigManager.shared
}
......@@ -138,8 +146,7 @@ public class AdView: UIView {
log.warning("failed to guess the top view controller.")
return
}
bannerView = NativeBannerContainerView(adUnitId: placement.adUnitId, adType: adType, rootViewController: topViewController)
bannerView = NativeBannerContainerView(adUnitId: placement.adUnitId, adType: adType, adLifeCycleTracker: adTracker, rootViewController: topViewController)
if let bannerView = bannerView {
bannerView.loggingAlias = logName
......@@ -328,6 +335,14 @@ extension AdView {
self.updateA9BidObject(requestAdWhenDone: false)
}
let addTrackerKeys = adTracker?.getCustomTrackableKeys()
if addTrackerKeys?.isEmpty == false {
keywords = keywords ?? [:]
for keyValue in addTrackerKeys ?? [:] {
keywords?[keyValue.key] = keyValue.value
}
}
return keywords
}
}
......
......@@ -14,6 +14,7 @@ import OneWeatherAnalytics
final class NativeAdItem: NSObject {
let log = AdLogger(componentName: "NativeAdItem")
private var adLifeCycleTracker: AdLifeCycleTrackable?
enum AdType {
case native
......@@ -47,16 +48,18 @@ final class NativeAdItem: NSObject {
return nativeAd != nil ? .native : .banner
}
init(nativeAd: GADNativeAd?, adUnitId: String?) {
init(nativeAd: GADNativeAd?, adUnitId: String?, adLifeCycleTracker: AdLifeCycleTrackable?) {
super.init()
self.nativeAd = nativeAd
self.adLifeCycleTracker = adLifeCycleTracker
self.nativeAd?.delegate = self
self.adUnitId = adUnitId
}
init(bannerAd: GAMBannerView, adUnitId: String?) {
init(bannerAd: GAMBannerView, adUnitId: String?, adLifeCycleTracker: AdLifeCycleTrackable?) {
super.init()
self.bannerAd = bannerAd
self.adLifeCycleTracker = adLifeCycleTracker
self.bannerAd?.delegate = self
self.adUnitId = adUnitId
}
......@@ -79,20 +82,30 @@ extension NativeAdItem: GADNativeAdDelegate {
params[.ANALYTICS_KEY_AD_ADAPTER] = adNetworkName
analytics(log: .ANALYTICS_AD_IMPRESSION, params: params)
adLifeCycleTracker?.recordAdState(.adImpression)
}
func nativeAdDidRecordClick(_ nativeAd: GADNativeAd) {
log.debug("NativeAd did record click")
analytics(log: .ANALYTICS_AD_CLICKED, params: analyticsParams)
adLifeCycleTracker?.recordAdState(.adClicked)
}
func nativeAdWillPresentScreen(_ nativeAd: GADNativeAd) {
log.debug("NativeAd Will present Modal")
analytics(log: .ANALYTICS_AD_CLICKED, params: analyticsParams)
adLifeCycleTracker?.recordAdState(.adOpened)
}
func nativeAdDidDismissScreen(_ nativeAd: GADNativeAd) {
log.debug("NativeAd Did Dismiss screen")
adLifeCycleTracker?.recordAdState(.adClosed)
}
func nativeAdWillLeaveApplication(_ nativeAd: GADNativeAd) {
log.info("NativeAd Will Leave Application")
analytics(log: .ANALYTICS_AD_CLICKED, params: analyticsParams)
adLifeCycleTracker?.recordAdState(.adLeftApplication)
}
}
......@@ -100,15 +113,18 @@ extension NativeAdItem: GADBannerViewDelegate {
func bannerViewDidRecordImpression(_ bannerView: GADBannerView) {
log.info("Impression recorded (banner)")
analytics(log: .ANALYTICS_AD_IMPRESSION)
adLifeCycleTracker?.recordAdState(.adImpression)
}
func bannerViewWillPresentScreen(_ bannerView: GADBannerView) {
log.info("Click recorded (will present screen)")
analytics(log: .ANALYTICS_AD_CLICKED)
adLifeCycleTracker?.recordAdState(.adOpened)
}
func adViewWillLeaveApplication(_ bannerView: GADBannerView) {
log.info("Click recorded (will leave application)")
analytics(log: .ANALYTICS_AD_CLICKED)
adLifeCycleTracker?.recordAdState(.adLeftApplication)
}
}
......@@ -24,7 +24,7 @@ class NativeBannerContainerView: UIView {
private var adLoader: NativeAdLoader
private let adType: AdType
private var adLifeCycleTracker: AdLifeCycleTrackable?
weak var delegate: NativeBannerContainerViewDelegate?
public var loggingAlias: String? {
......@@ -33,9 +33,13 @@ class NativeBannerContainerView: UIView {
}
}
init(adUnitId: String, adType: AdType, rootViewController: UIViewController) {
init(adUnitId: String,
adType: AdType,
adLifeCycleTracker: AdLifeCycleTrackable? = nil,
rootViewController: UIViewController) {
self.adType = adType
self.adLoader = NativeAdLoader(adUnitId: adUnitId, rootViewController: rootViewController, adTypes: [.native, .gamBanner])
self.adLifeCycleTracker = adLifeCycleTracker
super.init(frame: .zero)
self.adLoader.delegate = self
}
......@@ -81,15 +85,17 @@ extension NativeBannerContainerView: NativeAdLoaderDelegate {
func adLoader(_ adLoader: GADAdLoader, didReceive nativeAd: GADNativeAd) {
log.debug("Banner ad recieved (native_banner) by: \(String(describing: nativeAd.responseInfo.adNetworkClassName))")
guard let adView = NativeAdView.instantiateWithXib(adType: adType) else { return }
let nativeAdItem = NativeAdItem(nativeAd: nativeAd, adUnitId: adLoader.adUnitID)
let nativeAdItem = NativeAdItem(nativeAd: nativeAd, adUnitId: adLoader.adUnitID, adLifeCycleTracker: adLifeCycleTracker)
adView.nativeAdItem = nativeAdItem
addAsSubview(view: adView)
print("native ad loader: \(String(describing: nativeAd.headline))")
delegate?.adLoader(adLoader, didReceive: nativeAd)
adLifeCycleTracker?.recordAdState(.adLoaded)
}
func adLoader(_ adLoader: GADAdLoader, didFailToReceiveAdWithError error: Error) {
delegate?.adLoader(adLoader, didFailToReceiveAdWithError: error)
adLifeCycleTracker?.recordAdState(.adFailedToLoad)
}
func adLoader(_ adLoader: GADAdLoader, didReceived bannerView: GAMBannerView) {
......@@ -98,6 +104,7 @@ extension NativeBannerContainerView: NativeAdLoaderDelegate {
addAsSubview(view: bannerView)
delegate?.adLoader(adLoader, didReceived: bannerView)
adLifeCycleTracker?.recordAdState(.adLoaded)
}
}
......@@ -106,15 +113,26 @@ extension NativeBannerContainerView: GADBannerViewDelegate {
func bannerViewDidRecordImpression(_ bannerView: GADBannerView) {
log.info("Impression")
analytics(log: .ANALYTICS_AD_IMPRESSION)
adLifeCycleTracker?.recordAdState(.adImpression)
}
func bannerViewWillPresentScreen(_ bannerView: GADBannerView) {
log.info("Click (will present screen)")
analytics(log: .ANALYTICS_AD_CLICKED)
adLifeCycleTracker?.recordAdState(.adOpened)
}
func adViewWillLeaveApplication(_ bannerView: GADBannerView) {
log.info("Click (will leave application)")
analytics(log: .ANALYTICS_AD_CLICKED)
adLifeCycleTracker?.recordAdState(.adLeftApplication)
}
func bannerViewDidRecordClick(_ bannerView: GADBannerView) {
adLifeCycleTracker?.recordAdState(.adClicked)
}
func bannerViewWillDismissScreen(_ bannerView: GADBannerView) {
adLifeCycleTracker?.recordAdState(.adClosed)
}
}
......@@ -137,6 +137,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
settings.widgetPromotionTriggerCount = 0
}
}
// Initializing Essential Dependencies
EssentialDependencies().resolve()
return true
}
......@@ -251,6 +253,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
availabilityCheckers[feature] = activeProSubscription
case .extendedHourlyForecast:
availabilityCheckers[feature] = activeProSubscription
case .terceptSdk:
break // config only
}
}
......@@ -355,3 +359,12 @@ extension AppDelegate: SettingsAppDelegate {
UIApplication.shared.open(appSettingsURL)
}
}
// This class is placed here so no one else can access its initializer other than AppDelegate, Please remove it from here once we move all to other initializations file
class EssentialDependencies {
fileprivate init() { }
func resolve() {
let _: TerceptSDKManager = Dependency.shared.getDependency()
}
}
//
// AppInfo.swift
// 1Weather
//
// Created by Shailesh on 29/07/21.
//
import UIKit
import AdSupport
struct AppInfo {
static var idfa: String? { ASIdentifierManager.shared().advertisingIdentifier.uuidString
}
static let idfv: String? = UIDevice.current.identifierForVendor?.uuidString
static var appVersion: String = {
let appInfoDict = Bundle.main.infoDictionary
let appVersion: String = (appInfoDict?["CFBundleShortVersionString"] as? String) ?? "Unknown"
let buildNumber: String = (appInfoDict?[kCFBundleVersionKey as String] as? String) ?? ""
return "\(appVersion)(\(buildNumber))"
}()
}
......@@ -31,5 +31,7 @@
&quot;a9_refresh_rate&quot;: 0,
&quot;placements&quot;: {}
}</string>
<key>ios_terceptsdk_enabled</key>
<true/>
</dict>
</plist>
//
// Dependency.swift
// 1Weather
//
// Created by Shailesh on 14/07/21.
//
import Foundation
/*
Adding the class, to reduce use of single instances
whichever thing that we can use this to provide as and when dependency values and functionalities needed
*/
protocol DependencyRepresentable {
init()
}
class Dependency {
static var shared = Dependency()
private init() { }
private var dependencyMap: [String: DependencyRepresentable] = [:]
func getDependency<T: DependencyRepresentable>() -> T {
guard let dependency: T = dependencyMap[String(describing: T.self)] as? T else {
let dependency = T()
dependencyMap[String(describing: T.self)] = dependency
return dependency
}
return dependency
}
}
......@@ -133,6 +133,8 @@ class CCPAHelper {
FBAdSettings.setAdvertiserTrackingEnabled(canCollectData)
let terceptSDKManager: TerceptSDKManager? = Dependency.shared.getDependency()
terceptSDKManager?.canCollectData = canCollectData
// https://ams.amazon.com/webpublisher/uam/docs/web-integration-documentation/integration-guide/uam-ccpa.html?source=menu
if self.canCollectData == nil {
A9Cache.shared.ccpaPrivacyString = "1\(self.shownPrivacyNoticeBefore ? "Y" : "N")-"
......
......@@ -47,7 +47,8 @@ public class ConfigManager {
.nwsAlertsViaMoEngage: true,
.attPrompt: false,
.shortsLastNudge: false,
.onboarding: false
.onboarding: false,
.terceptSdk: false
], subscriptionsConfig: SubscriptionsListConfig()
)
......@@ -195,6 +196,8 @@ fileprivate extension AppFeature {
return "shorts_swipe_down_nudge_enabled"
case .onboarding:
return "ios_show_onboarding"
case .terceptSdk:
return "ios_terceptsdk_enabled"
case .ads, .airQualityIndex, .minutelyForecast, .shorts, .subscription, .subscriptionForPro, .extendedDailyForecast, .extendedHourlyForecast:
return nil
// don't use 'default', so that we didn't add new features here in the future.
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
//
// StubTerceptOptimizer.swift
// 1WeatherTests
//
// Created by Shailesh on 15/07/21.
//
import Foundation
@testable import _Weather
class StubTerceptOptimizer: TerceptOpimizable {
private let customTargettingKeys: [String : String?]
private(set) var initParamsMethodParams: (String, String, [String: Any]?)? = nil
private(set) var fetchMethodParams: [String]?
private(set) var setCustomParametersMethodParams: [String: String]?
private(set) var sentEventDataCalled: Bool = false
private(set) var logEventMethodParams: (String?, String?, [String: Any])?
init(customTargettingKeys: [String : String?]) {
self.customTargettingKeys = customTargettingKeys
}
func initParams(_ IDFA: String, _ IDFV: String, _ params: [String : Any]?) {
self.initParamsMethodParams = (IDFA, IDFV, params)
}
func fetch(_ adunits: [String]?) {
self.fetchMethodParams = adunits
}
func setCustomParameters(_ params: [String : String]) {
self.setCustomParametersMethodParams = params
}
func sendEventsData() {
self.sentEventDataCalled = true
}
func getCustomTargetingKeys(_ adunit: String?) -> [String : String?] {
return customTargettingKeys
}
func logEvent(_ adunit: String?, _ event: String?, _ customParams: [String : Any]) -> Bool {
self.logEventMethodParams = (adunit, event, customParams)
return true
}
}
extension AdLifeCycleState: CaseIterable {
public static var allCases: [AdLifeCycleState] = [.adClicked, .adClosed, .adFailedToLoad, .adImpression, .adLeftApplication, .adLoaded, .adOpened]
}
//
// TerceptSDKEnablerTests.swift
// 1WeatherTests
//
// Created by Shailesh on 02/08/21.
//
import XCTest
@testable import _Weather
class TerceptSDKEnablerTests: XCTestCase {
private var isInitCalled = false
private var isDeinitCalled = false
private var enabler = TerceptSDKEnabler()
override func setUp() {
super.setUp()
enabler = TerceptSDKEnabler()
self.enabler.attachObserver(observer: TerceptConditionsObserver(initSDK: {
self.isInitCalled = true
}, deinitSDK: {
self.isDeinitCalled = true
}))
}
override func tearDown() {
super.tearDown()
isInitCalled = false
isDeinitCalled = false
}
func testEnablingCondition() {
enabler.enableDataCollection(true)
XCTAssertFalse(isInitCalled)
XCTAssertFalse(isDeinitCalled)
enabler.setConfigToggle(true)
XCTAssertTrue(isInitCalled)
XCTAssertFalse(isDeinitCalled)
}
func testDelayedEnabledCondition() {
enabler.enableDataCollection(false)
XCTAssertFalse(isInitCalled)
XCTAssertFalse(isDeinitCalled)
enabler.setConfigToggle(false)
XCTAssertFalse(isInitCalled)
XCTAssertFalse(isDeinitCalled)
enabler.enableDataCollection(true)
XCTAssertFalse(isInitCalled)
XCTAssertFalse(isDeinitCalled)
enabler.setConfigToggle(true)
XCTAssertTrue(isInitCalled)
XCTAssertFalse(isDeinitCalled)
}
func testDataCollectionDeniedCondition() {
enabler.enableDataCollection(true)
XCTAssertFalse(isInitCalled)
XCTAssertFalse(isDeinitCalled)
enabler.setConfigToggle(true)
XCTAssertTrue(isInitCalled)
XCTAssertFalse(isDeinitCalled)
// Resetting init
isInitCalled = false
enabler.enableDataCollection(false)
XCTAssertFalse(isInitCalled)
XCTAssertTrue(isDeinitCalled)
}
func testRuntimeConfigOffCondition() {
enabler.enableDataCollection(true)
XCTAssertFalse(isInitCalled)
XCTAssertFalse(isDeinitCalled)
enabler.setConfigToggle(true)
XCTAssertTrue(isInitCalled)
XCTAssertFalse(isDeinitCalled)
// Resetting init
isInitCalled = false
enabler.setConfigToggle(false)
XCTAssertFalse(isInitCalled)
XCTAssertTrue(isDeinitCalled)
}
func testMultipleDeinitCallingIssue() {
testDataCollectionDeniedCondition()
isDeinitCalled = false
enabler.enableDataCollection(false)
XCTAssertFalse(isInitCalled)
XCTAssertFalse(isDeinitCalled)
}
func testMultipleConfigEnableCalls() {
enabler.enableDataCollection(true)
enabler.setConfigToggle(true)
isInitCalled = false
enabler.setConfigToggle(true)
XCTAssertFalse(isInitCalled)
XCTAssertFalse(isDeinitCalled)
enabler.setConfigToggle(false)
XCTAssertFalse(isInitCalled)
XCTAssertTrue(isDeinitCalled)
}
func testMultipleDataCollectionCalls() {
enabler.enableDataCollection(true)
enabler.setConfigToggle(true)
isInitCalled = false
enabler.enableDataCollection(true)
XCTAssertFalse(isInitCalled)
XCTAssertFalse(isDeinitCalled)
enabler.enableDataCollection(false)
XCTAssertFalse(isInitCalled)
XCTAssertTrue(isDeinitCalled)
}
}
//
// TerceptSDKManagerTests.swift
// 1WeatherTests
//
// Created by Shailesh on 15/07/21.
//
import XCTest
class TerceptSDKManagerTests: XCTestCase {
private let adUnits = ["Test/Ad/1", "Test/Ad/2"]
func testInitParams() {
let stub = StubTerceptOptimizer(customTargettingKeys: [:])
XCTAssertNil(stub.initParamsMethodParams?.0)
XCTAssertNil(stub.initParamsMethodParams?.1)
XCTAssertNil(stub.initParamsMethodParams?.2)
_ = TerceptSDKManager(optimizable: stub, adUnitIDs: adUnits)
XCTAssertNotNil(stub.initParamsMethodParams?.0)
XCTAssertNotNil(stub.initParamsMethodParams?.1)
XCTAssertNotNil(stub.initParamsMethodParams?.2)
}
func testFetchAdUnitsKeys() {
let stub = StubTerceptOptimizer(customTargettingKeys: [:])
XCTAssertNil(stub.fetchMethodParams)
_ = TerceptSDKManager(optimizable: stub, adUnitIDs: adUnits)
XCTAssertEqual(adUnits, stub.fetchMethodParams)
}
func testFetchAdUnitsKeysCustomTargettingKeys() {
let customTargettingKeys = ["Custom Target Key": "Value"]
let stub = StubTerceptOptimizer(customTargettingKeys: customTargettingKeys)
let manager = TerceptSDKManager(optimizable: stub, adUnitIDs: adUnits)
let tracker = manager.getTracker(with: adUnits.first!)
XCTAssertEqual(customTargettingKeys, tracker.getCustomTrackableKeys())
}
func testSetCustomParameter() {
let customParam = ["Custom Param Key": "Custom Param Value"]
let stub = StubTerceptOptimizer(customTargettingKeys: [:])
XCTAssertNil(stub.setCustomParametersMethodParams)
let manager = TerceptSDKManager(optimizable: stub, adUnitIDs: adUnits)
manager.setCustomParameters(customParam)
XCTAssertEqual(customParam, stub.setCustomParametersMethodParams)
}
func testLogEvent() {
let stub = StubTerceptOptimizer(customTargettingKeys: [:])
XCTAssertNil(stub.logEventMethodParams)
let manager = TerceptSDKManager(optimizable: stub, adUnitIDs: adUnits)
let tracker = manager.getTracker(with: adUnits.first!)
for item in AdLifeCycleState.allCases {
tracker.recordAdState(item)
XCTAssertEqual(stub.logEventMethodParams?.0, adUnits.first!)
XCTAssertEqual(stub.logEventMethodParams?.1, item.rawValue)
XCTAssertTrue(stub.logEventMethodParams?.2.isEmpty == true)
}
}
func testSendEventData() {
let stub = StubTerceptOptimizer(customTargettingKeys: [:])
XCTAssertFalse(stub.sentEventDataCalled)
let manager = TerceptSDKManager(optimizable: stub, adUnitIDs: adUnits)
NotificationCenter.default.post(name: UIApplication.didEnterBackgroundNotification, object: nil)
XCTAssertTrue(stub.sentEventDataCalled)
}
func testTerceptInit() {
let sdkManager = TerceptSDKManager()
XCTAssertFalse(sdkManager.logEvent("", "", [:]))
let configManager = ConfigManager()
configManager.config = AppConfig(popularCities: nil,
adConfig: AdConfig(),
ccpaUpdateInterval: nil,
nwsAlertsViaMoEngageEnabled: false,
showAttPrompt: false,
shortsLeftBelowCountKey: 0,
shortsLastNudgeEnabledKey: false,
shortsSwipeUpNudgeCountKey: 0,
terceptSDKTrackingEnabled: true)
sdkManager.dataUpdated(by: configManager)
XCTAssertFalse(sdkManager.logEvent("", "", [:]))
sdkManager.canCollectData = true
XCTAssertTrue(sdkManager.logEvent("", "", [:]))
}
func testTerceptFirebaseToggleOff() {
let sdkManager = TerceptSDKManager()
XCTAssertFalse(sdkManager.logEvent("", "", [:]))
let configManager = ConfigManager()
configManager.config = AppConfig(popularCities: nil,
adConfig: AdConfig(),
ccpaUpdateInterval: nil,
nwsAlertsViaMoEngageEnabled: false,
showAttPrompt: false,
shortsLeftBelowCountKey: 0,
shortsLastNudgeEnabledKey: false,
shortsSwipeUpNudgeCountKey: 0,
terceptSDKTrackingEnabled: false)
sdkManager.dataUpdated(by: configManager)
XCTAssertFalse(sdkManager.logEvent("", "", [:]))
sdkManager.canCollectData = true
XCTAssertFalse(sdkManager.logEvent("", "", [:]))
sdkManager.canCollectData = false
XCTAssertFalse(sdkManager.logEvent("", "", [:]))
}
func testDataCollectionPermission() {
let sdkManager = TerceptSDKManager()
XCTAssertFalse(sdkManager.logEvent("", "", [:]))
let configManager = ConfigManager()
configManager.config = AppConfig(popularCities: nil,
adConfig: AdConfig(),
ccpaUpdateInterval: nil,
nwsAlertsViaMoEngageEnabled: false,
showAttPrompt: false,
shortsLeftBelowCountKey: 0,
shortsLastNudgeEnabledKey: false,
shortsSwipeUpNudgeCountKey: 0,
terceptSDKTrackingEnabled: true)
sdkManager.dataUpdated(by: configManager)
sdkManager.canCollectData = true
XCTAssertTrue(sdkManager.logEvent("", "", [:]))
sdkManager.canCollectData = false
XCTAssertFalse(sdkManager.logEvent("", "", [:]))
}
}
//
// TerceptSDKEnabler.swift
// 1Weather
//
// Created by Shailesh on 02/08/21.
//
struct TerceptConditionsObserver {
let initSDK: (() -> Void)
let deinitSDK: (() -> Void)
}
final class TerceptSDKEnabler {
private var observer: TerceptConditionsObserver?
private var configToggle: Bool?
private var canCollectData: Bool?
private var hasInitializedSDK: Bool = false
private func invokeObserver() {
guard let firebaseConfig = configToggle,
let canCollectData = canCollectData else { return }
if firebaseConfig, canCollectData {
if !hasInitializedSDK {
hasInitializedSDK = true
observer?.initSDK()
}
} else if hasInitializedSDK {
hasInitializedSDK = false
observer?.deinitSDK()
}
}
func attachObserver(observer: TerceptConditionsObserver) {
self.observer = observer
}
func setConfigToggle(_ configToggle: Bool) {
self.configToggle = configToggle
invokeObserver()
}
func enableDataCollection(_ canCollectData: Bool) {
self.canCollectData = canCollectData
invokeObserver()
}
}
//
// TerceptSDKManager.swift
// 1Weather
//
// Created by Shailesh on 13/07/21.
//
import UIKit
import TerceptSDK
protocol TerceptOpimizable: AdEventLoggable {
func fetch(_ adunits: [String]?)
func setCustomParameters(_ params: [String : String])
func sendEventsData()
func getCustomTargetingKeys(_ adunit: String?) -> [String : String?]
func initParams(_ IDFA: String, _ IDFV: String, _ params: [String : Any]?)
}
protocol AdEventLoggable {
@discardableResult func logEvent(_ adunit: String?, _ event: String?, _ customParams: [String : Any]) -> Bool
}
extension TerceptOptimization: TerceptOpimizable { }
final class TerceptSDKManager: AdEventLoggable, DependencyRepresentable {
private var optimizer: TerceptOpimizable?
private let enabler: TerceptSDKEnabler
private struct Constants {
static let DFPCode: String = "120348554"
static var IDFA: String { AppInfo.idfa ?? "UNKNOWN" }
static var IDFV: String { AppInfo.idfv ?? "UNKNOWN" }
static var version: String { AppInfo.appVersion }
}
var canCollectData: Bool = false {
didSet {
enabler.enableDataCollection(canCollectData)
}
}
required init() {
self.enabler = TerceptSDKEnabler()
attachObserver()
registerNotifications()
}
init(optimizable: TerceptOpimizable, adUnitIDs: [String]) {
self.optimizer = optimizable
self.enabler = TerceptSDKEnabler()
attachObserver()
updateKeys()
optimizable.fetch(adUnitIDs)
registerNotifications()
fetchIfAvailable()
}
//MARK:- Private
private func attachObserver() {
self.enabler.attachObserver(observer: TerceptConditionsObserver(initSDK: initializeSDK, deinitSDK: deinitSDK))
}
private func initializeSDK() {
optimizer = TerceptOptimization(Constants.DFPCode)
updateKeys()
fetchIfAvailable()
}
private func deinitSDK() {
optimizer = nil
}
private func updateKeys() {
optimizer?.initParams(Constants.IDFA, Constants.IDFV, ["appVersion": Constants.version])
}
private func registerNotifications() {
NotificationCenter.default.addObserver(self,
selector: #selector(handleSwitchToBackground),
name: UIApplication.didEnterBackgroundNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(fetchIfAvailable),
name: .adConfigChanged,
object: nil)
ConfigManager.shared.add(delegate: self)
}
@objc private func fetchIfAvailable() {
guard let placements = ConfigManager.shared.config.adConfig.placements,
!placements.isEmpty else { return }
register(adUnitIDs: placements.values.map { $0.adUnitId })
}
private func register(adUnitIDs: [String]) {
optimizer?.fetch(adUnitIDs)
}
@objc private func handleSwitchToBackground() {
optimizer?.sendEventsData()
}
//MARK:- Public
func getTracker(with adUnitId: String) -> TerceptAdTracker {
return TerceptAdTracker(adUnitId: adUnitId,
logger: self,
customKeys: optimizer?.getCustomTargetingKeys(adUnitId) ?? [:])
}
func setCustomParameters(_ params: [String : String]) {
optimizer?.setCustomParameters(params)
}
@discardableResult func logEvent(_ adunit: String?, _ event: String?, _ customParams: [String : Any]) -> Bool {
return optimizer?.logEvent(adunit, event, customParams) ?? false
}
}
extension TerceptSDKManager: ConfigManagerDelegate {
func dataUpdated(by configManager: ConfigManager) {
enabler.setConfigToggle(configManager.config.isEnabled(feature: .terceptSdk))
}
}
enum AdLifeCycleState: String {
case adClicked = "onAdClicked"
case adClosed = "onAdClosed"
case adFailedToLoad = "onAdFailedToLoad"
case adImpression = "onAdImpression"
case adLeftApplication = "onAdLeftApplication"
case adLoaded = "onAdLoaded"
case adOpened = "onAdOpened"
}
protocol CustomTargetProvidable {
func getCustomTrackableKeys() -> [String: String?]
}
protocol AdLifeCycleTrackable {
func recordAdState(_ state: AdLifeCycleState)
}
typealias CustomTargetedAdLifeCycleTrackable = CustomTargetProvidable & AdLifeCycleTrackable
/// Get instance of TerceptAdTracker from TerceptSDKManager, using func getTracker(with adUnitId: String) method.
final class TerceptAdTracker: CustomTargetedAdLifeCycleTrackable {
private let adUnitId: String
private let customKeys: [String: String?]
private let logger: AdEventLoggable
fileprivate init(adUnitId: String, logger: AdEventLoggable, customKeys: [String: String?]) {
self.adUnitId = adUnitId
self.customKeys = customKeys
self.logger = logger
}
func getCustomTrackableKeys() -> [String: String?] {
return customKeys
}
func recordAdState(_ state: AdLifeCycleState) {
logger.logEvent(adUnitId, state.rawValue, [:])
}
}
// Copyright (c) 2021, Tercept (https://www.tercept.com/)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef PublicHeader_h
#define PublicHeader_h
#import <TerceptSDK/TerceptSDK.h>
#import <TerceptSDK/TerceptSDK-Bridging-Header.h>
#endif /* PublicHeader_h */
// Copyright (c) 2021, Tercept (https://www.tercept.com/)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/Foundation.h>
//! Project version number for TerceptSDK.
FOUNDATION_EXPORT double TerceptSDKVersionNumber;
//! Project version string for TerceptSDK.
FOUNDATION_EXPORT const unsigned char TerceptSDKVersionString[];
// In this header, you should import all the public headers of your framework using statements like
// #import <TerceptSDK/PublicHeader.h>
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.4 (swiftlang-1205.0.26.9 clang-1205.0.19.55)
// swift-module-flags: -target armv7-apple-ios9.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name TerceptSDK
import Foundation
import SafariServices
import Swift
import UIKit
import WebKit
public protocol Callable {
associatedtype R
func call() -> Self.R
}
public protocol TaskRunner_Callback {
associatedtype R
func onComplete(result: Self.R)
}
@objc public class TerceptOptimization : ObjectiveC.NSObject {
public static let sdkVersion: Swift.String
public static var isDebugMode: Swift.Bool
public init(_ networkCode: Swift.String)
public func initParams(_ IDFA: Swift.String, _ IDFV: Swift.String, _ params: [Swift.String : Any]?)
public func initParams(_ IDFA: Swift.String, _ IDFV: Swift.String)
public func fetch(_ adunits: [Swift.String]?)
public func logEvent(_ adunit: Swift.String?, _ event: Swift.String?) -> Swift.Bool
public func logEvent(_ adunit: Swift.String?, _ event: Swift.String?, _ customParams: [Swift.String : Any]) -> Swift.Bool
public func getEventsData() -> [Swift.String : Any]?
public func sendEventsData()
public func setCustomParameters(_ params: [Swift.String : Swift.String])
public func getCustomTargetingKeys(_ adunit: Swift.String?) -> Swift.Dictionary<Swift.String, Swift.String?>
@objc override dynamic public init()
@objc deinit
}
extension UIDevice {
public static let modelName: Swift.String
}
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.4 (swiftlang-1205.0.26.9 clang-1205.0.19.55)
// swift-module-flags: -target arm64-apple-ios9.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name TerceptSDK
import Foundation
import SafariServices
import Swift
import UIKit
import WebKit
public protocol Callable {
associatedtype R
func call() -> Self.R
}
public protocol TaskRunner_Callback {
associatedtype R
func onComplete(result: Self.R)
}
@objc public class TerceptOptimization : ObjectiveC.NSObject {
public static let sdkVersion: Swift.String
public static var isDebugMode: Swift.Bool
public init(_ networkCode: Swift.String)
public func initParams(_ IDFA: Swift.String, _ IDFV: Swift.String, _ params: [Swift.String : Any]?)
public func initParams(_ IDFA: Swift.String, _ IDFV: Swift.String)
public func fetch(_ adunits: [Swift.String]?)
public func logEvent(_ adunit: Swift.String?, _ event: Swift.String?) -> Swift.Bool
public func logEvent(_ adunit: Swift.String?, _ event: Swift.String?, _ customParams: [Swift.String : Any]) -> Swift.Bool
public func getEventsData() -> [Swift.String : Any]?
public func sendEventsData()
public func setCustomParameters(_ params: [Swift.String : Swift.String])
public func getCustomTargetingKeys(_ adunit: Swift.String?) -> Swift.Dictionary<Swift.String, Swift.String?>
@objc override dynamic public init()
@objc deinit
}
extension UIDevice {
public static let modelName: Swift.String
}
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.4 (swiftlang-1205.0.26.9 clang-1205.0.19.55)
// swift-module-flags: -target arm64-apple-ios9.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name TerceptSDK
import Foundation
import SafariServices
import Swift
import UIKit
import WebKit
public protocol Callable {
associatedtype R
func call() -> Self.R
}
public protocol TaskRunner_Callback {
associatedtype R
func onComplete(result: Self.R)
}
@objc public class TerceptOptimization : ObjectiveC.NSObject {
public static let sdkVersion: Swift.String
public static var isDebugMode: Swift.Bool
public init(_ networkCode: Swift.String)
public func initParams(_ IDFA: Swift.String, _ IDFV: Swift.String, _ params: [Swift.String : Any]?)
public func initParams(_ IDFA: Swift.String, _ IDFV: Swift.String)
public func fetch(_ adunits: [Swift.String]?)
public func logEvent(_ adunit: Swift.String?, _ event: Swift.String?) -> Swift.Bool
public func logEvent(_ adunit: Swift.String?, _ event: Swift.String?, _ customParams: [Swift.String : Any]) -> Swift.Bool
public func getEventsData() -> [Swift.String : Any]?
public func sendEventsData()
public func setCustomParameters(_ params: [Swift.String : Swift.String])
public func getCustomTargetingKeys(_ adunit: Swift.String?) -> Swift.Dictionary<Swift.String, Swift.String?>
@objc override dynamic public init()
@objc deinit
}
extension UIDevice {
public static let modelName: Swift.String
}
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.4 (swiftlang-1205.0.26.9 clang-1205.0.19.55)
// swift-module-flags: -target armv7-apple-ios9.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name TerceptSDK
import Foundation
import SafariServices
import Swift
import UIKit
import WebKit
public protocol Callable {
associatedtype R
func call() -> Self.R
}
public protocol TaskRunner_Callback {
associatedtype R
func onComplete(result: Self.R)
}
@objc public class TerceptOptimization : ObjectiveC.NSObject {
public static let sdkVersion: Swift.String
public static var isDebugMode: Swift.Bool
public init(_ networkCode: Swift.String)
public func initParams(_ IDFA: Swift.String, _ IDFV: Swift.String, _ params: [Swift.String : Any]?)
public func initParams(_ IDFA: Swift.String, _ IDFV: Swift.String)
public func fetch(_ adunits: [Swift.String]?)
public func logEvent(_ adunit: Swift.String?, _ event: Swift.String?) -> Swift.Bool
public func logEvent(_ adunit: Swift.String?, _ event: Swift.String?, _ customParams: [Swift.String : Any]) -> Swift.Bool
public func getEventsData() -> [Swift.String : Any]?
public func sendEventsData()
public func setCustomParameters(_ params: [Swift.String : Swift.String])
public func getCustomTargetingKeys(_ adunit: Swift.String?) -> Swift.Dictionary<Swift.String, Swift.String?>
@objc override dynamic public init()
@objc deinit
}
extension UIDevice {
public static let modelName: Swift.String
}
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.4 (swiftlang-1205.0.26.9 clang-1205.0.19.55)
// swift-module-flags: -target armv7-apple-ios9.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name TerceptSDK
import Foundation
import SafariServices
import Swift
import UIKit
import WebKit
public protocol Callable {
associatedtype R
func call() -> Self.R
}
public protocol TaskRunner_Callback {
associatedtype R
func onComplete(result: Self.R)
}
@objc public class TerceptOptimization : ObjectiveC.NSObject {
public static let sdkVersion: Swift.String
public static var isDebugMode: Swift.Bool
public init(_ networkCode: Swift.String)
public func initParams(_ IDFA: Swift.String, _ IDFV: Swift.String, _ params: [Swift.String : Any]?)
public func initParams(_ IDFA: Swift.String, _ IDFV: Swift.String)
public func fetch(_ adunits: [Swift.String]?)
public func logEvent(_ adunit: Swift.String?, _ event: Swift.String?) -> Swift.Bool
public func logEvent(_ adunit: Swift.String?, _ event: Swift.String?, _ customParams: [Swift.String : Any]) -> Swift.Bool
public func getEventsData() -> [Swift.String : Any]?
public func sendEventsData()
public func setCustomParameters(_ params: [Swift.String : Swift.String])
public func getCustomTargetingKeys(_ adunit: Swift.String?) -> Swift.Dictionary<Swift.String, Swift.String?>
@objc override dynamic public init()
@objc deinit
}
extension UIDevice {
public static let modelName: Swift.String
}
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.4 (swiftlang-1205.0.26.9 clang-1205.0.19.55)
// swift-module-flags: -target i386-apple-ios9.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name TerceptSDK
import Foundation
import SafariServices
import Swift
import UIKit
import WebKit
public protocol Callable {
associatedtype R
func call() -> Self.R
}
public protocol TaskRunner_Callback {
associatedtype R
func onComplete(result: Self.R)
}
@objc public class TerceptOptimization : ObjectiveC.NSObject {
public static let sdkVersion: Swift.String
public static var isDebugMode: Swift.Bool
public init(_ networkCode: Swift.String)
public func initParams(_ IDFA: Swift.String, _ IDFV: Swift.String, _ params: [Swift.String : Any]?)
public func initParams(_ IDFA: Swift.String, _ IDFV: Swift.String)
public func fetch(_ adunits: [Swift.String]?)
public func logEvent(_ adunit: Swift.String?, _ event: Swift.String?) -> Swift.Bool
public func logEvent(_ adunit: Swift.String?, _ event: Swift.String?, _ customParams: [Swift.String : Any]) -> Swift.Bool
public func getEventsData() -> [Swift.String : Any]?
public func sendEventsData()
public func setCustomParameters(_ params: [Swift.String : Swift.String])
public func getCustomTargetingKeys(_ adunit: Swift.String?) -> Swift.Dictionary<Swift.String, Swift.String?>
@objc override dynamic public init()
@objc deinit
}
extension UIDevice {
public static let modelName: Swift.String
}
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.4 (swiftlang-1205.0.26.9 clang-1205.0.19.55)
// swift-module-flags: -target i386-apple-ios9.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name TerceptSDK
import Foundation
import SafariServices
import Swift
import UIKit
import WebKit
public protocol Callable {
associatedtype R
func call() -> Self.R
}
public protocol TaskRunner_Callback {
associatedtype R
func onComplete(result: Self.R)
}
@objc public class TerceptOptimization : ObjectiveC.NSObject {
public static let sdkVersion: Swift.String
public static var isDebugMode: Swift.Bool
public init(_ networkCode: Swift.String)
public func initParams(_ IDFA: Swift.String, _ IDFV: Swift.String, _ params: [Swift.String : Any]?)
public func initParams(_ IDFA: Swift.String, _ IDFV: Swift.String)
public func fetch(_ adunits: [Swift.String]?)
public func logEvent(_ adunit: Swift.String?, _ event: Swift.String?) -> Swift.Bool
public func logEvent(_ adunit: Swift.String?, _ event: Swift.String?, _ customParams: [Swift.String : Any]) -> Swift.Bool
public func getEventsData() -> [Swift.String : Any]?
public func sendEventsData()
public func setCustomParameters(_ params: [Swift.String : Swift.String])
public func getCustomTargetingKeys(_ adunit: Swift.String?) -> Swift.Dictionary<Swift.String, Swift.String?>
@objc override dynamic public init()
@objc deinit
}
extension UIDevice {
public static let modelName: Swift.String
}
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.4 (swiftlang-1205.0.26.9 clang-1205.0.19.55)
// swift-module-flags: -target x86_64-apple-ios9.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name TerceptSDK
import Foundation
import SafariServices
import Swift
import UIKit
import WebKit
public protocol Callable {
associatedtype R
func call() -> Self.R
}
public protocol TaskRunner_Callback {
associatedtype R
func onComplete(result: Self.R)
}
@objc public class TerceptOptimization : ObjectiveC.NSObject {
public static let sdkVersion: Swift.String
public static var isDebugMode: Swift.Bool
public init(_ networkCode: Swift.String)
public func initParams(_ IDFA: Swift.String, _ IDFV: Swift.String, _ params: [Swift.String : Any]?)
public func initParams(_ IDFA: Swift.String, _ IDFV: Swift.String)
public func fetch(_ adunits: [Swift.String]?)
public func logEvent(_ adunit: Swift.String?, _ event: Swift.String?) -> Swift.Bool
public func logEvent(_ adunit: Swift.String?, _ event: Swift.String?, _ customParams: [Swift.String : Any]) -> Swift.Bool
public func getEventsData() -> [Swift.String : Any]?
public func sendEventsData()
public func setCustomParameters(_ params: [Swift.String : Swift.String])
public func getCustomTargetingKeys(_ adunit: Swift.String?) -> Swift.Dictionary<Swift.String, Swift.String?>
@objc override dynamic public init()
@objc deinit
}
extension UIDevice {
public static let modelName: Swift.String
}
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.4 (swiftlang-1205.0.26.9 clang-1205.0.19.55)
// swift-module-flags: -target x86_64-apple-ios9.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name TerceptSDK
import Foundation
import SafariServices
import Swift
import UIKit
import WebKit
public protocol Callable {
associatedtype R
func call() -> Self.R
}
public protocol TaskRunner_Callback {
associatedtype R
func onComplete(result: Self.R)
}
@objc public class TerceptOptimization : ObjectiveC.NSObject {
public static let sdkVersion: Swift.String
public static var isDebugMode: Swift.Bool
public init(_ networkCode: Swift.String)
public func initParams(_ IDFA: Swift.String, _ IDFV: Swift.String, _ params: [Swift.String : Any]?)
public func initParams(_ IDFA: Swift.String, _ IDFV: Swift.String)
public func fetch(_ adunits: [Swift.String]?)
public func logEvent(_ adunit: Swift.String?, _ event: Swift.String?) -> Swift.Bool
public func logEvent(_ adunit: Swift.String?, _ event: Swift.String?, _ customParams: [Swift.String : Any]) -> Swift.Bool
public func getEventsData() -> [Swift.String : Any]?
public func sendEventsData()
public func setCustomParameters(_ params: [Swift.String : Swift.String])
public func getCustomTargetingKeys(_ adunit: Swift.String?) -> Swift.Dictionary<Swift.String, Swift.String?>
@objc override dynamic public init()
@objc deinit
}
extension UIDevice {
public static let modelName: Swift.String
}
......@@ -22,4 +22,5 @@ public enum AppFeature: String, Codable, CaseIterable {
case subscription
/// Discounted subscription for people who previously purchased an in-app to remove ads
case subscriptionForPro
case terceptSdk
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment