在 iOS
开发中,对外发布的 TestFlight
版或者正式版都是以 Relase
方式构建。而我们一般会在 Debug
模式下开启某些调试功能或打印日志,但是这些操作在 Release
无效。
当我们需要在 Release
版本中启用调试功能时,就不太方便了。而解决这个问题似乎没有很优雅的方式。
在读过 How to Enable Custom Debugging in Release Builds 这篇文章后,发现一个比较新奇的方法。
常规方式
文章前半部分提供了几种可能的方式,比如:
使用一个新的配置定义
TESTFLIGHT
。
在发版本的时候打两个包,一个是开启TESTFLIGHT
配置,另一个关闭TESTFLIGHT
。但这并不能很好的区分哪个包对应的是什么配置。硬编码
userId
。
只对指定的userId
生效。但添加/删除需要修改userId
列表,且有些功能并不需要登录。可以进一步延伸到使用deviceId
,通过服务端下发列表。检查安装来源。
区分TestFlight/App Store/本地 build
几种安装方式。而苹果并没提供官方API
来获取来源,作者提供了一个hack
的方式,但可靠性有待确定,因为这种方式极大的依赖苹果的实现。-
使用秘密的手势。
使用某种复杂的手势来作为开关。但是如果有用户知道了这个手势,也可以调起调试功能。这种方式让我想起上家公司有关某个功能调试开关的实现,思路比较好玩。
在
app
的某个页面,比如「设置 - 关于我们」(最好是无额外点击事件的页面)。把当前页面按照数字0-9
分为10
个虚拟区域,类似拨号键盘排布。如下图所示:
而密码就是当前的手机时间,以 4
位数字表示,不足补 0
。在页面上按照预设区域输入当前时间(当前实际上看不到任何分隔线的,估摸大致区域),即可打开调试开关。
-
使用秘密的
url scheme
。通过特定
url scheme
来唤起app
,以开启调试功能。但它有几个弊端:- 为了防止被破解,
url
不能太简单,而且开发者需要记住该url
,调试不便。 - 由于需要在浏览器中输入
url
,那么意味着需要离开app
进行操作,增加调试成本。 - 如果有其他人意外知道了
url
,同样也可以调试。
- 为了防止被破解,
重头戏
下面重头戏来了,其方式是通过检测一个特殊的描述文件 Debug Profile
是否在机器上安装,来确定是否开启调试功能。
这种方式有如下好处:
- 如果想要调试就必须有描述文件,而普通用户无法获取。
- 只需安装一次描述文件。
- 一个描述文件 可以被多个
app
使用,不同优先级的功能可以使用不同的描述文件。 - 从
App Store
安装的app
也可以使用,因为只需要有描述文件。 - 不需要特殊的环境和用户。
但是也有其坏处:
- 需要创建存储证书、 描述文件。
- 需要根据检查证书信任结果来确定是否安装了描述文件,并且作者承认,检查方式有一点
hack
。 - 需要将描述文件存起来,不能丢失。
原理
这种方式是基于 SecTrustEvaluate
方法来验证证书是否受信任。
使用 SecTrustEvaluate
来确定用户是否安装且信任了证书(通过安装描述文件),还是仅仅打包在 app
中未受信任的证书。
因此我们需要做的事情如下:
- 创建证书
- 创建和安装描述文件
- 检查证书是否被信任
下面我们来一步步的操作。
1. 创建证书
a. 打开证书助理,选择创建证书颁发机构。
b. 填入名称和邮箱,反选 Make this CA the default
,并勾选 Let me override defaults
。
c. 点击继续,修改有效期为 7300
天,即为 20
年。
d. 下一步,选择证书。这里我选择的是自己账号的证书。
e. 接下来,一直下一步,直至创建完成。
点击 Show CA Certificate
可以看到成功创建的证书。
2. 创建叶子证书
a. 打开证书助理,选择创建证书颁发机构
b. 填入名称和邮箱,反选 Make this CA the default
,并勾选 Let me override defaults
。
注意这里跟第一步有所不同:
Identity Type
选择intermediate CA
。-
User Certificate
选择custom
,找到第一步中创建的xx.certAuthorityConfig
文件。其目录在
~/Library/Application Support/Certificate Authority/<your CA name>/<your CA name>.certAuthorityConfig
c. 修改有效期,同第一步中的 c
。
d. 选择证书,同第一步中的 d
。
e. 选择发行者。
这里选择第一步中创建的证书,我的证书名是 summer
。
f. 一路下一步,直至完成。
3. 导出叶子证书
在 Keychain Access
中,选中第 2
步创建的 leaf certificate
,右键导出为 cer
格式,并加入到自己的工程中。
4. 导出 CA 证书
在 Keychain Access
中,选中第 1
步创建的 root certificate
,右键导出为 cer
格式。不需要添加到工程。
5. 创建 Debug 描述文件
- 下载
Apple Configurator 2
- 点击
File -> New Profile
a. 在General
中填入描述文件名称。
b. 在Certificates
一栏中选择在第4
步中导出的root.cer
。
c.cmd+s
保存Profile
,取个合适的文件名,这里我将其命名为custom-debug.mobileconfig
。
6. 编写检测代码
我将原文中的 SecTrustEvaluate
替换为了SecTrustEvaluateWithError
,因为 SecTrustEvaluate
在 iOS 13
上不再推荐使用。
SecTrustEvaluateWithError
的返回值为 bool
。若为 true
,则表示信任;若为 false
,则表示不受信任。
- (BOOL)IsMobileConfigInstalled {
NSString* certPath = [[NSBundle mainBundle] pathForResource:@"Certificates" ofType:@"cer"];
if (certPath == nil) {
return NO;
}
NSData* certData = [NSData dataWithContentsOfFile:certPath];
if (certData == nil) {
return NO;
}
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) certData);
SecPolicyRef policy = SecPolicyCreateBasicX509();
SecTrustRef trust;
OSStatus err = SecTrustCreateWithCertificates((__bridge CFArrayRef) [NSArray arrayWithObject:(__bridge id)cert], policy, &trust);
NSLog(@"Error Status?: %d", err);
CFErrorRef error;
BOOL result = SecTrustEvaluateWithError(trust, &error);
NSLog(@"%d, %@", result, error);
CFRelease(trust);
CFRelease(policy);
CFRelease(cert);
return result;
}
7. 在需要调试的机器上安装描述文件
- 将手机用
USB
连上电脑,选择对应的设备。 - 点击添加,选择
Profiles
,再选择第5
步中创建的描述文件。
- 此时,在手机上会出现描述文件已经下载的提示。
同时 Apple Configurator
是这个状态,等安装完成会消失。
- 安装描述文件。打开
手机设置 -> 通用 -> 描述文件与设备管理
,选择对应的描述文件进行安装。
8. 运行工程
运行工程,会发现此时 SecTrustEvaluateWithError
返回的 result
为 YES
。
另外可以自行测试一下,如果移除了描述文件 Debug Profile
,该结果则为 NO
。