iOS ASA 的归因技术支持_Swift

摘要:
这里的ASA归因是本文的重点。ASA自我归因和第三方归因的主要区别是什么。影响ASA广告展示的因素有三个:相关性、出价和用户行为。目前,它支持启动。必须实施AdServices,建议为与14.2及以下版本兼容的设备实施iAd,以充分准确地将安装归因于ASA广告。iAd中。该框架也用于ASA归因。它受到ATT和LAT的限制,如果用户允许跟踪,则可以将其归因。
ASA简介

苹果ASA搜索广告服务已全面上线,在App Store中搜索关键词,搜索结果的顶部会出现带有“广告”标识的App展示。

ASA拥有高转化率、低成本、用户精准、流量安全等优势,是一个相当重要的获量渠道。ASA不知道是什么请看这里

https://ads.apple.com/cn/?cid=BD-BZ-Desktop-SC-CN-001

ASA投放

创建ASA账号,以及投放广告是BD和运营的事情,这些不清楚的请参考百度。

ASA归因

这里是该文章的重点。

ASA的自归因和第三方归因主要差异是什么

ASA自归因:App直接和广告媒体做数据对接,并进行归因。

第三方归因:App通过第三方归因平台处理数据统计业务。

iOS ASA 的归因技术支持_Swift第1张

ASA后台和第三方归因的数据是实时的吗?

ASA后台不是。正常情况下Apple Ads后台的数据有3-6个小时的延迟,有时甚至会达到24小时。

第三方归因平台则要取决于各自的方案。通常获取曝光和点击数据的延时性取决于ASA的API反馈速度。

一个关键词下广告多次展示,却没有用户点击会有影响吗?

影响ASA广告展示的因素有三个:相关性、竞价价格、用户行为。用户点击属于用户行为的一部分,对于用户行为表现较差的广告,会被认为App广告和关键词的相关性并不强,从而减少广告展示权重。

教育行业是否支持投放ASA?

目前已支持投放。近期,苹果发布了《适用于中国大陆的Apple广告指南》文档的版本更新。在本次的更新内容中,就新增了“教育”类别的资质需求,可以自行查看详情。

iOS ASA 的归因技术支持_Swift第2张
 

ASA的自归因

我公司合作的数据统计平台如果开通第三方归因服务每年需要8W,所有自然就是自己处理,虽然效果差些,但分析数据现阶段够了。下面进入正题

Apple Ads 归因 API 说明

AdServices & iAd

Apple Ads 的归因 API 包括两个,分别是 iAd Framework 和 AdServices 
Framework 。AdServices 是必须实施的,为兼容 14.2 及以下设备建议实施 iAd, 
以充分准确地归因来自 ASA 广告的安装。
 
注:iAd Framework 及 AdServices Framework 必须 App 版本更新进行集成

两个方案的版本支持及成功率

对于 iOS 的版本支持以及用户隐私相关的限制: 
iOS 14.3 (含)以及更高版本的设备,优先使用 AdServices API 获取归因,该方案不涉 
及用户隐私限制,理论上归因成功率超过 90%; 
iAd API 可支持所有版本的设备(iOS 4.0+),但仅限于【允许跟踪】的设备;
 
在 iOS 13 以及更低版本的设备中,隐私设置中的【限制广告跟踪】为关闭状态,在 iOS 14 
以及更高版本的设备中,隐私设置中的【允许应用程序请求跟踪】为开启状态,且如果 App 
已实施 App Tracking Transparency 框架,还需用户点击“允许跟踪”。
 
注:如存在复杂归因逻辑的场景,例如多渠道归因、新老用户、网络异常等,上述归因 
成功率预估或存在偏差。

集成步骤

1、将框架添加到您的项目工程
 
AdServices.framework 框架是用于 ASA 归因的,不受 ATT 约束,即无论用户是否 
允许跟踪都可以归因,仅支持 14.3 及更高版本系统,需 XCode 12.3 及更高版本 支 
持。
 
