AdServices归因和iAd归因集成

AdServices framework 是 Apple 专门为 ASA 提供的归因框架 。尤其在ATT 政策推出以后,app 获取用户 IDFA 的比例大幅降低,传统的依靠IDFA 的方法也无法准确归因。


但是 Apple 为 ASA 开了一个后门,其他广告渠道无法获取用户的 IDFA 作为身份标识符进行归因,而 ASA 可以获取一个甚至比 IDFA 更好的用户token 进行归因-因为即便 ATT 之前,用户在 iPhone 的设置也会导致无法完全获取 IDFA,但是 ASA 的用户 token ,只要接入 AdServicesframework,就可以获取到。


另外要注意的是,除了 AdServices framework, ASA 还有一个 iAdframework。但是后者仅适用于 ioS 14.3 之前版本,且此框架已经停用iOS 14.3 之前的用户占比也很小,所以不接入对归因也没有太大影响,所以这节课不会涉及 iAd framework。


官方文档
https://ads.apple.com/cn/help/reporting/0028-apple-ads-attribution-api

 

AdServices 接入目的
通过 AdServices framework,可以让我们对每一个激活用户获取一个Token 标识符,用此 Token 可以得知该用户是否点击过 ASA 广告、点击过那个广告系列/广告组/关键词/素材等广告信息。
 

AdServices 接入步骤
        1.选择项目主文件-TARGETS-General
        2.入 AdServices framework
        3.进入 link binary with Libraries 将 AdServices 改为 optional
        4.导入AdServices framework
        5.添加获取用户 Token 的代码
        6.更新 App 并发布至 App Store


AdServices 接入流程

AdServices归因和iAd归因集成

前提:AdServices归因框架的集成必须是iOS14.3以上版本xcode版本12.3以上;旧版本依旧使⽤iAd 归因框架。

第一步:找到framework的添加入

添加framework

第二步:分别搜索AdServices.framework、AdSupport.framework、iAd.framework,进行添加

AdServices.framework


AdSupport.framework

iAd.framework

第三步:在Build Phases中Link Binary With Libraries 修改 AdServices.framework、AdSupport.framework、iAd.framework的类型为Optional

AdServices.framework、AdSupport.framework、iAd.framework的类型为Optional

第四部:将AdServices.framework、AdSupport.framework、iAd.framework的头文件加入到项目

#pragma mark -- 归因
#import "iAd/iAd.h"
#import "AdServices/AdServices.h"
#import "AppTrackingTransparency/AppTrackingTransparency.h"

添加头文件

第五步:代码集成,可在AppDelegate中添加 

5.1 AdServices获取token               

+(void)getAdToken
{
    if (@available(iOS 14.3, *)) {
        NSError *error;
        NSString *token = [AAAttribution attributionTokenWithError:&error];
        if (token != nil) {
            // 发送POST请求归因数据
            [self sendToken:[self getANullableString:@"token" content:token]completeBlock:^(NSDictionary *attrData) {
                NSLog(@"成功==:14.3+ Dict: %@", attrData);
                //可将数据发送给服务端
            }];
        }
    } else {
        // 老版本请求
        if ([[ADClient sharedClient]respondsToSelector:@selector(requestAttributionDetailsWithBlock:)]) {
            NSLog(@"LogAds:iAd called");
            [[ADClient sharedClient]requestAttributionDetailsWithBlock:^(NSDictionary *attrData, NSError *error) {
                //异步,会延后
                NSLog(@"成功:14- Dict: %@", attrData);
                //可将数据发送给服务端
                // ... ...
            }];
        }
    }
}

5.2 AdServices获取归因数据     

/** 读取可能为空的字符串*/

-(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{
            // 请求成功
            NSLog(@"请求成功");
            NSError *resError;
            NSMutableDictionary *resDic = [NSJSONSerialization JSONObjectWithData:data         options:kNilOptions error:&resError];
            result = [[NSDictionary alloc] initWithDictionary:resDic];
            if (completeBlock) {
                completeBlock(result);
            }
        }
    }];
    [dataTask resume];
}

5.3 AdServices 返回归因数据包示例

经测试,IDFA允许用户跟踪后,得到的数据是详细数据包

{
adGroupId = 1234567890;
attribution = 1;
campaignId = 1234567890;
clickDate = "2022-04-27T07:59Z";
conversionType = Download;
countryOrRegion = US;
creativeSetId = 1234567890;
keywordId = 12323222;
orgId = 1234567890;
}

未允许,得到的数据是标准数据包,没有clickDate字段

{
"attribution": true,
"orgId": 40669820,
"campaignId": 542370539,
"conversionType": "Download",
"adGroupId": 542317095,
"countryOrRegion": "US",
"keywordId": 87675432,
"creativeSetId": 542317136
}

