Files
purchases-ios/Sources/SubscriberAttributes/AttributionDataMigrator.swift
T
NachoSoto 0bff3f65fe Sendable support (#1795)
Fixes [CSDK-379] and https://github.com/RevenueCat/purchases-ios/issues/1041#issuecomment-1206445939

[CSDK-379]: https://revenuecats.atlassian.net/browse/CSDK-379?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

### Main changes:
- `Sendable` conformances to all types that are thread-safe
- `@unchecked Sendable` for types that the compiler can't enforce, but with documentation as to why
- Made classes that aren't mocked `final` (note that this doesn't change the API, because none of these were `open` to begin with)
- Made some classes thread-safe that weren't (like `DeviceCache`)

For the non-`final` `class`es, I've actually managed to compile the SDK with all of them as `final` and `Sendable`, to make sure that making them `@unchecked Sendable` didn't hide any other issues.

### Future improvements:
- https://twitter.com/nachosoto/status/1557139992056500224?s=21&t=arWdvEzTIFANBvwqQ0vPiA
- https://twitter.com/nachosoto/status/1557141944777592832?s=21&t=arWdvEzTIFANBvwqQ0vPiA
- https://twitter.com/nachosoto/status/1557143374922059776?s=21&t=arWdvEzTIFANBvwqQ0vPiA

### Depends on:
- #1794
- #1804
- #1806
- #1807
- #1808
- #1813
- #1822
- #1823
- #1824
- #1825
- #1826
2022-08-31 13:30:38 -07:00

165 lines
7.4 KiB
Swift

//
// Copyright RevenueCat Inc. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// AttributionDataMigrator.swift
//
// Created by César de la Vega on 6/16/21.
//
import Foundation
class AttributionDataMigrator {
func convertToSubscriberAttributes(attributionData: [String: Any], network: Int) -> [String: String] {
let network = AttributionNetwork(rawValue: network)
let attributionData = attributionData.removingNSNullValues()
var convertedAttribution: [String: String] = [:]
if let value = attributionData[AttributionKey.idfa.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.idfa.key] = value
}
if let value = attributionData[AttributionKey.idfv.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.idfv.key] = value
}
if let value = attributionData[AttributionKey.ip.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.ip.key] = value
}
if let value = attributionData[AttributionKey.gpsAdId.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.gpsAdId.key] = value
}
let networkSpecificSubscriberAttributes = convertNetworkSpecificSubscriberAttributes(
for: network,
attributionData: attributionData
)
return convertedAttribution.merging(networkSpecificSubscriberAttributes)
}
}
// @unchecked because:
// - Class is not `final` (it's mocked). This implicitly makes subclasses `Sendable` even if they're not thread-safe.
extension AttributionDataMigrator: @unchecked Sendable {}
private extension AttributionDataMigrator {
// This implementation follows the backend mapping of the attribution data to subscriber attributes
func convertNetworkSpecificSubscriberAttributes(for network: AttributionNetwork?,
attributionData: [String: Any]) -> [String: String] {
let networkSpecificSubscriberAttributes: [String: String]
switch network {
case .adjust:
networkSpecificSubscriberAttributes = convertAdjustAttribution(attributionData)
case .appsFlyer:
networkSpecificSubscriberAttributes = convertAppsFlyerAttribution(attributionData)
case .branch:
networkSpecificSubscriberAttributes = convertBranchAttribution(attributionData)
case .tenjin,
.facebook:
networkSpecificSubscriberAttributes = [:]
case .mParticle:
networkSpecificSubscriberAttributes = convertMParticleAttribution(attributionData)
case .none, .appleSearchAds, .adServices:
// Apple Search Ads & AdServices use standard attribution system
networkSpecificSubscriberAttributes = [:]
}
return networkSpecificSubscriberAttributes
}
func convertMParticleAttribution(_ data: [String: Any]) -> [String: String] {
var convertedAttribution: [String: String] = [:]
if let value = data[AttributionKey.MParticle.id.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.mpParticleID.key] = value
}
if let value = data[AttributionKey.networkID.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.mpParticleID.key] = value
}
return convertedAttribution
}
func convertBranchAttribution(_ data: [String: Any]) -> [String: String] {
var convertedAttribution: [String: String] = [:]
if let value = data[AttributionKey.Branch.channel.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.mediaSource.key] = value
}
if let value = data[AttributionKey.Branch.campaign.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.campaign.key] = value
}
return convertedAttribution
}
// swiftlint:disable:next cyclomatic_complexity
func convertAppsFlyerAttribution(_ data: [String: Any]) -> [String: String] {
var fixedData = data
if let innerDataObject = fixedData[AttributionKey.AppsFlyer.dataKey.rawValue] as? [String: String?] {
if fixedData[AttributionKey.AppsFlyer.statusKey.rawValue] != nil {
for (key, value) in innerDataObject {
fixedData[key] = value
}
}
}
var convertedAttribution: [String: String] = [:]
if let value = fixedData[AttributionKey.networkID.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.appsFlyerID.key] = value
}
if let value = fixedData[AttributionKey.AppsFlyer.id.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.appsFlyerID.key] = value
}
if let value = fixedData[AttributionKey.AppsFlyer.mediaSource.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.mediaSource.key] = value
}
if let value = fixedData[AttributionKey.AppsFlyer.channel.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.mediaSource.key] = value
}
if let value = fixedData[AttributionKey.AppsFlyer.campaign.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.campaign.key] = value
}
if let value = fixedData[AttributionKey.AppsFlyer.adSet.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.adGroup.key] = value
}
if let value = fixedData[AttributionKey.AppsFlyer.adGroup.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.ad.key] = value
}
if let value = fixedData[AttributionKey.AppsFlyer.ad.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.ad.key] = value
}
if let value = fixedData[AttributionKey.AppsFlyer.adKeywords.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.keyword.key] = value
}
if let value = fixedData[AttributionKey.AppsFlyer.adId.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.creative.key] = value
}
return convertedAttribution
}
func convertAdjustAttribution(_ data: [String: Any]) -> [String: String] {
var convertedAttribution: [String: String] = [:]
if let value = data[AttributionKey.Adjust.id.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.adjustID.key] = value
}
if let value = data[AttributionKey.Adjust.network.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.mediaSource.key] = value
}
if let value = data[AttributionKey.Adjust.campaign.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.campaign.key] = value
}
if let value = data[AttributionKey.Adjust.adGroup.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.adGroup.key] = value
}
if let value = data[AttributionKey.Adjust.creative.rawValue] as? String {
convertedAttribution[ReservedSubscriberAttribute.creative.key] = value
}
return convertedAttribution
}
}