支持 Xcode 10.1 或以下版本在 iOS 12.3 上进行真机测试的解决方案

问题的发生​

Apple 在北京时间 5 月 14 日凌晨发布了 iOS 12.3 等系统更新。

一方面,iOS 12.3 更新优化了系统整体流畅性,进行了一系列功能改进[1],最重要的——带回了体验更流畅、观感更舒适的后台任务动画。这些原因让我对这次更新非常期待,阶段性结束了一系列产品需求后,我就把自己的主力机 iPhone XS 升级到了 iOS 12.3。

另一方面,Xcode 在上一个更新周期 (即 Xcode 10.2) 中就不再支持 Swift 3.x。目前我工作中接触的工程绝大多数采用 Swift 3.2 和 Objective-C 混编,少部分升级到了 Swift 4.0,还有极少部分是纯 Objective-C 工程。上一个更新周期,我提取了 Xcode 10.2 中关于 iOS 12.2 的 device support 文件放入老版本的 Xcode 中,算是解决了 IDE 和设备系统不兼容的问题。

这次,在 Xcode 版本没有更新的情况下,为了能够在 iOS 12.3 系统的设备上做真机测试,Xcode 的做法是在 10.2.1 版本上在 IDE 层级上与 iOS 12.2 共用同一套 device support 文件,但是这种方法在 Xcode 10.1 上是失效的。​

问题的解决​

错误的尝试:

将 iOS 12.2 的 device support 复制一份并重命名为”12.3(16F156)”,真机测试后发现并没有生效。

有效的方案:

修改 device support 中的数字签名,重命名文件夹为”12.3(16F156)”。

修改后的文件的下载链接: https://zhr.moe/wp-content/uploads/2019/05/12.3-16F156.zip

使用方法:解压后将文件夹放入 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport ,并重新启动 Xcode。

 

[1] 关于 iOS 12.3 的功能改进参考: https://applech2.com/archives/20190514-apple-ios-12-3-now-available.html ; 关于 iOS 12.3 的安全性内容参考: https://support.apple.com/zh-cn/HT210118

聊聊 App Transport Security 和 HTTPS

App Transport Security

苹果总是成为引领行业的企业,即使很多产品和功能别人早就做出来了,苹果也能靠自己惊人的庞大体量和宣传效果让它火起来。去年,苹果在 iOS 9 和 OS X El Capitan 中引入了 ATS,顾名思义,这项功能屏蔽了所有 HTTP 访问,并保护用户的 HTTPS 访问。

由于这只是 app 中一项起到保护作用的功能,多数开发者一般选择关闭这项功能(下文中会提到)。可能是苹果的责任心爆发,他们要求开发者在 2017 年 1 月 1 日后提交的APP中必须启用 ATS,换句话说,以后的 iOS 应用必须采用 HTTPS 访问,不仅是数据抓取,即使是 web view 访问也必须是 HTTPS 的。

HTTPS

请写出专业名词全称(2 分):

HTTP: Hypertext Transfer Protocol

HTTPS: Hypertext Transfer Protocol Secure

SSL: Secure Sockets Layer

TLS: Transport Layer Security

CA: Certificate Authority

把上面的这些名词串在一起就可以解释 HTTPS 了。它是在 HTTP 的基础上进行加密的协议,而 SSL/TLS (在这里就不解释这二者了,它们可以简单地看做同一个东西在不同阶段的名字)在传输层上完成的就是加密的任务。

HTTPS 的工作原理广义上类似于人的社交关系。用火车站检票举例,火车站信任检票员可以胜任自己的工作,他们负责检查你的票和身份证是否和本人相符。如果你提供的信息是正确的,那么你将借由检票员获得火车站的信任,并允许你乘车。

回到 HTTPS,你信任你的浏览器通过可信的证书提供商 CA 进行验证,浏览器检查网站证书与网站内容是否相符,通过这层验证表示你通过你的浏览器信任了这个 HTTPS 网站。