iAd.framework 框架亦是用于 ASA 归因的,受 ATT 以及 LAT 约束,如果用户允许 
跟踪方可以归因。支持当前所有 iOS 版本,但未来可能废弃。
 
AppTrackingTransparency.framework 是在iOS 14 及更高版本用于征求用户跟踪许 
可的框架,即弹窗询问用户是否同意跟踪,在 iOS 14.5 苹果将强制要求开发者实施, 
也是获取 IDFA 的前提。
 
AdSupport.framework 是用于获取 IDFA,以及在低于 iOS 14 的版本中获取 LAT 信 
息。
以上 framework 在添加到项目中后,均设置为 Optional。
 

客户端处理逻辑参考

由于各种原因导致的获取归因包失败时,需要做容错处理,及时进行重试, 
重试多次仍然失败的,应用在下次启动时再进行获取;
XCode 版本必需 12.3 及以上;

归因数据包格式说明

iOS14.3+
["adGroupId": 1234567890, "creativeSetId": 1234567890, "conversionType": Download, "orgId": 1234567890, "clickDate": 2021-12-07T04:25Z, "keywordId": 12323222,  "countryOrRegion": US, "campaignId": 1234567890,  "attribution": 1]

iOS14.3-XX
"Version3.1": {
"iad-adgroup-id" = 1234567890;
"iad-adgroup-name" = AdGroupName;
"iad-attribution" = true;
"iad-campaign-id" = 1234567890;
"iad-campaign-name" = CampaignName;
"iad-click-date" = "2021-12-03T07:32:38Z";
"iad-conversion-date" = "2021-12-03T07:32:38Z";
"iad-conversion-type" = Download;
"iad-country-or-region" = US;
"iad-creativeset-id" = 1234567890;
"iad-creativeset-name" = CreativeSetName;
"iad-keyword" = Keyword;
"iad-keyword-id" = 12323222;
"iad-keyword-matchtype" = Broad;
"iad-lineitem-id" = 1234567890;
"iad-lineitem-name" = LineName;
"iad-org-id" = 1234567890;
"iad-org-name" = OrgName;
"iad-purchase-date" = "2021-12-03T07:32:38Z";
}

这里我是登录后对数据进行了二次加工,把userId和与后台定义的type加进去

iOS14.3+
["adGroupId": 1234567890, "creativeSetId": 1234567890, "conversionType": Download, "orgId": 1234567890, "clickDate": 2021-12-07T04:25Z,

"keywordId": 12323222,"userId": "20XX", "countryOrRegion": US, "campaignId": 1234567890, "type": 1, "attribution": 1]

iOS14.3-XX
["userId": "20XX";
"type": 2;
"Version3.1": {
"iad-adgroup-id" = 1234567890;
"iad-adgroup-name" = AdGroupName;
"iad-attribution" = true;
"iad-campaign-id" = 1234567890;
"iad-campaign-name" = CampaignName;
"iad-click-date" = "2021-12-03T07:32:38Z";
"iad-conversion-date" = "2021-12-03T07:32:38Z";
"iad-conversion-type" = Download;
"iad-country-or-region" = US;
"iad-creativeset-id" = 1234567890;
"iad-creativeset-name" = CreativeSetName;
"iad-keyword" = Keyword;
"iad-keyword-id" = 12323222;
"iad-keyword-matchtype" = Broad;
"iad-lineitem-id" = 1234567890;
"iad-lineitem-name" = LineName;
"iad-org-id" = 1234567890;
"iad-org-name" = OrgName;
"iad-purchase-date" = "2021-12-03T07:32:38Z";
}]

之后对数据进行转json字符串处理然后传给后台

高版本

iOS ASA 的归因技术支持_Swift第3张

iOS ASA 的归因技术支持_Swift第4张

 

 低版本

