编者按:微信作为国内最大的移动社交软件和开放平台,其技术也一直为外人所好奇。InfoQ 特地与微信团队合作推出系列专栏,介绍支撑亿级并发背后的技术。
每年 iOS 升级,都会带来一些坑,这次 iOS9 也不例外。本文总结了微信在适配 iOS9 上遇到的问题和解决方案。
一、iOS9 问题汇总
1. 编译问题(Bitcode)
大部分人升级到 Xcode7 后,首先遇到的问题是编译不过,错误提示大致是
xxx does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target.
这是因为 Xcode7 默认启用 Bitcode,但是如果我们用到的第三方库编译时还没启用 Bitcode,主工程就会编译不过。
最简单的解决办法是先把 Bitcode 关掉:把 Build settings - Build Options - Enable Bitcode 改为 NO。不过,这只是权宜之计。Bitcode 是苹果 App Thinning 的机制之一,可以减少安装包的大小,等我们把所有库都替换成支持 Bitcode 之后,主工程就可以启用 Bitcode 了。
2、HTTP 请求失败
解决了编译问题后,程序跑起来了,却发现很多网络请求失败。这是因为 iOS9 默认不支持 HTTP 请求,需要改用更安全的 HTTPS(默认用 TLS 1.2)。
但事实上,有些地方用 HTTP 比 HTTPS 更适合,而且把服务端升级到 TLS 1.2 也不是一时半会能够搞定的。幸好苹果还提供了配置,使得所有安全性更低的网络请求也能使用,解决方案就是在 info.plist 里面增加以下配置:
<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict>
如果复杂一些,还可以指定白名单域名,声明所支持 TLS 的最低版本,这里就不再详细描述了。
另外需要注意的是,即使写了上述配置,在 HTTPS 页面中,HTTP 的 javascript 或 css 不会被加载,因为苹果认为这降低了页面的安全性。
3、canOpenUrl 限制
canOpenUrl 可以用来判断用户是否安装了某个 APP。也许是出于用户隐私的考虑,iOS9 上对 canOpenUrl 做了限制,最多只能对 50 个 scheme 做判断。
如果是用 Xcode7 编译,需要在 plist 里面声明这些 scheme,没有声明的会直接返回 NO:
<key>LSApplicationQueriesSchemes</key> <array> <string>weixin</string> <string>wechat</string> </array>
如果是用 Xcode6 编译,系统会在用户手机上记住 APP 每次调用 canOpenUrl 的 scheme,如果累计达到 50 种,剩下的其它调用,也会直接返回 NO。所以在 iOS9beta 刚出来的时候,有些用户无法从微信跳转到第三方 app,就是因为已经达到了限制数量,系统直接返回 NO,程序以为用户没有安装该 APP,就没有去跳转。
解决办法是加白名单,并且尽量减少不必要的 canOpenUrl 调用,以免超过 50 个名额的限制。
例如,openUrl 函数是不受限制的(在 iOS9 的某 beta 版上,openUrl 也受同样限制,但跟苹果沟通后确认是 iOS 的 bug,后面的版本也已经更正过来了),所以对于
if (canOpenUrl(scheme)) then openUrl(scheme); else xxx;
这种只需要改写成
if (!openUrl(scheme)) then xxx;
就不用占用白名单了。
4、systemName
[[UIDevice currentDevice] systemName] 在过去版本中一直返回"iPhone OS",但在 iOS9.1 beta 中,这个函数返回值变成了"iOS"。
这个看似不起眼的改动,却使得微信出现了很多问题。刷了 9.1beta 的用户会发现,所有的公众号消息、小视频、红包等消息都无法查看,登陆验证也会失败。这是因为后台依赖 systemName 来判断设备类型,未知类型会使得后台以为该设备不支持某些功能,导致该功能失效。
解决方法是后台修改判断条件,并吸取教训支持可配置,上线后解决了这个问题。
然而,在 iOS9.1 正式版上,苹果又把 systemName 改回"iPhone OS"了。或许苹果也发现这个小小的改动会引起一些致命问题,所以又改了回来。
5、preferredLanguages
[NSLocale preferredLanguages] 会返回用户的首选语言。在之前的版本,系统用"zh-Hans"来表示简体中文,这个常量在 iOS9.0beta 上也是如此。然而到了 iOS9.0 正式版,苹果突然在后面加了国家码后缀,变成了"zh-Hans-CN"。但是,对于台湾繁体中文,却没有变化,依然是"zh-TW"。
这个变动导致部分用户升级到 iOS9,微信语言变成了英文。这是因为程序在用户首选语言中没匹配到简体中文的选项。
目前我们解决办法是改用前缀匹配。
6、API 更新
iOS9 照例淘汰了一些旧接口,其中有一些旧接口虽然还能用,但或多或少都会有些问题:
- 6.1 AddressBookUI.framework 在 iOS9 上已经被淘汰,需要改用 ContactsUI.framework
旧接口还能够读取通讯录,但是添加信息到通讯录时,系统界面会卡住。 - 6.2 UIAlertView 需要改成 UIAlertController
旧接口还能够使用,只是在有键盘的情况下弹 UIAlertView,可能会有键盘闪现等体验问题。 - 6.3 UIPopoverController 需要改用普通的 UIViewController,设置 modalPresentationStyle=UIModalPresentationPopover,然后 present 出来
旧接口也能够使用,但在 iPad 分屏下会有问题。
7、windowLevel 问题
如下图所示,当键盘已经弹起的时候,再显示我们自己写的确认窗口等 window,会发现 window 被键盘挡住了。
这是因为 iOS9 下系统键盘的 windowLevel 是很高的,达到 10^7。而且进一步发现,这个值是系统允许的最大值。如果把某个 window 的 windowLevel 改成比 10^7 大的值,系统只会设为 10^7。
解决这个问题有两种方法:
一个是把我们自己 window 的 level 调大,同样设为 10^7,因为比系统键盘晚出现,所以还是能够把系统键盘盖住。这种方法的缺点是使得 window 的层次结构不好管理,且依赖于系统键盘的 level。而且 window 上也无法再显示 UIAlertView 等系统窗口了。
另一种方法是在显示 window 时先调用 [mainWindow endEditing:YES],把主 window 的键盘收起来,然后再显示 window。这种方法的缺点是,有些场景下用户是正在输入的,收起键盘对用户的体验不好。
两种方法各有优缺点,可以根据使用场景来选择。
8. 启动 crash(window.rootViewController 问题)
crash 信息为:
Application windows are expected to have a root view controller at the end of application launch
原因是启动完的时候,如果现有的 window 没有 rootViewController,就会 crash。
解决办法就是按要求设置 rootViewController。
注:启动完后再生成的 window,可以不设 rootViewController,但还是建议以后所有 window 都要设。
二、iPad 分屏
1、如何启用 iPad 分屏
a. 用 Xcode7 iOS9 SDK 编译
b. 用 Launch StoryBoard 做启动界面
c. 支持所有的旋转方向
需要注意的是,支持分屏后,iPad 上所有界面都需要支持转屏。如果以前通过 supportedInterfaceOrientations 等函数来限制某些界面在 iPad 上不能转屏,在启用分屏后这个限制将失效。
如果不支持分屏,需要在项目设置中的 General - Deployment Info 中勾选 Requires full screen
2、如何适配 iPad 分屏
分屏和转屏本质上都是改变了屏幕的尺寸。正常来说,如果界面适配了 iPad 转屏(不管是用哪种方式,例如 AutoLayout,或者 AutoResizing,或者是在 viewDidLayoutSubviews 里面重新排版,等等),那在 iPad 分屏下也能够正常显示。(除了一些特殊情况,例如 hardcode 了屏幕尺寸等,见后面第 3 点。)
如果界面在不同尺寸的屏幕下有不同的排版设计,官方的建议是根据系统回调在 Regular 模式和 Compact 模式之间切换。微信因为是使用了配置文件来处理不同设备的排版差异的,所以根据自己的实际情况,采用以下原则:在 320 屏幕下按照 iPhone5 的排版;438 屏幕下按照 iPhone6 的排版,其它分屏下按照 iPad 的排版。
3、分屏后的几个问题
3.1 有了分屏后,APP 当前屏幕的大小不能再用 [UIScreen mainScreen].bound 来获取了,这个取到的是整个设备的屏幕大小,不是分屏后的屏幕大小。
解决办法是,启动时初始化 window,不需要 initWithFrame,直接用 init 就可以了。系统知道当前屏幕的大小,会帮我们正确地设置 frame。然后取这个 frame 就能拿到实际屏幕大小了。
3.2 以前适配 iPad 转屏时,有些地方会使用 willRotateToInterfaceOrientation 等转屏回调来处理屏幕尺寸变化。从 iOS8 开始,系统新增了 viewWillTransitionToSize:withTransitionCoordinator 回调来代替它。新的回调可以用来处理转屏和分屏引起的屏幕尺寸变化。
3.3 分屏状态下,系统的视频录制功能不可用。如果某个功能用到了视频录制功能,建议像系统照相机一样,在分屏时给用户提示一下。
3.4 避免 hardcode。要注意 iPad 的屏幕不再是 1024*768,而且在运行中屏幕的尺寸是会随时变化的(分屏或转屏时),所以如果以前有些代码做了 hardcode,会导致分屏后有 bug。
三、总结
本文总结了微信在适配 iOS9 中遇到的常见问题,相信 iOS9 还有其它深坑有待挖掘,欢迎大家补充。
评论