在这之前可以耍的小聪明

ATS 强制使用 HTTPS,对于遍地 HTTP 的中国互联网环境来说,是个坏消息。虽然 HTTP 和 HTTPS 在迁移上并不复杂,但是一个好用的且可用于商业用途的HTTPS 证书价格不菲,这导致开发者一般都选择屏蔽这个功能。

在 info.plist 文件中,新建一个键为 NSAppTransportSecurity,在其中新建 Boolean 型的键 NSAllowsArbitraryLoads 设置为 YES。

访问 HTTP 网页报错

为了模拟这种情况,创建一个新工程,并添加了一个 webView,加入如下代码:

运行后,不会加载 QQ 的主页,同时会收到访问被禁止的 log,这表示 ATS 完全屏蔽了 HTTP 的访问。

访问 HTTPS 网页报错(-9802)

将上述的代码域名改为 https://www.baidu.com/ 后,得到了报错。

为什么我们访问了HTTPS协议的网站却依然报错呢?这里我们必须参考 NSAppTransportSecurity 的官方文档查找答案。

根据文档,ATS 不仅要求请求必须是 HTTPS 协议的,还有其他的 3 点要求:

1.TLS 协议的版本必须在 TLS1.2 以上

2.要求支持完全正向保密(Perfect Forward Privacy)的加密方式,列举如下:

  • TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
  • TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
  • TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
  • TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
  • TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
  • TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
  • TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
  • TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
  • TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
  • TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
  • TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA

3.签名算法至少使用一个 SHA256 指纹和一个高于 2048 位的 RSA(Rivest-Shamir-Adleman) 或高于 256 位的 ECC(Elliptic-Curve Cryptography) 秘钥

检查百度的证书,发现它没有符合上述的第二个条件,引起了 ATS 的报错。

AFNetWorking + HTTPS(-1012)

作为 iOS/OS X 平台上最好的网络访问库,AFNetWorking 本身是支持 HTTPS 访问的,而且对于 HTTPS 的要求在某些方面甚至高于 ATS 本身。

由于私人开发者一般不愿意购买权威 CA 颁发的证书,所以 AFNetWorking 会判断我们的证书是非法的,这时我们有两种方法选择。

1. 让 AFNetWorking 完全不检测证书

早期的 AFNetWorking 中,我们可以通过引用的 pch(Precompile prefix header),设置一下 AFNetWorking 的配置:

后来,这种宏的设定失效了,于是我们可以修改库的安全政策:

另外,由于域名审核也是一个非常麻烦的过程,有的开发者还没有自己的域名,这时还需要设置不匹配域名:

2. 让 AFNetworking 检测本地提供的个人证书

这种方法则是把自己当做一个 CA,自己一定是信任自己的,由此检查访问的域名是否匹配。

由于库要求检查 cer 文件,如果你手中的证书是 pem 文件则可以进行转换:

然后设置安全策略:

AFNetworking 自动检查 bundle 当中的证书,这种方式的安全性相比第一种高了一些。

定制 ATS

这里列举的一些方式可能会在未来必须采用ATS后失效,但在目前是可用的。

1. 设置不使用 ATS 的域

在 info.plist 的 ATS 部分中的 NSExceptionDomains 中添加域名,并在其中将 NSIncludesSubdomains 和 NSExceptionsAllowsInsecureHTTPLoads 均设为 YES。

2. 设置使用 ATS 的域

首先按照上面的方法将 NSAllowsArbitraryLoads 设为 YES,并在 NSExceptionDomains 中添加域名,把 NSExceptionsAllowsInsecureHTTPLoads 置为 NO。

3. 降低 ATS 要求

同样在 NSExceptionDomains 中添加域名。如果需要降低 TLS 版本则修改 NSExceptionMinimumTLSVersion 为 TLSv1.1 或 TLSv1.0。如果不支持正向保密,则设置NSExceptionRequiresForwardSecrecy 为 NO。

