本文主要介绍iOS如何在云眼平台使用简单API实施AB测试。
简单API是使用版本抽签接口(即activate函数)得到版本名称,依据版本名称编写if-else分支代码,不同版本执行不同分支代码从而形成不同版本。该方案每次进行新的测试时需要修改代码和重新发布应用程序,灵活性相对较弱,但是简单易用,一般适合用于比较固定不变的两个或多个版本的AB测试场景。
阅读提示:第1~3节适合业务(产品、市场、运营、数据等)人员阅读;第4~8节,适合技术人员(程序员)阅读。
1、设计优化方案
根据想到的假设,设计出AB测试的方案,云眼系统中称作“优化方案”,也叫做“试验”或“实验”。我们需要考虑好有哪些版本,衡量结果的唯一主目标是什么。设计优化方案时最好提出一种明确的假设,比如“橙色按钮比起黑色按钮更能引起注意从而带来更多注册”,而不是“按钮的颜色可能会影响注册”这种模糊的假设。明确的假设能让测试的成功率更高。
2、配置优化方案
2.1创建优化方案
在云眼控制台中点击“业务优化”,等到页面加载完成后点击“创建优化方案”按钮。平台选择“全栈API”,填写优化方案名称,点击“确认”创建优化方案。创建完成后,点击对应优化方案的“编辑”按钮来打开编辑器。
2.2添加版本并分配流量
在“版本设置”中根据优化方案设计的情况来添加版本并分配流量,通常情况下均分流量即可。总流量根据实际流量调整,业务流量较大的应用选择部分流量参与测试就足够了。
2.3设置目标
根据优化方案设计的情况,将需要用到的衡量指标创建为一个目标。点击“+”号将其添加到优化方案中。
2.4设置受众
设置受众可以将优化方案定位到在云眼控制台预先定义的特定受众群体中,云眼将会在激活优化方案时通过属性值评估用户是否满足受众条件,从而决定是否激活优化方案。在云眼控制台中点击“业务优化”,业务优化界面打开后再点击“自定义属性(全栈)”。点击创建自定义属性,输入自定义属性的名称和描述,点击确定按钮。
然后在编辑器中点击“创建受众”,输入受众的名称和描述,将右侧已创建好的自定义属性拖拽至受众条件下,然后输入一个属性的值,例如:male,然后点击“保存受众”按钮。(注意:创建的自定义属性的名称和输入的值最终会形成键值对的形式:”Gender”:”male”)。
2.5启动优化方案
启动优化方案,完成配置工作。
3、查看实验结果
当实验运行一段时间后,就可以到云眼控制台查看实验的运行结果。
4、简单API AB测试
对于简单API方法,给出两种常见的使用方式来产生多个版本。点击这个链接可以阅读 iOS AB测试API参考文档
4.1第一种简单API方式
第一种方式是在打开新的View前,进行版本抽签,依据抽签结果创建不同的View从而形成不同的版本。这种方式比较适合版本差异较大的情况,直接制作多个View作为不同的版本。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 设置根视图控制器 self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; UIViewController *fakeRootViewController = [[UIViewController alloc] init]; UIImageView *imageView = [[UIImageView alloc] initWithFrame:UIScreen.mainScreen.bounds]; [imageView setImage:[UIImage imageNamed:@"LaunchImage.png"]]; fakeRootViewController.view = imageView; self.window.rootViewController = fakeRootViewController; [self.window makeKeyAndVisible]; // 初始化EOCManager _eocManager = [EOCManager init:^(EOCManagerBuilder * _Nullable builder) { builder.projectId = @"PROJECT_ID"; }]; // 异步网络任务下载配置文件,请求超时时间为5s [_eocManager initializeWithCallback:^(NSError * _Nullable error, EOCClient * _Nullable client) { // 完成下载及版本抽签,根据抽签结果创建不同版本的View,最后更新主界面 dispatch_async(dispatch_get_main_queue(), ^{ EOCVariation *variation = [client activate:@"登录按钮实验" userId:[self getUserId] attributes:[self getUserAttributes]]; YFLoginViewController *loginViewController = [[YFLoginViewController alloc] init]; if ([variation.variationKey isEqualToString:@"优化版本#1"]) { loginViewController.view.backgroundColor = [UIColor orangeColor]; } self.window.rootViewController = loginViewController; }); }]; return YES; }
4.2第二种简单API方式
第二种方式是在需要体现版本差异的地方进行版本抽签,依据抽签结果进入不同的代码逻辑,从而使得应用的界面或者行为变得不同。例如,通过代码修改界面的样式。
- (void)loadView { self.loginView = [[YFLoginView alloc] init]; self.view = _loginView; // Eyeofcloud AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; self.eocClient = [[delegate getEOCManager] getEyeofcloud]; self.userId = [delegate getUserId]; self.userAttributes = [delegate getUserAttributes]; EOCVariation *variation = [self.eocClient activate:@"登录按钮实验" userId:self.userId attributes:self.userAttributes]; UIColor *loginButtonColor = [UIColor colorWithRed:0.29 green:0.73 blue:0.97 alpha:1.00]; if ([variation.variationKey isEqualToString:@"优化版本#1"]) { loginButtonColor = [UIColor orangeColor]; } _loginView.loginButton.backgroundColor = loginButtonColor; }
完成版本差异化的代码之后,添加触发目标的代码。
- (void)loginButtonAction { [[_loginView.loginButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) { [self.eocClient track:@"点击按钮" userId:self.userId attributes:self.userAttributes]; [self.loginViewModel.loginCommad execute:nil]; }]; }
调用EOCManager的getEyeofcloud方法得到的EOCClient对象是在应用启动时创建的。在这之后,本地缓存的配置文件是会自动定时更新的,相应EOCClient对象中配置也会自动更新,因此无需重新调用initialize方法构造新的EOCClient对象。
4.3简单API中使用参数
使用简单API方式可以和参数化API进行配合使用,先调用简单API的activate方法激活一个试验得到版本名称,然后调用参数化API variableString/variableBoolean/variableDouble/variableFloat来获取在云眼控制台定义的变量值,需要提供3个参数,分别是变量名称、用户ID、布尔值true/false,其中最后一个参数的布尔值代表是否激活试验,我们只需要获取变量的值便于使用而并不需要激活试验,所以我们最后一个参数传递false。在这种情况下,我们使用activate API激活试验得到的是哪个版本,则使用参数化API获取到的变量就是我们在云眼控制台定义的该版本的值。例如:我们用activate激活试验返回的是优化版本,则我们使用参数化API获取到的变量值也是优化版本中的变量值。
- (void)loadView { self.loginView = [[YFLoginView alloc] init]; self.view = _loginView; //Eyeofcloud AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; self.eocClient = [[delegate getEOCManager] getEyeofcloud]; self.userId = [delegate getUserId]; self.userAttributes = [delegate getUserAttributes]; EOCVariation *variation = [self.eocClient activate:@”登录按钮试验”userId:self.userId attributes:self.userAttributes]; NSString *accountPlaceHolder = [self.eocClient variableString:@”LoginAccountPlaceHolder” userId:self.userId attributes:self.userAttributes activateExperimetn:false] //在不同的版本中使用获取到的变量值 UIColor *loginButtonColor = [UIColor colorWithRed:0.29 green:0.73 blue:0.97 alpha:1.00]; if([variation.variationKey isEqualToString:@”优化版本#1”]){ loginButtonColor = [UIColor orangeColor]; } _loginView.loginButton.backgroundColor = loginButtonColor; }
5、SDK安装
打开云眼控制台(app.eyeofcloud.com),登录后点击“业务优化”,业务优化界面打开后再点击“SDK下载与安装”。
在新打开的页面中,选择“全栈API SDK安装”标签,再从下方的标签栏中选择“Objective-C”。根据下方提示完成SDK的安装。
6、SDK初始化
所有和AB测试相关的接口都是定义在EOCClient这个类上的,通过这个类才能够版本抽签、触发目标、获取参数从而进行AB测试。同时,SDK还提供了一个EOCManager类来负责EOCClient的构造和获取、配置文件(包含AB测试信息)的下载和更新、配置文件的缓存管理等等。所以,初始化SDK的第一步就是构造一个EOCManager。建议在Appdelegate上设置一个私有属性保存全局唯一的一个EOCManager,再设计一个getEOCManager的接口将其暴露出来。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 初始化EOCManager _eocManager = [EOCManager init:^(EOCManagerBuilder * _Nullable builder) { builder.projectId = @"PROJECT_ID"; }]; return YES; }
- (EOCManager *)getEOCManager { return _eocManager; }
参数PROJECT_ID可以从“SDK下载与安装”界面中查到。
如果您在使用私有部署的云眼系统,则需要修改配置文件和事件发送的URL。 首先,实例化EOCDatafileManagerDefault类和EOCEventDispatcherDefault类,在各自的初始化方法里设置相应的URL。然后,调用EOCManager的初始化方法,并将以上两个类的实例作为参数传入并赋值给builder 。
EOCDatafileManagerDefault *datafileManager = [EOCDatafileManagerDefault init:^(EOCDatafileManagerBuilder * _Nullable builder) { builder.projectId = @"PROJECT_ID"; builder.networkServiceCDNServerURL = @"配置文件URL"; }]; EOCEventDispatcherDefault *eventDispatcher = [EOCEventDispatcherDefault init:^(EOCEventDispatcherBuilder * _Nullable builder) { builder.eventDispatcherEventURL = @"事件发送URL"; }]; // 初始化EOCManager EOCManager *eocManager = [EOCManager init:^(EOCManagerBuilder * _Nullable builder) { builder.projectId = @"PROJECT_ID"; builder.datafileManager = datafileManager; builder.eventDispatcher = eventDispatcher; }];
配置文件URL和事件发送URL可以在“SDK下载与安装”界面的Objective-C全栈API使用说明中查看。
在构造完EOCManager对象后,我们需要调用EOCManager的initializeWithCallback方法完成EOCClient的初始化。该方法会执行一个异步网络任务去下载配置文件,然后根据配置文件构造一个EOCClient对象并将其保存在eocManager中。在之后需要使用SDK的地方,通过EOCManager的getEyeofcloud方法获取缓存的EOCClient对象再来调用相关接口。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 设置根视图控制器 self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; UIViewController *fakeRootViewController = [[UIViewController alloc] UIImageView *imageView = [[UIImageView alloc] initWithFrame:UIScreen.mainScreen.bounds]; [imageView setImage:[UIImage imageNamed:@"LaunchImage.png"]]; fakeRootViewController.view = imageView; self.window.rootViewController = fakeRootViewController; [self.window makeKeyAndVisible]; // 初始化EOCManager _eocManager = [EOCManager init:^(EOCManagerBuilder * _Nullable builder) { builder.projectId = @"PROJECT_ID"; }]; // 异步网络任务下载配置文件,请求超时时间为5s [_eocManager initializeWithCallback:^(NSError * _Nullable error, EOCClient * _Nullable client) { // 完成配置文件下载以及EOCClient的初始化,进入主界面 dispatch_async(dispatch_get_main_queue(), ^{ YFLoginViewController *loginViewController = [[YFLoginViewController alloc] init]; self.window.rootViewController = loginViewController; }); }]; return YES; }
这里需要注意的一点是,在网络不通的情况下,EOCClient初始化就会失败。例如,首次启动APP时,会触发一个弹框,要求用户设定APP的网络权限。在完成设定之前,APP的网络请求是被禁止的,从而导致EOCClient初始化失败。为了保证AB测试的顺利进行,就要把EOCClient的初始化推迟到APP联网之后。一个简单的办法就是使用AFN监听APP的网络状态,一旦APP联网就开始初始化EOCClient以及加载其它资源,然后重新渲染APP的界面。
在调用SDK的接口时,会需要一个唯一的userId来标识用户。另外,如果要使用受众功能,还会需要用户的属性值。为了方便,可以在Appdelegate中定义两个方法getUserAttributes 和getUserId 。
- (NSString *)getUserId { if (!_userId) { _userId = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; } return _userId; } - (NSDictionary *)getUserAttributes { if (!_userAttributes) { _userAttributes = @{@"Device":@"iPhone SE"}; } return _userAttributes; }
7、常见问题
Q:上传APP的ipa至iTunes Connect时,如果报错Unsupported Architectures. Your executable contains unsupported architecture ‘[x86_64, i386]’,该如何解决?
A:报错原因是APP项目包含x86_64和i386架构(这两种架构只用于模拟器)的Framework,解决办法就是在Archive项目时使用脚本去除这两种架构。首先,在Xcode中选择项目 -> Targets -> Build Phases。接着,在Xcode的菜单栏中选择Editor -> Add Build Phase -> Add Run Script Build Phase。
然后,拷贝以下代码并粘贴至Run Script的代码框中。
APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}" # This script loops through the frameworks embedded in the application and # removes unused architectures. find "$APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK do FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable) FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME" echo "Executable is $FRAMEWORK_EXECUTABLE_PATH" EXTRACTED_ARCHS=() for ARCH in $ARCHS do echo "Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME" lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH" EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH") done echo "Merging extracted architectures: ${ARCHS}" lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}" rm "${EXTRACTED_ARCHS[@]}" echo "Replacing original executable with thinned version" rm "$FRAMEWORK_EXECUTABLE_PATH" mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH" done
最后,再次Archive项目并重新上传ipa至iTunes Connect。