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