小结

ATS 作为苹果保证用户安全地访问提供的一项功能,填补了在移动端用户不能了解自己是否访问了加密的站点的盲区。对这一功能的强行要求,可能会加强开发者甚至是用户对网络安全的重视。但即使如此,开发者也不能对安全问题掉以轻心,反而在关键服务上应该更加小心。

iOS 9.3 以上系统版本的迷之卡顿

作为一名SIF玩家,在不适当的时候弹出一条推送或者收到一个电话绝对有让人摔手机的冲动。但是自从升级到 iOS 9.3 以后,SIF 又出了个新的毛病——迷之卡顿。

不仅仅是日服客户端,盛大版、美服都遇到了这个问题。在不同版本出现了同一问题,我把问题归结为iOS在相关接口和实现上的改动。但是,这种情况的出现没有任何规律可循,不知道什么时候就会卡顿,而不是一直都在卡。(事实上,查阅 iOS 9.3 的 API Differences ,并没有发现 API 实现上的更新,苹果一般也不会在这样的版本里大幅度调整 API)

直到我在城际上玩 SIF,出站的时候突然发生了卡顿,随后屏幕跟着周围光线变得更亮,我开始怀疑这种卡顿的出现和屏幕亮度的自动调节相关。单一变量测试,关闭自动调节屏幕亮度之后,很长一段时间内没有出现卡顿。另外,即使是在不是游戏这种大量消耗资源的应用里,亮度的自动调节也会带来掉帧,这样一来几乎可以确认这个问题的源头了。

只不过,我们并不了解 iOS 9.3 后对屏幕亮度的调节具体做了哪些改变,我只能猜测是它使用了不该使用的资源,如果你对手机的帧率有非常高的要求并可以牺牲亮度调节功能的话(比如玩游戏),可以选择暂时关闭这个功能。

P.S. 关于自动调节亮度和 Night Shift 的一点解释

iOS 9.3 引入了 Night Shift 功能,即在夜间开启防蓝光模式,也就是俗话说的暖屏模式。理论上,降低蓝光有两种解决方案:

  1. 降低亮度
  2. 减弱蓝光

苹果用户应该比较了解,苹果的设备在最低亮度下依然保持着相当高的亮度,这是由屏幕的特性导致的。由此,苹果决定采用第二种方式,让屏幕变暖来解决问题。

你可能会问,是不是屏幕同时要照顾到亮度调节以及变暖才导致了卡顿呢?我认为理论上不是这样的。亮度调节是由屏幕背光板来完成的,而减弱蓝光则是完全让屏幕显示的颜色在 RGB 分布上向 R 倾斜,是由两个机制操作完成的,理论上不会出现互相占用资源的情况。

后续情况

非常不幸的是,6 月 6 日发布的最新测试版 iOS 9.3.3 beta2 中,自动调节亮度依然有掉帧的问题。好在 iOS 10 的预览版就要更新了,我们也只能期待这个问题尽快被解决了。

无法识别的 Selector 发送给实例的快速排错法

How to solve

Unrecognized selector sent to instance… 应该是 iOS 开发中比较常见的一种问题,但是一般这种错误的报错会指向 main.m,一时间很难找到是哪个对象发生了问题。这时,我们可以下一个 Debug 断点。

在 Xcode 的菜单栏中选择 Debug -> Breakpoints -> Create Symbolic Breakpoint… ,在弹出的标签的 Symbol 栏填入

这时再次运行就会发现真正出现问题的地方了。

Example

今天在给 WebView 的一个属性赋值的时候一直报错。如果初始化 WebView 时不涉及这个属性的话,则不会出现问题。虽然我已经定位了问题的所在,但是如何解决依然没有头绪。添加断点后立刻发现,给这个属性传入的对象的某个属性出现了循环引用,改成 weak 后解决了问题。