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