字段类型说明
iad-attributionBoolean如果用户在应用下载前30天点击了Apple Search Ads广告,则为True。
iad-org-nameString广告系列所属的账户组织名称
iad-org-idInteger广告系列所属的账户组织ID
iad-campaign-idInteger广告系列ID
iad-campaign-nameString广告系列名称
iad-click-dateDate/time string用户点击相应广告的日期和时间
iad-purchase-dateDate/time string用户首次下载您的应用的日期和时间。 当iad-conversion-type的值为“Redownload”,此字符串表示原始购买日期。 该购买可能与Apple Search广告无关。
iad-conversion-dateDate/time string用户通过点击Apple搜索广告下载您的应用的日期和时间。
iad-conversion-typeString表明是否首次下载。"Redownload"说明用户在本设备下载/卸载过,或者用同一账户在其他设备下载过。
iad-adgroup-idInteger广告组ID
iad-adgroup-nameString广告组名称
iad-country-or-regionString广告系列相关的国家或地区
iad-keywordString带来广告展示次数并带来相应广告点击的关键字
iad-keyword-idString带来广告展示次数的关键字的ID
iad-keyword-matchtypeString带来广告展示次数的关键字的匹配类型。 值是广泛匹配、完全匹配或搜索匹配。
iad-creativeset-idInteger相应广告所属的广告素材集的ID
iad-creativeset-nameString相应广告所属的广告素材集的名称

后台存表处理逻辑参考

当归因包返回的 attribution 为 false,其他数据字段没有,后台存表需注意。

客户端获取归因数据示意代码

//  LXADSHelper.swift
//  Psybot
//
//  Created by 李俊成LX on 2021/12/3.
//  Copyright © 2021 lianxin. All rights reserved.
//
import AdSupport
import AdServices
import iAd
import AppTrackingTransparency
import Foundation
import RxSwift
import RxCocoa
import NSObject_Rx