5.4 iAd 代码

// 老版本请求
if ([[ADClient sharedClient]respondsToSelector:@selector(requestAttributionDetailsWithBlock:)]) {
    NSLog(@"LogAds:iAd called");
    [[ADClient sharedClient]requestAttributionDetailsWithBlock:^(NSDictionary *attrData, NSError *error) {
        //异步,会延后
        NSLog(@"成功:14- Dict: %@", attrData);
        //可将数据发送给服务端
        // ... ...
    }];
}

5.5iAd返回归因数据包示例

{
"iad-adgroup-id" = 1234567890;
"iad-adgroup-name" = AdGroupName;
"iad-attribution" = true;
"iad-campaign-id" = 1234567890;
"iad-campaign-name" = CampaignName;
"iad-click-date" = "2022-04-27T07:31:36Z";
"iad-conversion-date" = "2022-04-27T07:31:36Z";
"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" = "2022-04-27T07:31:36Z";
};

1.1 引言<br>约有90%的企业信息化管理系统基于数据库实现,这类系统中又有超过30%的代码集中在数据访问层负责业务数据存取。除了实现数据的增删改查,数据访问层还要提供一些与业务无关功能,例如面向对象的持久化与访问机制、本地事务与分布式事务支持、多数据库支持,这些机制或功能形成相对独立的逻辑领域,其主要目的有:<br><br>1、 提供简单易用的数据库访问方法,提高开发效率;<br><br>2、 提供面向对象的方式来简化对数据库访问与操作,也就是ORMap方式;<br><br>3、 屏蔽数据库差异,使开发出的产品容易在不同数据库产品上移植。<br><br>为了适应软件快速开发的需要,软件企业应该借助组件产品或开源软件项目来搭建这些基础设施。在.Net平台上,目前比较成功应用较广的开源项目有NHibernateIBatisNet等,它们在ORMap方面的表现尤为突出。依赖这些平台,软件开发效率产品质量都得到了极大提高,ORMap机制渐渐成为大势所趋。<br><br>本公司致力于软件组件开发,提供的AppFramework数据库访问组件具有高效的ORMap机制,调用接口简单灵活,支持各种主流的数据库平台,是个非常优秀的数据访问组件。本文详尽描述了AppFramework数据库访问组件使用的方法技巧,有助于开发者最大程度发挥出AppFramework的优势。<br><br>1.2 各种优秀ORMap工具比较<br>NHibernateIBatisNet等虽然都实现了ORMap,但它们的设计侧重点有所不同,有着各自的优势缺陷,适合于特定的项目。NHibernate实现了纯对象化的ORMap,在屏蔽数据库差异、面向对象方面做的非常好,但在访问与操作海量数据时其性能表现较差,也不易实现复杂的查询统计功能。NHibernate适合那些数据量不大、性能要求不高、复杂度不高的场合。<br><br>IBatisNet是一个轻量级ORMap工具,它把所有的SQL脚本以模板的方式集中到若干个XML配置文件里,用反射的方式向把C#类实体对象属性与SQL模板的参数绑定,动态生成参数化的SQL语句发送给数据库执行,查询的结果集也用反射的方式构造为对象集合返回给程序。由于提供了灵活的SQL模板机制,在海量数据的访问与操作方面其性能比NHibernate要高得多,也很容易实现各种复杂的数据查询统计功能。但是IBatisNet也存在许多几个不足之处,有些还是先天因素造成的:<br><br>第一,数据库可移植性差。IBatisNet获得高性能与灵活性也是付出代价的,它牺牲了数据库可移植性:由于编写SQL模板不得不用到数据库产品的一些语法差异,例如ORACLE的TO_DATE、Length()、SYSDATE等,为了把产品移植到其它数据库,开发人员不得不对大量的SQL模板进行翻译。<br><br>第二,无法方便地向数据库中插入NULL值。因为IBatisNet是直接把C#的基本类型(如int/DateTime)插入到数据库中字段的。对于一些C#值类型,例如int,并没有NULL状态。于是不得不采用一些特殊的值来表示NULL,例如int.MinValue。IBatisNet数据映射器会自动把int.MinValue转换为NULL插入到数据库,而从数据库中获得NULL时,也会转化为C#的int.MinValue。这样,程序就要对int.MinVaue这个值进行特殊处理,例如不能把int.MinValue直接显示在DataGrid里或界面上。有一种规避的方法,就是避免向数据库插入NULL,即要求所有业务数据入库的值都不为空。但这样作实际上是限制了程序的行为能力,例如无法实现单据草稿的保存。<br><br>第三,在插入数据时无法方便的使用字段默认值。最明显的就是类似于LAST_UPDATE_TIME了,通常为了保证这个字段的一致性,通常在插入新记录时采用当前数据库时间作为字段值。但IBatisNet接收的实体类对象属性都是普通C#类型,并不具备传入表达式的能力。如果采用动态SQL,则会导致SQL模板参数实体类过于复杂,将极大降低性能。<br><br>第四,在查询返回大量对象时,用反射的方式构造实体的方式性能损失是相当大的。实体类属性数目越多、返回记录数越多,用到反射的次数也越多,查询性能降低就越明显。<br><br>第五,不能方便地限定查询语句返回的字段。ADO.Net执行查询时,select语句里设置了几个字段就返回几个字段到DataTable。而IBatisNet若要返回不同的字段就要定义多套ResultMap,否则就定义一套所有字段的ResultMap,任何查询都返回所有字段。这样无疑浪费了数据库服务器与应用服务器之间的网络带宽,在进行海量数据访问时性能将严重降低。<br><br>第六,返回的结果IList不能够方便地进行二次查询。相比之下,ADO.Net返回的DataTable虽然性能差一些,但可以实现在应用程序内存中灵活高性能的二次查询。<br><br>第七,无法直接利用数据库的特殊语法支持海量数据的分页查询功能。众所周知Oracle提供了ROWNUM实现数据数据库内分页功能,若要利用这一特性,就要在SQL模板里直接硬编码这些特殊语法,这必然导致大量的移植工作。<br><br>第八、缺少代码生成检查工具。程序里的变量名与Sql模板里的变量经常会出现不一致,而这些错误无法在编译时发现,靠人工检查很容易造成错误泄漏。也没提供工具直接生成SQL模板映射配置文件。<br><br>第九,IBatisNet的SqlMap文件里的SQL语句以明文存放,容易被修改造成重大安全隐患,尤其不适合开发C/S应用程序。<br><br>第十,由于Sql模板采用动态加载的方式,如果写错了SQL,程序里难以跟踪调试,这对初学者来说无疑是对耐心信心的极大考验。<br><br>总之,IBatisNet虽然提灵活、高性能的ORMap机制,但却损失了数据库可移植性的,在使用方便性局部性能方面也都有很大提高的余地。<br><br>1.3 AppFramework数据访问组件的组成优势<br>AppFramework数据访问组件由下列文件组成:<br><br>1、 AppFramework.DBAccess.dll<br><br>提供多数据库统一的访问接口,提供DAO管理器、数据库会话管理器。<br><br>2、 AppFramework.Data.dll<br><br>提供核心的数据结构基础类。<br><br>3、 AppFramework.Tools.CodeGenPlugin.msi<br><br>提供集成于Visual Studio 2005的C#代码生成器插件,用于生成DAO/Model/各种接口。<br><br>4、 DBAccess.config<br><br>配置数据库连接串。<br><br>5、 CodeGenPlogin.config<br><br>配置代码生成器参数。<br><br>6、 *.DaoGen文件<br><br>配置DAO生成信息,并由CodeGenPlugin解析生成代码。<br><br> <br><br>AppFramework数据库访问组件针对IBatisNet的种种缺陷提出相应的解决方案,相比之下有如下优势:<br><br>1、 从扩展基础数据类型入手,解决了空值问题默认值问题;<br><br>2、 提供了内置的数据库内分页访问机制,极大地提高了海量查询的响应速度;<br><br>3、 增加ObjectTable<T>泛型类来承载查询返回的对象集,不但比IList更加强类型化,还提供了二分查找功能,使得对象结果集可以在应用程序内存中进行重排序快速查找;<br><br>4、 提供了强大的QueryFilter类构造查询条件,使得实现数据查询不再需要编写复杂的SQL语句;<br><br>5、 提供类似IBatisNet的Sql模板功能,为复杂的查询统计提供较直观的开发模式;<br><br>6、 提供代码生成工具,生成的类代码的同时可以类之间的继承关系接口实现关系,所有DAO类方法均以接口作为参数,使得代码更加具有可扩展性灵活性。<br><br>7、 Sql模板ORMap直接生成.cs原代码,编译为可执行代码,各种ORMap映射文件无需再随主程序集一起部署,提高了代码的安全性,提高了代码的可调试性,也提高了ORMap的性能。<br><br> <br><br>下面三张表格罗列的测试数据,可以明显看出AppFramework数据库访问组件的性能全面超越了IBatisNet: <br><br> <br><br>表I –10并发20循环(数据库测试机分开)<br><br>对比项目<br> iBatis2.0<br><br>(毫秒)<br> AppFramework<br><br>(毫秒)<br> 后者前者性能对比<br><br>(倍)<br> <br>根据主键获取实体<br><br>(20次单条select)<br> 19.8<br> 16.1<br><br>QueryFilter:18.0<br> 1.23<br><br>1.10<br> <br>每秒插入实体<br><br>(20次insert)<br> 41<br> 21<br> 1.95<br> <br>更新实体<br><br>(20次单条update)<br> 27<br> 19<br><br>SqlMap:24<br> 1.42<br><br>1.13<br> <br>查询结果集(平均101行)<br><br>(2循环200次select)<br> 1100<br> 690<br><br>不定字段:720<br> 1.59<br><br>1.53<br> <br><br> <br><br>表II –50并发4循环(数据库测试机分开)<br><br>对比项目<br> iBatis2.0<br><br>(毫秒)<br> AppFramework<br><br>(毫秒)<br> 后者前者性能对比<br><br>(倍)<br> <br>根据主键获取实体<br><br>(20次单条select)<br> 17.5<br> 13.3<br><br>QueryFilter:14.5<br> 1.32<br><br>1.21<br> <br>插入实体<br><br>(20次insert)<br> 36.1<br> 17.4<br> 2.07<br> <br>更新实体<br><br>(20次单条update)<br> 23.5<br> 15.9<br><br>SqlMap:20.3<br> 1.48<br><br>1.16<br> <br>查询结果集(平均101行)<br><br>(1循环200次select)<br> 1055.1<br> 666.8<br><br>不定字段:710.1<br> 1.58<br><br>1.50<br> <br><br> <br><br>表III –50并发10循环(数据库测试机同机)<br><br>对比项目<br> iBatis2.0<br><br>(毫秒)<br> AppFramework<br><br>(毫秒)<br> 后者前者性能对比<br><br>(倍)<br> <br>根据主键获取实体<br><br>(20次单条select)<br> 6.1<br> 5.3<br><br>QueryFilter: 5.75<br> 1.15<br><br>1.06<br> <br>插入实体<br><br>(20次insert)<br> 15.1<br> 10.8<br> 1.40<br> <br>更新实体<br><br>(20次单条update)<br> 10.4<br> 7.5<br><br>SqlMap:9.3<br> 1.38<br><br>1.12<br> <br>查询结果集(平均101行)<br><br>(1循环200次select)<br> 560<br> 360<br><br>不定字段:380<br> 1.56<br><br>1.47<br> <br><br> <br><br>总之,AppFramework数据库访问组件是一个高性能、接口简单、可移植性强、高灵活性的综合数据访问解决方案。使用AppFramework数据库访问组件,可以降低企业的开发人员培训成本,提高产品的开发速度,提高产品稳定可靠性,提高产品的可伸缩性可移植性。下文将分入门、精通、高级三个篇章,详细讲述如何使用AppFramework数据库访问组件来搭建应用程序。 <br>
| Project | URL | | --- | --- | | partner_modules | http://10.24.71.91/gerrit/c/sharp/vendor/partner_modules/+/142367 | | WIFI | http://10.24.71.91/gerrit/c/PCQ/platform/prebuilts/module_sdk/Wifi/+/142279 | | Uwb | http://10.24.71.91/gerrit/c/PCQ/platform/prebuilts/module_sdk/Uwb/+/142278 | | StatsD | http://10.24.71.91/gerrit/c/PCQ/platform/prebuilts/module_sdk/StatsD/+/142277 | | SdkExtensions | http://10.24.71.91/gerrit/c/PCQ/platform/prebuilts/module_sdk/SdkExtensions/+/142276 | | Permission | http://10.24.71.91/gerrit/c/PCQ/platform/prebuilts/module_sdk/Permission/+/142274 | | OnDevicePersonalization | http://10.24.71.91/gerrit/c/PCQ/platform/prebuilts/module_sdk/OnDevicePersonalization/+/142273 | | MediaProvider | http://10.24.71.91/gerrit/c/PCQ/platform/prebuilts/module_sdk/MediaProvider/+/142272 | | Media | http://10.24.71.91/gerrit/c/PCQ/platform/prebuilts/module_sdk/Media/+/142271 | | IPsec | http://10.24.71.91/gerrit/c/PCQ/platform/prebuilts/module_sdk/IPsec/+/142270 | | conscrypt | http://10.24.71.91/gerrit/c/PCQ/platform/prebuilts/module_sdk/conscrypt/+/142269 | | Connectivity | http://10.24.71.91/gerrit/c/PCQ/platform/prebuilts/module_sdk/Connectivity/+/142268 | | art | http://10.24.71.91/gerrit/c/PCQ/platform/prebuilts/module_sdk/art/+/142267 | | AppSearch | http://10.24.71.91/gerrit/c/PCQ/platform/prebuilts/module_sdk/AppSearch/+/142264 | | AdServices | http://10.24.71.91/gerrit/c/PCQ/platform/prebuilts/module_sdk/AdServices/+/142263 | | Scheduling | http://10.24.71.91/gerrit/c/PCQ/platform/prebuilts/module_sdk/Scheduling/+/142275 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值