整理 iOS 9 适配中出现的坑

2015年10月19日 11:11 0 点赞 0 评论 更新于 2025-11-21 19:12

本文主要介绍一些 iOS 9 适配中出现的问题。如果您只是想单纯了解 iOS 9 新特性,可以查看瞄神的《开发者所需要知道的 iOS 9 SDK 新特性》。

9 月 17 日凌晨,苹果向用户推送了 iOS 9 正式版。随着用户陆续升级,逐渐衍生出一系列问题。笔者也在为自己维护的 App 进行适配,本文所提及的问题基本都是亲身体验。

一、NSAppTransportSecurity

iOS 9 中,所有 HTTP 请求默认使用 HTTPS,原来基于 HTTP 协议的传输改为使用 TLS 1.2 协议。这直接导致 App 发起请求时,可能会弹出网络无法连接的提示。

解决方案

在项目的 info.plist 文件中添加如下节点:

NSAppTransportSecurity - NSAllowsArbitraryLoads

该子节点的含义是是否允许任意加载。将其设置为 YES,可以禁用 App Transport Security,转而使用用户自定义的设置,从而解决此问题。

需要注意的是,虽然苹果限制了 HTTP 协议,但并非所有 HTTPS 都能完美适配 iOS 9。例如,在 App 内使用 webView 加载 HTTPS 网页时,新建项目并编写加载网页的代码:

// 示例代码,中间的 url 为想要加载的 https 地址
UIWebView *webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
NSURL *url = [NSURL URLWithString:@"https://your-url.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[webView loadRequest:request];
[self.view addSubview:webView];

分别使用 https://baidu.com/https://github.com/ 进行测试,会得到不同的结果。GitHub 的网页可以正常打开,而百度的网页无法打开,同时控制台会打印如下日志:

NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)

根据苹果官方资料,要使 HTTPS 请求正常工作,首先必须基于 TLS 1.2 版本协议,并且证书的加密算法需要达到 SHA256 或者更高位的 RSA 密钥或 ECC 密钥。如果不符合这些要求,请求将被中断并返回 nil

在浏览器中,可以通过点击地址栏的绿锁图标,再点击证书信息,直接查看网站的加密算法。从相关截图可以看出,GitHub 采用带 RSA 加密的 SHA - 256 算法,符合苹果的要求,因此可以正常展示。

针对百度的情况,可以在 info.plist 中进行如下配置。如果引用的网站较多,需要针对每个网站进行单独配置:

NSAppTransportSecurity
NSExceptionDomains
baidu.com
NSIncludesSubdomains
NSExceptionRequiresForwardSecrecy
NSExceptionAllowInsecureHTTPLoads

其中,ForwardSecrecy 是一种超前的密码保护算法,官方资料中列出了共 11 种。配置完成后,百度网页即可正常访问。

二、Bitcode

Bitcode 可以理解为将程序编译成的一种过渡代码,苹果会将其进一步编译成可执行程序。Bitcode 允许苹果在后期重新优化程序的二进制文件,具有类似 App 瘦身的思想。

使用 Xcode 7 编译器编译之前正常的项目时,可能会出现如下报错:

XXXX’ 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. for architecture arm64

问题原因是某些第三方库还不支持 Bitcode。解决办法有两种:一是等待库的开发者升级该功能并更新库;二是禁用 Bitcode。

禁用 Bitcode 的方法是找到相关配置,将其选为 NO(在 iOS 中,Bitcode 默认值为 YES;在 watchOS 中,Bitcode 必须为 YES,不可更改)。

三、设置信任

此问题仅与企业级应用或 in - house 应用有关,与通过 App Store 渠道发布的应用无关。

在 iOS 8 中,安装企业级应用时会弹出一个窗口,询问是否信任该应用;而在 iOS 9 中,应用会直接被禁止。若要信任该应用,需要用户手动开启。这类似于在 Mac 系统中,从未知开发者处下载的 .dmg 文件无法直接打开,需要到系统偏好设置的安全性与隐私中手动开启。以下是 iOS 8 和 iOS 9 的对比截图(此处可插入相应截图)。

用户需要前往“设置” -> “通用” -> “描述文件” 中自行添加信任。

处理此类问题的方法有两种:一是提前告知用户暂时不要升级到 iOS 9;二是对于大多由公司员工使用的企业级应用,可群发指导邮件。