class LXADSHelper{
    static let disposeBag = DisposeBag()
    class func initSDK() {
        //苹果ASA;延迟4秒再发送,等ATT用户操作结果,可能有IDFA
        DispatchQueue.main.asyncAfter(deadline: .now() + 7) {
            let defaultStand = UserDefaults.standard
            let pushedADS = defaultStand.value(forKey: "pushedADS")
            if Platform.isSimulator {
            }else {
                if  pushedADS == nil {
                    self.logAds()
                }
            }
        }
    }
    /// 苹果Ads广告
    /// TODO:有些旧设备新系统(iPhone8),会出现token为空的问题
    class func logAds() {
        if #available(iOS 14.3, *) {
            var token: String? = nil
            do {
                token = try AAAttribution.attributionToken()
            } catch {
            }
            LXSimpleLogs("LogAds:AdServces,Token: \(token ?? "")")
            if let token = token {
                // 1、发送POST给苹果得到归因数据
                sendToken(getANullableString("token", content: token)) { attrData in
                    // 异步,会延后
                    LXSimpleLogs("LogAds:14.3+ Dict: \(attrData ?? [:])")
                    // TODO::发送数据给服务端
                    // ... ...
                    if attrData != nil {
                        var attrDataL:[String:Any] = attrData!
                        // 添加userId
                        attrDataL["type"] = "1"
                        let defaultStand = UserDefaults.standard
                        defaultStand.set(attrDataL, forKey:"pushADSDic")
                        defaultStand.synchronize()
                        self.logOpen()
                    }
                }
            }
        }else{
            if ADClient.shared().responds(to: #selector(ADClient.requestAttributionDetails(_:))) {
                LXSimpleLogs("LogAds:iAd called")
                ADClient.shared().requestAttributionDetails({ attrData, error in
                    // 异步,会延后
                    LXSimpleLogs("LogAds:14- Dict: \(attrData ?? [:])")
                    // TODO::发送数据给服务端
                    if attrData != nil {
                        var haveVersion :Int = 0
                        var haveVersionStr :String = "Version"
                        for  keystr in attrData!.keys {
                            if keystr.contains("Version") {
                                haveVersion = 1
                                haveVersionStr = keystr
                            }
                        }
                        if haveVersion == 1 {
                            var attrDataLL : [String:Any] = [:]
                            let attrDataL:[String:Any] = attrData![haveVersionStr] as! [String : Any]
                            // 旧数据统一处理一下
                            attrDataLL["iadPurchaseDate"] = attrDataL["iad-purchase-date"]
                            attrDataLL["iadLineitemId"] = attrDataL["iad-lineitem-id"]
                            attrDataLL["iadOrgName"] = attrDataL["iad-org-name"]
                            attrDataLL["iadCreativesetId"] = attrDataL["iad-creativeset-id"]
                            attrDataLL["iadCreativesetName"] = attrDataL["iad-creativeset-name"]
                            attrDataLL["iadOrgId"] = attrDataL["iad-org-id"]
                            attrDataLL["iadLineitemName"] = attrDataL["iad-lineitem-name"]
                            attrDataLL["iadAdgroupName"] = attrDataL["iad-adgroup-name"]
                            attrDataLL["iadConversionDate"] = attrDataL["iad-conversion-date"]
                            attrDataLL["iadClickDate"] = attrDataL["iad-click-date"]
                            attrDataLL["iadKeywordMatchtype"] = attrDataL["iad-keyword-matchtype"]
                            attrDataLL["iadCountryOrRegion"] = attrDataL["iad-country-or-region"]
                            attrDataLL["iadConversionType"] = attrDataL["iad-conversion-type"]
                            attrDataLL["iadKeywordId"] = attrDataL["iad-keyword-id"]
                            attrDataLL["iadCampaignId"] = attrDataL["iad-campaign-id"]
                            attrDataLL["iadAttribution"] = attrDataL["iad-attribution"]
                            attrDataLL["iadCampaignName"] = attrDataL["iad-campaign-name"]
                            attrDataLL["iadKeyword"] = attrDataL["iad-keyword"]
                            attrDataLL["iadAdgroupId"] = attrDataL["iad-adgroup-id"]
                            //
                            attrDataLL["type"] = "2"
                            let defaultStand = UserDefaults.standard
                            defaultStand.set(attrDataLL, forKey:"pushADSDic")
                            defaultStand.synchronize()
                            self.logOpen()
                        }
                    }
                })
            }
        }
    }
    /// 读取可能为空的字符串
    class func getANullableString(_ desc: String?, content: String?) -> String? {
        if content == nil {
            return ""
        }
        return "\(content ?? "")"
    }
    /// 发送归因token得到数据
    class func sendToken(_ token: String?, completeBlock: @escaping (_ data: [String : Any]?) -> Void) {
        let url = "https://api-adservices.apple.com/api/v1/"
        var request: URLRequest? = nil
        if let url1 = URL(string: url) {
            request = URLRequest(url: url1)
        }
        request?.httpMethod = "POST"
        request?.addValue("text/plain", forHTTPHeaderField: "Content-Type")
        let postData = token?.data(using: .utf8)
        request?.httpBody = postData
        // 发出请求
        URLSession.shared.dataTask(with: request!) { data, response, error in
            var result: [String : Any]? = nil
            if error != nil {
                // 请求失败
                LXSimpleLogs("LogAds:sendToken ERR")
                let nulldict: [String : Any] = [:]
                completeBlock(nulldict)
            }else{
                // 请求成功
                var resDic: [String : Any]? = nil
                do {
                    resDic = try JSONSerialization.jsonObject(with: data!, options: []) as? [String : Any]
                } catch _ {
                }
                result = resDic
                completeBlock(result)
            }
        }.resume()
    }

    /// 激活日志,这里登录后发送
    class func logOpen() {
        
        let defaultStand = UserDefaults.standard
        let pushedADS = defaultStand.value(forKey: "pushedADS")
        if  pushedADS == nil && LXUserModel.islogined , let userId = LXUserModel.localModel()?.userId{
            LXSimpleLogs("LogOpen")
            var attrData:[String:Any] = defaultStand.value(forKey: "pushADSDic") as! [String : Any]
            if attrData.keys.count > 0 {
                // 添加userId
                attrData["userId"] = userId
                LXSimpleLogs("LogOpenAds Dict: \(attrData)")
                // 上传数据
                let params = ["asaData":attrData.jsonString] as [String : Any]
                LXSimpleLogs("LogOpenAds Dict_params: \(params)")
                DispatchQueue.main.async {
                    //code
              上传数据伪代码
                            // 上传数据后OK
                            let defaultStand = UserDefaults.standard
                            defaultStand.set(true, forKey:"pushedADS")
                            defaultStand.synchronize()

                }
            }
        }
    }
   
    struct Platform {
        static let isSimulator: Bool = {
            var isSim = false
            #if arch(i386) || arch(x86_64)
                isSim = true
            #endif
            return isSim
        }()
    }
    
}

 大数据分析归因处理参考

 以上就把相关的归因数据存表了,但是有的只有关键词ID,并没有对应的关键词,这里就会用到获取对应关键词的官方API接口

