Objective - C 可用性检查
场景:
由于iOS系统每年都会有新的功能新的API发布,我们希望能够把这些新API在我们的App里使用,但是你仍然要支持旧的系统,你不可能要求安装你App的用户的手机系统都是最新的,这些新的API在旧系统中无法使用;
但是在iOS系统里支持反向配置,可以设置build setting最低支持版本;
但是这样并不安全,如果你在iOS9的设备上调用了iOS11的方法,你的App就很有可能会Crash或出现其他意外情况;
下面就来说可用性检查怎么帮助用户安全配置App到旧的系统中?
以前的做法:
- 查询OC运行时,来确定API是否适用,但是这样很容易出错或者忘记判断直接执行,如果出错很难测试定位问题,而且它需要不同的语法来检查每项全局变量、函数、类、实例方法和类方法;
- 在Swift 2.0 已经支持使用语法关键字
#available,在运行时查询API的可用性;编译器在编译时能捕捉缺失的可用性,相关的可以具体到WWDC 2015 <Swift in Practice>
现在的做法:
- 在iOS11中把Swift的可用性检查引入到Objective - C
- 如果直接调用新的API,编译器会报如下警告:

- 使用
@available查询API可用性,来处理警告

注:当当前是iOS11,
@available结构返回值为真,这种情况下调用API很安全,如果当前系统不适合,则可以在else函数处理
-
if (@available(iOS 11, *))在iOS11或者更新的系统里返回真,*号表明在其它所有平台上查询为真(比如macOS) - 可用性针对新系统定制的一套功能很方便
应用指定方法:在方法实现里无需再加@available检查可用性,但调用该方法的人需要使用,否则会收到警告
@interface MyAlbumController : UIViewController
- (void)showFaces API_AVAILABLE(ios(11.0));
@end
应用到指定类:
API_AVAILABLE(ios(11.0))
@interface MyAlbumController : UIViewController
- (void)showFaces;
@end
C/C++的用性检查API
__builtin_available
if (__builtin_available(iOS 11, macOS 10.13, *)) {
CFNewAPIOniOS11();
}
-
API_AVAILABLE宏需要包含<os/availability.h>
#include <os/availability.h>
// 修饰方法
void myFunctionForiOS11OrNewer(int i) API_AVAILABLE(ios(11.0), macos(10.13));
// 修饰类
class API_AVAILABLE(ios(11.0), macos(10.13)) MyClassForiOS11OrNewer;
建议:对于现有项目,不建议直接使用新的API,需要使用@available或者API_AVAILABLE检查新API的可用性
对于查找定位bug,以下介绍一些Xcode的新功能,如静态分析新功能和编译器警告~
Analyzer 静态分析新功能
Analyzer擅长捕捉难以重现的极端的bug,下面介绍新加入Analyzer的三种情况:
对于 NSNumber 和CFNumberRef的一些错误比较方式
- 错误一:
NSNumber指针值直接和0比,这个操作实际上是将指针值和nil相比较
@property NSNumber *photoCount;
- (BOOL)hasPhotos {
return self.photoCount > 0; // X 错误:不能用NSNumber直接和0比较
}

@property NSNumber *photoCount;
- (BOOL)hasPhotos {
return self.photoCount.integerValue > 0; // 正确: compare integer value to integer value
}
- 错误二:布尔运算的隐式变换的歧义
@property NSNumber *faceCount;
- (void)identifyFaces {
if (self.faceCount) // 歧义:这里`faceCount`是为nil还是0的时候return?
return;
// Expensive Processing
}

明确的和nil做比较!
@property NSNumber *faceCount;
- (void)identifyFaces {
if (self.faceCount) != nil)
return;
// Expensive Processing
}
在Xcode设置检查选项:

函数 dispatch_once()的使用注意
这个函数它保证这个代码块会被调用一次并且只有一次,常用于初始化共享全局状态;
确保代码块只执行一次,第一个参数必须是global 或则 static的变量


解决方案:使用NSLock 确保初始化只执行一次
@implementation Album {
NSLock *photosLock;
}
[photosLock lock];
if (self.photos == nil) {
self.photos = [self loadPhotos];
}
[photosLock unlock];
关于NSMutable类的copy 属性的检查
定义一个可变类型的
property用copy修饰时,一般会在该属性的Setter方法里对属性进行-copy操作,这样会导致该可变类型变成不可变类型
会有如下问题:

Analyzer 中的提示信息:

解决方案:在Setter方法明确的执行 -mutableCopy,确保属性是可变的

相关WWDC议题:Finding Bugs Using Xcode Runtime Tools
编译器警告
Xcode9新加了100多个错误和警告,来帮助我们调试和处理问题,下面有两个很重要的错误警告:
在ARC的Block里捕获参数:
一般来说,在ARC的Block里捕获大多数的参数都很安全
请找出下面代码会出问题的地方:
- (BOOL)validateDictionary:(NSDictionary *)dict usingChecker:(Checker *)checker error:(NSError **)error {
__block BOOL isValid = YES;
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([checker checkObject:obj forKey:key]) return;
*stop = YES;
isValid = NO;
if (error) *error = [NSError errorWithDomain:...]; // 在 Block里分配参数是很不安全的
// if (error) *error = [[[NSError errorWithDomain:...] retain] autorelease]; //默认会加上`__autoreleasing`
}];
return isValid;
}
注意:
- 在 Block里分配参数是很不安全的,在ARC Block的外部参数会隐式的被加上
__autoreleasing -
enumerateKeysAndObjectsUsingBlock这个block 内部默认有autoreleasepool
具体警告如下:

解决方案:使用__strong修饰输出参数,确保输出时对象存在,没有被销毁

声明没有参数的方法
在iOS9,需要明确指定无参为void, 不然会报如下警告:

明确设置函数的无参void 后,对该函数传递参数会直接报错:

在 Build Setting里配置:

(LTO:Link-Time Optimization)链接时间优化更新
相关WWDC 2016:What's New in LLVM
相关 Sessions