四、字体

在 iOS 8 中,系统字体为 Helvetica,中文字体类似于“华文细黑”。由于苹果手机自带渲染,其显示效果可能比普通的华文细黑更美观。而在 iOS 9 中,中文系统字体变为专为中国设计的“苹方”,类似于 Word 中的“幼圆”字体。该字体有轻微加粗效果,且字体间隙变大。

这可能导致很多原本固定宽度的 label 出现显示省略号(...)的情况。通过对比 iOS 8 和 iOS 9 下同一界面、同一 label 的显示效果(此处可插入相应截图),可以直观地看出这种变化。

为避免界面显示出错,即使是固定长度的文字,也建议使用 sizeToFit 方法,或者使用 ceilf() 进行向上取整,也可以提前计算文字的尺寸:

CGSize size = [title sizeWithAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:14.0f]}];
CGSize adjustedSize = CGSizeMake(ceilf(size.width), ceilf(size.height));

五、URL scheme

URL scheme 通常用于应用程序的分享或跳转到其他平台进行授权,完成操作后再跳转回原应用。

在 iOS 8 中,对 URL scheme 的使用没有过多限制;但在 iOS 9 中,需要将外部调用的 URL scheme 添加到白名单中,才能完成跳转。

如果在 iOS 9 中未进行适配,会出现如下错误:

canOpenURL: failed for URL : "mqzone://qqapp" - error: "This app is not allowed to query for scheme mqzone"

具体的解决方案是在 info.plist 中设置 LSApplicationQueriesSchemes,其类型为数组,并添加所有使用到的 scheme。

六、statusbar

此问题仅会产生一个警告,即使不处理也不会影响应用的正常运行。警告信息如下:

: CGContextSaveGState: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.

在以往的开发中,为了实时控制顶部状态栏的样式,我们可能会使用以下代码:

[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
[[UIApplication sharedApplication] setStatusBarHidden:YES];

但在使用这些代码之前,需要在 info.plist 中添加 View controller - based status bar appearance,并将其 BOOL 值设为 NO,即禁用控制器对状态栏的控制权限,转而使用 UIApplication 进行控制。不过,这种做法在 iOS 9 中不建议使用。建议将该 BOOL 值设为 YES,然后使用控制器的方法来管理状态栏,例如:

// 在 UIViewController 子类中重写方法
- (BOOL)prefersStatusBarHidden {
return YES;
}

- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}

查看头文件可以验证上述说法:

@property(readwrite, nonatomic,getter=isStatusBarHidden) BOOL statusBarHidden NS_DEPRECATED_IOS(2_0, 9_0, "Use -[UIViewController prefersStatusBarHidden]");

七、didFinishLaunchingWithOptions

如果在运行时出现如下错误,说明 didFinishLaunchingWithOptions 方法的实现可能存在问题:

***** Assertion failure in -[UIApplication _runWithMainScene:transitionContext:completion:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3505.16/UIApplication.m:3294**

在 iOS 9 中,不允许在 didFinishLaunchingWithOptions 方法结束后仍未设置 windowrootViewController,这可能是由于 Xcode 7 编译器的要求。

解决方法是先为 rootViewController 初始化一个值,之后再根据需要进行替换:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
// 先初始化一个 rootViewController
UIViewController *initialVC = [[UIViewController alloc] init];
self.window.rootViewController = initialVC;
[self.window makeKeyAndVisible];
// 后续可以替换 rootViewController
// ...
return YES;
}

八、tableView

尽管 iOS 9 已经发布正式版,但在使用过程中,会感觉到 App 比以前更加卡顿,其中 tableView 拖动时的卡顿现象最为明显。此外,还遇到过一个问题:原本正常的项目使用 Xcode 7 编译后,tableView 的刷新出现问题,调用 [tableView reloadData] 方法无效,某一行 cell 内容发生改变,但界面未刷新。推测可能是该方法与某些新特性冲突,reloadData 的操作被推迟到下一个 RunLoop 执行,最终导致失效。

解决方法是注释掉 [tableView reloadData] 方法,改用局部刷新:

[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone];

希望以上内容能帮助各位 iOS 开发者及时做好 iOS 9 适配,解决相关问题。祝愿大家的应用在下个版本上线后,所有问题都能得到解决。

作者信息

洞悉

洞悉

共发布了 3994 篇文章