请参考https://developer.apple.com/documentation/apple_search_ads/implementing_oauth_for_the_apple_search_ads_api

当然如果不想这样,那就通过批量上传关键词,在上传关键词.csv文件的时候给大数据工程师一份。

iOS ASA 的归因技术支持_Swift第5张

 好了,就这样吧!

参考:https://baijiahao.baidu.com/s?id=1709681570145946947&wfr=spider&for=pc

 

 
 

免责声明:文章转载自《iOS ASA 的归因技术支持_Swift》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇This system is not registered with an entitlement server. You can use subscription-manager to register.介绍Oracle自带的一些ASM维护工具 (kfod/kfed/amdu)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

oracle之to_char,to_date用法

[转载自]http://www.jb51.net/article/45591.htm 这篇文章主要介绍了oracle中to_date详细用法示例,包括期和字符转换函数用法、字符串和时间互转、求某天是星期几、两个日期间的天数、月份差等用法 TO_DATE格式(以时间:2007-11-02 13:45:25为例) 1. 日期和字符转换函数用法(to_date,...

读懂IL代码就这么简单(二)

一 前言   IL系列 第一篇写完后 得到高人指点,及时更正了文章中的错误,也使得我写这篇文章时更加谨慎,自己在了解相关知识点时,也更为细致。个人觉得既然做为文章写出来,就一定要保证比较高的质量,和正确率 。感谢 @冰麟轻武 的指点 你没有看第一篇?  点这里看第一篇 读懂IL代码就这么简单(一)   IL指令大全 :IL指令详解 IL反编译工具: ILD...

在oracle中 将一个以逗号隔开的String字符串转换成以单引号逗号隔开的集合

在oracle中,使用一条语句实现将'1,2,3'拆分成'1','2','3'的集合将一个以逗号隔开的String字符串转换成以单引号逗号隔开的集合select regexp_substr('1,2,3','[^,]+', 1, level) as para_code1 from dualconnect by regexp_substr('1,2,3','...

如何制作prezi swf格式字体(prezi 中文字体)

如何制作prezi swf格式字体(prezi 中文字体) 文/玄魂 前言 Prezi软件虽然没有正式进入中国,但是中国的Prezi爱好者却在不遗余力的推广着Prezi。我接触这款软件比较晚,但是从接触到现在,已经正式抛弃了PPT,爱不释手。 Prezi在4.60及之前版本之前不支持中文输入,社区内的高手制作了swf格式,通过编辑css替换字体的方法做到...

Android学习使用基本界面组件(下拉框,单选框,复选框,数字转轮,滚动条)

(一)建立单选框按钮 RadioGroup和RadioButton建立单选框按钮 字符串资源文件: <resources> <string name="app_name">婚姻建议程序</string> <string name="sex">性别:</string> <...

CSS文本部分之字体样式[1]

1. 字体系列 [通用字体系列] 1. serif字体:带衬线字体,如Georiga、Times等 2. sans-serif字体:不带衬线字体,包括Arial、Geneva等 3. Monospace字体:等宽字体,包括Courier等 4. Cursive字体:手写字体,包括Author等 5. Fanstasy字体:无法归类,包括Western等...