调用高版本API会有什么问题?
在iOS7上,调用iOS8新加入的方法会导致崩溃。
- (BOOL) findString:(NSString *)s inString:(NSString *)destString {
NSString * lowDest = destString.lowercaseString;
return [lowDest containsString:s];
}
NSString
的containsString
方法是在iOS8才被加入的,只要在iOS7上调用了containsString
方法就会直接崩溃:
在iOS7上初始化iOS8才有的对象会返回nil。虽然不会崩溃,但是可能会导致预料外的行为。
LAContext* context = [[LAContext alloc] init];
self.touchIDEnabled =
[context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
error:&error];
LAContext
是一个iOS8才加入的对象。在iOS7上,[[LAContext alloc] init]
会返回nil。
调用context
的canEvaluatePolicy:error:
方法,依然返回nil。self.touchIDEnabled
就被设置成了NO。
按照代码逻辑,在iOS7上self.touchIDEnabled
恰好就应该被设置为NO,在这个例子中不会有更严重的问题发生,但如果这个变量是self.touchIDDisabled
,事情就不好说了。
一定要用低版本系统不支持的API的时候该怎么办?
iOS系统更新会带来新的接口和特性,我们总是会遇到不得不用新版本API的情况,所以代码中必须兼容新旧两种系统:
- 通过判断iOS系统版本来兼容
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
// Do something
}
- 在调用API前检查
respondsToSelector
if ([cell respondsToSelector:@selector(setPreservesSuperviewLayoutMargins:)]) {
[cell setPreservesSuperviewLayoutMargins:NO];
}
- 在创建对象时判断
[ClassName class]
是否为nil
- (BOOL)alertControllerAvailable {
return [UIAlertController class] != nil; // iOS 8 and later.
}
从代码的可维护性来考虑,比较推荐后两种方式。
检查API可用性的工具
比较好的方案是用MJGAvailability.h在编译期检查。
把MJGAvailability.h文件加入工程中,在预编译头文件最开头加上下面的代码即可:
#define __IPHONE_OS_VERSION_SOFT_MAX_REQUIRED __IPHONE_7_0
#import "MJGAvailability.h"
其中的__IPHONE_7_0
定义在Availability.h文件中,可以改成需要的任何系统版本。
配置完成后,调用不可用的API会出现如下形式的警告:
xxxxxxx.m:64:18: 'containsString:' is deprecated: TOO NEW!
如果配置后编译没有生效,把Build Settings里面的Enable Modules (C and Objective-C)
项改为NO试试,具体原因我还不知道是为什么。
这个工具无法检查出我们的代码有没有进行过版本兼容处理,它会对所有有问题的代码报错。所以我们要在处理过兼容性的地方,显式的用宏把代码包起来:
MJG_START_IGNORE_TOO_NEW
if ([cell respondsToSelector:@selector(setPreservesSuperviewLayoutMargins:)]) {
[cell setPreservesSuperviewLayoutMargins:NO];
}
if ([cell respondsToSelector:@selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}
MJG_END_IGNORE_TOO_NEW
另外有一个叫做Faux Pas的代码静态分析工具,可以进行API可用性的检查,但因为它的试用版随机隐藏了检查结果,所以就没再研究了。