Swift软件开发工具包快速入门
Swift软件开发工具包快速入门
欢迎阅读 云眼灰度发布(特性标帜) 的 Swift SDK 快速入门指南。
按照本指南中的步骤可以完成创建灰度发布(特性标帜)标帜、推出标帜,以及运行 A/B 测试等操作。
第一部分:创建和配置灰度发布(特性标帜)标帜
1. 创建“全栈灰度发布(特性标帜)”项目
需要一个 云眼帐户才能完成本指南的操作。如果还没有帐户,可以在云眼平台(https://app.eyeofcloud.com/register)注册一个免费帐户。如果已有帐户,请打开或创建一个新的灰度发布(特性标帜)项目。
对于新注册的帐号,在欢迎页面里点击“创建灰度发布(特性标帜)项目”。
对于已经建立项目的帐号,点击切换项目->新建项目->创建灰度发布(特性标帜)项目,即可创建新项目。
2. 在项目中创建buy标帜
灰度发布(特性标帜)项目创建完毕后,转到主页面菜单栏上“灰度发布(特性标帜)”页面,点击创建标帜,将新标帜命名为“buy”。
3. 在buy标帜中添加变量
buy标帜建立后,点击此标帜,在标帜设置-缺省变量处依次添加变量,本案例为了简化操作和节省时间,变量名称和缺省值都采用云眼平台提供的默认值。
4. 在事件管理中增加事件buy
转到菜单栏上“事件管理”页面,点击新建事件按钮。将事件标识命名为buy,点击创建事件,创建完成。
第二部分:编码实现灰度发布(特性标帜)和AB测试
1. 安装 云眼灰度发布(特性标帜) Swift SDK。
要求
- Swift 客户端应用程序必须使用 Swift 5 或更高版本。
- 支持的最低操作系统版本是 iOS/9.0 和 tvOS/9.0。
云眼 Swift SDK 可通过 CocoaPods 或 Swift Package Manager (SPM) 进行分发。可以将此 SDK 与用 Swift 和 Objective-C 编写的应用程序一起使用。
将此行添加到 Podfile: 苹果 Podfile
pod 'EyeofcloudSwiftSDK','~> 3.10.1'
运行命令: Shell pod install
有关更多安装信息,请参阅 CocoaPods 入门指南。
File > Swift Packages > Add Package Dependency 输入 https://gitee.com/eyeofcloud/swift-sdk 接受缺省规则 (Version: 'Up to Next Major').
完整的源代码可在gitee上找到。
2. 调用SDK方法,创建eyeofcloudClient实例
在代码合适的位置(在 MyApplication.java 文件里)创建eyeofcloudClient实例,并把实例放在全局变量中,以便后续使用它。创建eyeofcloudClient实例需要传入sdkKey。本演示程序为了演示方便从页面输入sdkKey,但是实际应用时,需要直接写在代码里。sdkKey可以在云眼平台上拿到。
在代码能够获得用户id和属性信息的位置,通过调用eyeofcloudClient实例的方法createUserContext,得到user对象,user对象非常重要,它包含AB测试需要的2个主要方法decide和trackEvent。调用createUserContext需要传入两个参数:userId 和 attributes。 可以用设备加随机数作为userId,只要能够唯一识别用户即可。attributes是key、value对组成的数组,一般基于业务系统中的用户属性信息来构建。 user对象产生后,可以把它作为属性变量添加到app对象上,以便在其他地方可以使用。
ViewController.swift
import UIKit
import Eyeofcloud
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let eyeofcloudClient = EyeofcloudClient(sdkKey: "YOUR_SDK_KEY", defaultLogLevel: .error)
// this Eyeofcloud initialization is asynchronously. for other methods see the Swift SDK reference
eyeofcloudClient.start { result in
switch result {
case .success(let datafile):
// --------------------------------
// to get rapid demo results, generate random users. Each user always sees the same variation unless you reconfigure the flag rule.
// --------------------------------
var hasOnFlags = false
for _ in 0...9 {
let userId = String(Int.random(in: 1000...9999))
// --------------------------------
// Create hardcoded user & bucket user into a flag variation
// --------------------------------
let user = eyeofcloudClient.createUserContext(userId: userId)
// "product_sort" corresponds to a flag key in your Eyeofcloud project
let decision = user.decide(key: "product_sort")
// did decision fail with a critical error?
if decision.variationKey == nil || decision.variationKey == "" {
print("decision error: \(decision.reasons)" )
}
// get a dynamic configuration variable
// "sort_method" corresponds to a variable key in your Eyeofcloud project
let sortMethod: String? = decision.variables.getValue(jsonPath: "sort_method")
// always returns false until you enable a flag rule in your Eyeofcloud project
if decision.enabled {
// Keep count how many visitors had the flag enabled
hasOnFlags = true
}
// --------------------------------
// Mock what the users sees with print statements (in production, use flag variables to implement feature configuration)
// --------------------------------
print("\n\nFlag \(decision.enabled ? "on" : "off"). User number \(user.userId) saw flag variation: \(decision.variationKey ?? "") and got products sorted by: \(String(describing: sortMethod!)) config variable as part of flag rule: \(decision.ruleKey ?? "")")
}
if !hasOnFlags {
var projectId: Any?
if let config: Any = try? JSONSerialization.jsonObject(with: datafile, options: []), let convertedDict = config as? [String: Any] {
projectId = convertedDict["projectId"]
}
print("\n\nFlag was off for everyone. Some reasons could include:\n1. Your sample size of visitors was too small. Rerun, or increase the iterations in the FOR loop\n2. Check your SDK key. Verify in Settings>Environments that you used the right key for the environment where your flag is toggled to ON.\ncheck your key at https://app.eyeofcloud.com/v2/projects/\(String(describing: projectId!))/settings/implementation")
}
case .failure(_):
print("Eyeofcloud client invalid. Verify in Settings>Environments that you used the primary environment's SDK key")
}
}
}
}
3. 调用decide(),获取并使用变量。
在buy页面加载时,调用user的一个重要方法:decide,获得当前用户在灰度发布(特性标帜)buy的抽签分桶结果decision对象,decision里包含抽中的各个变量的值。我们可以用这些变量值控制页面显示,业务逻辑,AI参数,推荐算法等。
BuyViewModel.java
package com.eyeofcloud.demo.flag.ui.buy;
import Swift.os.Bundle;
import Swift.view.LayoutInflater;
import Swift.view.View;
import Swift.view.ViewGroup;
import Swift.widget.Button;
import Swift.widget.TextView;
import Swiftx.annotation.NonNull;
import Swiftx.appcompat.app.AlertDialog;
import Swiftx.fragment.app.Fragment;
import Swiftx.lifecycle.ViewModelProvider;
import Swiftx.navigation.NavController;
import Swiftx.navigation.Navigation;
import com.eyeofcloud.demo.flag.MyApplication;
import com.eyeofcloud.demo.flag.R;
import com.eyeofcloud.demo.flag.databinding.FragmentBuyBinding;
public class BuyFragment extends Fragment {
private FragmentBuyBinding binding;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
MyApplication app = (MyApplication) getActivity().getApplication();
BuyViewModel buyViewModel =
new ViewModelProvider(this).get(BuyViewModel.class);
binding = FragmentBuyBinding.inflate(inflater, container, false);
View root = binding.getRoot();
final TextView buttonTextView = binding.button;
buyViewModel.getStringVariable().observe(getViewLifecycleOwner(), buttonTextView::setText);
final TextView ruleKeyTextView = binding.ruleKey;
buyViewModel.getRuleKey().observe(getViewLifecycleOwner(), ruleKeyTextView::setText);
final TextView variationKeyTextView = binding.variationKey;
buyViewModel.getVariationKey().observe(getViewLifecycleOwner(), variationKeyTextView::setText);
final TextView stringTextView = binding.stringValue;
buyViewModel.getStringVariable().observe(getViewLifecycleOwner(), stringTextView::setText);
Button button = binding.button;
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// trackEvent
app.getUser().trackEvent("buy");
}
});
if (app.getSdkKey() != null && !app.getSdkKey().isEmpty()) {
buyViewModel.init(app);
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
String connection = MyApplication.getConnectivityStatusString(getContext());
// Set the message show for the Alert time
builder.setMessage("Connection status: " + connection +",SDK Key尚未设置,是否使用缺省SDK Key:" + MyApplication.DEFUAULT_SDK_KEY + "?");
// Set Alert Title
builder.setTitle("注意!");
// Set Cancelable false for when the user clicks on the outside the Dialog Box then it will remain show
builder.setCancelable(false);
// Set the positive button with yes name Lambda OnClickListener method is use of DialogInterface interface.
builder.setPositiveButton("是,用缺省", (dialog, which) -> {
// When the user click yes button then app will close
//finish();
app.setSdkKey(MyApplication.DEFUAULT_SDK_KEY);
buyViewModel.init(app);
NavController navController = Navigation.findNavController(getActivity(), R.id.nav_host_fragment_activity_main);
navController.navigate(R.id.navigation_buy);
});
// Set the Negative button with No name Lambda OnClickListener method is use of DialogInterface interface.
builder.setNegativeButton("否,去设置", (dialog, which) -> {
// If user click no then dialog box is canceled.
dialog.cancel();
NavController navController = Navigation.findNavController(getActivity(), R.id.nav_host_fragment_activity_main);
navController.navigate(R.id.navigation_config);
});
// Create the Alert dialog
AlertDialog alertDialog = builder.create();
// Show the Alert Dialog box
alertDialog.show();
}
return root;
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}
4. 调用trackEvent方法,将事件发出
BuyFragment.java
package com.eyeofcloud.demo.flag.ui.buy;
import Swift.os.Bundle;
import Swift.view.LayoutInflater;
import Swift.view.View;
import Swift.view.ViewGroup;
import Swift.widget.Button;
import Swift.widget.TextView;
import Swiftx.annotation.NonNull;
import Swiftx.appcompat.app.AlertDialog;
import Swiftx.fragment.app.Fragment;
import Swiftx.lifecycle.ViewModelProvider;
import Swiftx.navigation.NavController;
import Swiftx.navigation.Navigation;
import com.eyeofcloud.demo.flag.MyApplication;
import com.eyeofcloud.demo.flag.R;
import com.eyeofcloud.demo.flag.databinding.FragmentBuyBinding;
public class BuyFragment extends Fragment {
private FragmentBuyBinding binding;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
MyApplication app = (MyApplication) getActivity().getApplication();
BuyViewModel buyViewModel =
new ViewModelProvider(this).get(BuyViewModel.class);
binding = FragmentBuyBinding.inflate(inflater, container, false);
View root = binding.getRoot();
final TextView buttonTextView = binding.button;
buyViewModel.getStringVariable().observe(getViewLifecycleOwner(), buttonTextView::setText);
final TextView ruleKeyTextView = binding.ruleKey;
buyViewModel.getRuleKey().observe(getViewLifecycleOwner(), ruleKeyTextView::setText);
final TextView variationKeyTextView = binding.variationKey;
buyViewModel.getVariationKey().observe(getViewLifecycleOwner(), variationKeyTextView::setText);
final TextView stringTextView = binding.stringValue;
buyViewModel.getStringVariable().observe(getViewLifecycleOwner(), stringTextView::setText);
Button button = binding.button;
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// trackEvent
app.getUser().trackEvent("buy");
}
});
if (app.getSdkKey() != null && !app.getSdkKey().isEmpty()) {
buyViewModel.init(app);
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
String connection = MyApplication.getConnectivityStatusString(getContext());
// Set the message show for the Alert time
builder.setMessage("Connection status: " + connection +",SDK Key尚未设置,是否使用缺省SDK Key:" + MyApplication.DEFUAULT_SDK_KEY + "?");
// Set Alert Title
builder.setTitle("注意!");
// Set Cancelable false for when the user clicks on the outside the Dialog Box then it will remain show
builder.setCancelable(false);
// Set the positive button with yes name Lambda OnClickListener method is use of DialogInterface interface.
builder.setPositiveButton("是,用缺省", (dialog, which) -> {
// When the user click yes button then app will close
//finish();
app.setSdkKey(MyApplication.DEFUAULT_SDK_KEY);
buyViewModel.init(app);
NavController navController = Navigation.findNavController(getActivity(), R.id.nav_host_fragment_activity_main);
navController.navigate(R.id.navigation_buy);
});
// Set the Negative button with No name Lambda OnClickListener method is use of DialogInterface interface.
builder.setNegativeButton("否,去设置", (dialog, which) -> {
// If user click no then dialog box is canceled.
dialog.cancel();
NavController navController = Navigation.findNavController(getActivity(), R.id.nav_host_fragment_activity_main);
navController.navigate(R.id.navigation_config);
});
// Create the Alert dialog
AlertDialog alertDialog = builder.create();
// Show the Alert Dialog box
alertDialog.show();
}
return root;
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}
5. 运行一下小程序
至此,编码工作完成,但是灰度发布(特性标帜)标志还没有启动。我们先看一下在不启动灰度发布(特性标帜),用缺省值来运行小程序时界面的样子。此时,按钮显示的是缺省的“购买”两个字。
第三部分:运行灰度发布(特性标帜)实验
小程序产品或运营人员,在云眼灰度发布(特性标帜)平台上操作来改变按钮的文字,并跟踪和评估各个变体的转化效果。
1. 修改buy标帜变量形成不同变体
点击buy标帜,将缺省变量中string variable的默认值设置为“购买”。添加两个变体:变体1和变体2,变体1中设置string variable值为“立即购买”,变体2中设置string variable值为“我想要”。
2. 为灰度发布(特性标帜)buy配置AB测试规则
配置完buy标帜的变量和变体后,开始配置buy标帜的AB测试规则,在development环境下点击“增加规则”->“AB测试”, 实验受众选择缺省的所有访客,百分比为100%,指标设置为事件buy的唯一转化,变体1和变体2的流量权重设置为50%对50% 点击保存。
3. 启动并运行灰度发布(特性标帜)。
buy标帜规则设置完成后,我们打开buy标帜。
然后模拟用户访问buy页面。可以看到,此次访问结果为变体1,购买按钮的文字信息为“立即购买”,说明将用户分桶到了变体1。我们可以卸载并重新访问小程序,来模拟另一个新用户,这次购买按钮的文字信息是“我想要”,说明用户分桶到了变体2。实际运营时,可以做很多种实验,比如定向到不同受众,配置各种类型的指标,以及人工智能流量调整等,我们会有专门视频来讲解。
|||
第四部分、分析结果,推出获胜变体
分析结果,推出获胜变体。小程序灰度发布(特性标帜)标帜推出一段时间后,我们分析实验结果,根据实验结果推出获胜变体。
1. 查看实验报告,分析结果。
点击buy标帜上AB测试中的“查看结果”按钮。
根据变体1和变体2主指标数值和统计显著性来判定获胜者,统计显著性一般要大于95%。本案例中可以看出变体1胜出,指标提升55.69%,统计显著性达到98%。
2. 根据实验结果推出获胜变体。
确定胜出变体后,我们可以将将少部分流量(比如5%)继续用于AB测试,其余流量(95%)推出胜出的变体1。
本教程只是指导您完成最简单的灰度发布(特性标帜),在灰度发布(特性标帜)之前进行 A/B 测试。
下表显示灰度发布(特性标帜)和 A/B 测试之间的差异:
灰度发布(特性标帜) | A/B 测试规则 |
---|---|
可以将标帜推广到一定比例的一般用户群(或特定受众),或者在遇到错误时回滚。 | 在投资交付之前,通过 A/B 测试标帜进行实验,这样您就知道要构建什么。跟踪用户在标帜变体中的行为,然后使用 云眼平台 统计引擎解释实验结果。 |
回顾和结论
为了实施本案例的AB测试,我们总共做了4部分的工作,可以简单概括为:实验的设计、实现、实施和行动,与PDCA过程类似。
每部分工作都需要处理AB测试相关的信息,并由相关人员来负责完成,我们把参与AB测试的人员分为业务人员和技术人员两大类。
部分 | 工作内容 | 概括 | 处理信息 | 参与者 |
---|---|---|---|---|
一 | 创建灰度发布(特性标帜)标帜 | 设计 | 变量、事件 | 业务人员:产品、运营、数据; 技术人员:开发、QA |
二 | 灰度发布(特性标帜)编码实现 | 实现 | 变量、事件 | 技术人员:开发、QA |
三 | 配置并运行灰度发布(特性标帜) | 实施 | 实验、变体、指标、流量 | 业务人员:产品、运营、数据 |
四 | 分析结果,推出获胜变体 | 行动 | 实验、变体、指标、流量、结果、推出 | 业务人员:产品、运营、数据 |
从表格中,我们可以看到,只有第一部分工作是由业务人员和技术人员共同完成的,而他们共同处理的信息只有变量和事件,在后面的第三和第四部分中更多的信息,技术人员不需要知道。这样的好处是,可以尽最大可能减少业务人员和技术人员之间的相互依赖。业务人员在实施AB测试过程中,不需要依赖技术人员;技术人员在实现AB测试时也不依赖业务人员。大家可以相对独立、高效的进行工作。这是云眼灰度发布(特性标帜)/AB测试方案的一大优势。 另外,云眼AB测试结束后可以无缝的实现对特定人群的定向发布,做到AB测试和灰度发布(特性标帜)的有机结合,这是云眼产品的另一大特点。此外,云眼产品在AB测试/灰度发布(特性标帜)各个环节里还有很多独特的功能,我们将在后续的视频中详细介绍。
祝贺!您已成功设置并启动了第一个灰度发布(特性标帜)实验。虽然此示例侧重于优化销售过程,但 云眼 实验平台可以支持任何场景的实验用例。
培训视频可以从这里观看
本案例的 Swift App demo代码可以从这里获取