前段时间,苹果终于在大陆区开放了应用商店的竞价广告。毫无疑问又开启了苹果应用导量的新玩法,各大厂商都紧跟脚步吃螃蟹。本篇讲解苹果广告中的归因部分。
苹果广告其实在海外已运行多年,而因为IDFA的政策变动,现在苹果有新旧两套归因框架,通常我们都要接入。
一、iAd 和 AdServices 框架概述
iAd 框架:适用于iOS14.3以下版本,基于IDFA,需要用户允许使用IDFA。尤其iOS14.0起,IDFA的政策变动,要接入ATT追踪框架。
归因流程:打开APP -> 调用iAd框架 -> 读取广告因素(JSON) -> 发送广告因素到后端 -> 发送激活日志到后端
AdServices 框架:适用于iOS14.3及以上版本,不需用户授权。
归因流程:打开APP -> 调用AdServices框架 -> 读取token -> 发送token到苹果后端换取广告因素(JSON) -> 发送广告因素到后端 -> 发送激活日志到后端
两框架在流程上区别不大,得到数据也类似,主要区别是AdServices只有ID,没有具体的名字。详细JSON字段:
两套框架都要接,详细区别不大,主要是跟iOS版本相关
区别 | iAd | AdServices |
归因窗口期 | 30天 | 30天 |
ATT影响 | 支持ATT Opt-In | 不影响 |
归因误差率 | 15%-70%不等 | 10%左右或更低 |
数据延迟 | 三方MMP数据称3秒内返回结果比例大于50% | 三方MMP数据称延迟0.5-1秒 |
参数丰富度 | 较全 | 较少(只返回ID) |
是否支持展示归因和指纹信息归因 | 不支持 | 不支持 |
是否支持非AppStore上架APP(越狱包) | 不支持 | 不支持 |
▲▲▲高版本(14.5+)的idfa获取,要等待弹窗被用户授权后才能得到,所以需要延迟调用广告归因和激活日志:
+(void)initSDK{ //... ... //苹果ASA;延迟4秒再发送,等ATT用户操作结果,可能有IDFA dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [MySDK LogAds]; }); //激活日志;延迟6秒再发送,先让Ads发送完再发 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [MySDK LogOpen]; }); //... ... }
二、与第三方广告的归因的区别
1、第三方广告(如头条快手)使用的是广告点击的监测短链,由广告商回传给我们后台;苹果采用的是iAd和AdServices,由接入的苹果SDK发送参数到我们后台;
第三广告的监测短链示例:
https://api.myhost.com/ad/toutiao/click?adkey=abcde&idfa=__IDFA__&ip=__IP__&os=__OS__&callback=__CALLBACK__
苹果ASA的客户端回传示例:
https://api.myhost.com/ad/asa/click?idfa=xxxx&orgId=1234&campaignId=123456&adGroupId=123456&keywordId=12345678
(注意客户端回传的参数,若没有增加IP地址的参数,则需要服务端读取请求者的IP地址,以作归因参数)
2、第三方广告在点击时回传,苹果广告在应用打开时回传;
3、第三方广告使用自定义的adkey作为广告依据,苹果广告建议使用苹果的广告组ID(adGroupId)作为后台广告依据;
广告组ID可以在苹果投放后台的右上角查看,如图
自己BI后台的广告列表示例:
广告序号 | 广告名称 | 应用 | 渠道 | adKey标识 | 广告短链 | 渠道广告ID |
12323 | 头条广告001 | 游戏A | 头条1 | abcde | https://api.myhost.com/... | (无) |
12324 | 苹果广告001 | 游戏A | 苹果1 | 123456 |
三、苹果端SDK的接入
1、准备条件,开发环境 Xcode12.3+,MacOS11+。如果版本不满足,则需更新开发软件(和系统)。
2、接入方法,添加iAd到Xcode项目:
1)选择项目主文件 > TARGETS > General
2)引入 iAd.framework、AdServices.framwork、AdSupport.framwork
3)进入 Link Binary With Libraries,将上述3个框架都改为Optional
4、代码 MySDK.m(Objective-C)
/** 导入上述3个框架*/ #import <iAd/iAd.h> #import <AdServices/AdServices.h> #import <AppTrackingTransparency/AppTrackingTransparency.h> /** 苹果Ads广告*/ /** TODO:有些旧设备新系统(iPhone8),会出现token为空的问题*/ +(void)LogAds{ // 14.3之后 if (@available(iOS 14.3, *)) { NSError *error; NSString *token = [AAAttribution attributionTokenWithError:&error]; NSLog(@"LogAds:AdServces,Token: %@", token); if (token != nil) { // 1、发送POST给苹果得到归因数据 [MySDK sendToken:[MySDK getANullableString:@"token" content:token] completeBlock:^(NSDictionary *attrData) { //异步,会延后 NSLog(@"LogAds:14.3+ Dict: %@", attrData); //TODO::发送数据给服务端 // ... ... }]; } // 14.3之前 } else { if ([[ADClient sharedClient] respondsToSelector:@selector(requestAttributionDetailsWithBlock:)]) { NSLog(@"LogAds:iAd called"); [[ADClient sharedClient] requestAttributionDetailsWithBlock:^(NSDictionary *attrData, NSError *error) { //异步,会延后 NSLog(@"LogAds:14- Dict: %@", attrData); //TODO::发送数据给服务端 // ... ... }]; } } } /** 读取可能为空的字符串*/ +(nullable NSString *)getANullableString:(NSString *)desc content:(NSString *)content{ if(content == nil){ return @""; } return [NSString stringWithFormat:@"%@", content]; } /** 发送归因token得到数据 */ +(void)sendToken:(NSString *)token completeBlock:(void(^)(NSDictionary* data))completeBlock{ NSString *url = [NSString stringWithFormat:@"https://api-adservices.apple.com/api/v1/"]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]]; request.HTTPMethod = @"POST"; [request addValue:@"text/plain" forHTTPHeaderField:@"Content-Type"]; NSData* postData = [token dataUsingEncoding:NSUTF8StringEncoding]; [request setHTTPBody:postData]; NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSDictionary * result = NULL; if (error) { //请求失败 NSLog(@"LogAds:sendToken ERR"); if (completeBlock) { NSMutableDictionary *nulldict = [NSMutableDictionary dictionary]; completeBlock(nulldict); } }else{ // 请求成功 NSError *resError; NSMutableDictionary *resDic = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&resError]; result = [[NSDictionary alloc] initWithDictionary:resDic]; if (completeBlock) { completeBlock(result); } } }]; [dataTask resume]; }
至此,苹果ASA的广告归因接入告一段落
待后台对接好广告归因逻辑代码,苹果包上架应用商店,就可以了。
附:IDFA的新旧版本的接入
#import <AppTrackingTransparency/AppTrackingTransparency.h> -(void)initSDK{ //... ... //IDFA iOS14不同方式 if (@available(iOS 14, *)) { // iOS14及以上版本需要先请求权限 [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) { // 获取到权限后,依然使用老方法获取idfa // iOS14以后,idfa在回调之后才能获得,应当等回调后再发送日志 if (status == ATTrackingManagerAuthorizationStatusAuthorized) { self->_idfa = [[ASIdentifierManager sharedManager].advertisingIdentifier UUIDString]; } }]; }else{ _idfa = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]; } //... ... }