Objective - C 可用性檢查
場(chǎng)景:
由于iOS系統(tǒng)每年都會(huì)有新的功能新的API發(fā)布,我們希望能夠把這些新API在我們的App里使用,但是你仍然要支持舊的系統(tǒng)翻翩,你不可能要求安裝你App的用戶的手機(jī)系統(tǒng)都是最新的却紧,這些新的API在舊系統(tǒng)中無(wú)法使用;
但是在iOS系統(tǒng)里支持反向配置轴合,可以設(shè)置build setting
最低支持版本;
但是這樣并不安全碗短,如果你在iOS9的設(shè)備上調(diào)用了iOS11的方法受葛,你的App就很有可能會(huì)Crash
或出現(xiàn)其他意外情況;
下面就來(lái)說(shuō)可用性檢查怎么幫助用戶安全配置App到舊的系統(tǒng)中偎谁?
以前的做法:
- 查詢OC運(yùn)行時(shí)总滩,來(lái)確定API是否適用,但是這樣很容易出錯(cuò)或者忘記判斷直接執(zhí)行巡雨,如果出錯(cuò)很難測(cè)試定位問(wèn)題闰渔,而且它需要不同的語(yǔ)法來(lái)檢查每項(xiàng)全局變量、函數(shù)铐望、類(lèi)冈涧、實(shí)例方法和類(lèi)方法茂附;
- 在Swift 2.0 已經(jīng)支持使用語(yǔ)法關(guān)鍵字
#available
,在運(yùn)行時(shí)查詢API的可用性督弓;編譯器在編譯時(shí)能捕捉缺失的可用性营曼,相關(guān)的可以具體到WWDC 2015 <Swift in Practice>
現(xiàn)在的做法:
- 在iOS11中把Swift的可用性檢查引入到Objective - C
- 如果直接調(diào)用新的API,編譯器會(huì)報(bào)如下警告:
- 使用
@available
查詢API可用性愚隧,來(lái)處理警告
注:當(dāng)當(dāng)前是iOS11蒂阱,
@available
結(jié)構(gòu)返回值為真,這種情況下調(diào)用API很安全狂塘,如果當(dāng)前系統(tǒng)不適合蒜危,則可以在else函數(shù)處理
-
if (@available(iOS 11, *))
在iOS11或者更新的系統(tǒng)里返回真,*
號(hào)表明在其它所有平臺(tái)上查詢?yōu)檎妫ū热鏼acOS) - 可用性針對(duì)新系統(tǒng)定制的一套功能很方便
應(yīng)用指定方法:在方法實(shí)現(xiàn)里無(wú)需再加@available
檢查可用性睹耐,但調(diào)用該方法的人需要使用辐赞,否則會(huì)收到警告
@interface MyAlbumController : UIViewController
- (void)showFaces API_AVAILABLE(ios(11.0));
@end
應(yīng)用到指定類(lèi):
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));
// 修飾類(lèi)
class API_AVAILABLE(ios(11.0), macos(10.13)) MyClassForiOS11OrNewer;
建議:對(duì)于現(xiàn)有項(xiàng)目,不建議直接使用新的API硝训,需要使用@available
或者API_AVAILABLE
檢查新API的可用性
對(duì)于查找定位bug响委,以下介紹一些Xcode的新功能,如靜態(tài)分析新功能和編譯器警告~
Analyzer 靜態(tài)分析新功能
Analyzer
擅長(zhǎng)捕捉難以重現(xiàn)的極端的bug窖梁,下面介紹新加入Analyzer
的三種情況:
對(duì)于 NSNumber
和CFNumberRef
的一些錯(cuò)誤比較方式
- 錯(cuò)誤一:
NSNumber
指針值直接和0比赘风,這個(gè)操作實(shí)際上是將指針值和nil相比較
@property NSNumber *photoCount;
- (BOOL)hasPhotos {
return self.photoCount > 0; // X 錯(cuò)誤:不能用NSNumber直接和0比較
}
@property NSNumber *photoCount;
- (BOOL)hasPhotos {
return self.photoCount.integerValue > 0; // 正確: compare integer value to integer value
}
- 錯(cuò)誤二:布爾運(yùn)算的隱式變換的歧義
@property NSNumber *faceCount;
- (void)identifyFaces {
if (self.faceCount) // 歧義:這里`faceCount`是為nil還是0的時(shí)候return?
return;
// Expensive Processing
}
明確的和nil做比較!
@property NSNumber *faceCount;
- (void)identifyFaces {
if (self.faceCount) != nil)
return;
// Expensive Processing
}
在Xcode設(shè)置檢查選項(xiàng):
函數(shù) dispatch_once()
的使用注意
這個(gè)函數(shù)它保證這個(gè)代碼塊會(huì)被調(diào)用一次并且只有一次纵刘,常用于初始化共享全局狀態(tài)邀窃;
確保代碼塊只執(zhí)行一次,第一個(gè)參數(shù)必須是global
或則 static
的變量
解決方案:使用NSLock
確保初始化只執(zhí)行一次
@implementation Album {
NSLock *photosLock;
}
[photosLock lock];
if (self.photos == nil) {
self.photos = [self loadPhotos];
}
[photosLock unlock];
關(guān)于NSMutable
類(lèi)的copy
屬性的檢查
定義一個(gè)可變類(lèi)型的
property
用copy
修飾時(shí)假哎,一般會(huì)在該屬性的Setter方法里對(duì)屬性進(jìn)行-copy
操作瞬捕,這樣會(huì)導(dǎo)致該可變類(lèi)型變成不可變類(lèi)型
會(huì)有如下問(wèn)題:
Analyzer 中的提示信息:
解決方案:在Setter方法明確的執(zhí)行 -mutableCopy
,確保屬性是可變的
相關(guān)WWDC議題:Finding Bugs Using Xcode Runtime Tools
編譯器警告
Xcode9新加了100多個(gè)錯(cuò)誤和警告舵抹,來(lái)幫助我們調(diào)試和處理問(wèn)題肪虎,下面有兩個(gè)很重要的錯(cuò)誤警告:
在ARC的Block里捕獲參數(shù):
一般來(lái)說(shuō),在ARC的Block里捕獲大多數(shù)的參數(shù)都很安全
請(qǐng)找出下面代碼會(huì)出問(wèn)題的地方:
- (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里分配參數(shù)是很不安全的
// if (error) *error = [[[NSError errorWithDomain:...] retain] autorelease]; //默認(rèn)會(huì)加上`__autoreleasing`
}];
return isValid;
}
注意:
- 在 Block里分配參數(shù)是很不安全的惧蛹,在ARC Block的外部參數(shù)會(huì)隱式的被加上
__autoreleasing
-
enumerateKeysAndObjectsUsingBlock
這個(gè)block 內(nèi)部默認(rèn)有autoreleasepool
具體警告如下:
解決方案:使用__strong
修飾輸出參數(shù)扇救,確保輸出時(shí)對(duì)象存在,沒(méi)有被銷(xiāo)毀
聲明沒(méi)有參數(shù)的方法
在iOS9香嗓,需要明確指定無(wú)參為void
, 不然會(huì)報(bào)如下警告:
明確設(shè)置函數(shù)的無(wú)參void
后迅腔,對(duì)該函數(shù)傳遞參數(shù)會(huì)直接報(bào)錯(cuò):
在 Build Setting里配置:
(LTO:Link-Time Optimization)鏈接時(shí)間優(yōu)化更新
相關(guān)WWDC 2016:What's New in LLVM