如你所知,已廢棄(Deprecated)的API指的是那些已經(jīng)過(guò)時(shí)的并且在將來(lái)某個(gè)時(shí)間最終會(huì)被移除掉的方法或類。通常,蘋果在引入一個(gè)更優(yōu)秀的API后就會(huì)把原來(lái)的API給廢棄掉。因?yàn)椋乱氲腁PI通常意味著可以更好的發(fā)揮新硬件或操作系統(tǒng)的性能,或者可以使用一些在構(gòu)建原有API時(shí)根本還沒(méi)有的語(yǔ)言特性(e.g. blocks)。
每當(dāng)蘋果添加新方法的時(shí)候,他們都會(huì)在方法聲明的后面用一個(gè)很特殊的宏來(lái)標(biāo)明哪些iOS版本支持它們。例如,在UIViewController中,蘋果引入了一個(gè)使用block來(lái)處理回調(diào)的方法用來(lái)展示一個(gè)模態(tài)controller,它的聲明是這樣的:
-(void)presentViewController:(UIViewController*)viewControllerToPresentanimated:(BOOL)flagcompletion:(void(^)(void))completion ?NS_AVAILABLE_IOS(5_0);
注意到NS_AVAILABLE_IOS(5_0)了嗎?這就告訴我們這個(gè)方法可以在iOS5.0及以后的版本中使用。如果我們?cè)诒戎付ò姹靖系陌姹局姓{(diào)用這個(gè)方法,就會(huì)引起崩潰。
那被這個(gè)方法替換了的那個(gè)舊方法又怎么樣了呢?同樣,它的聲明后面也帶了一個(gè)類似的語(yǔ)法,表示它已經(jīng)被廢棄了:
-(void)presentModalViewController:(UIViewController*)modalViewControlleranimated:(BOOL)animated ?NS_DEPRECATED_IOS(2_0,6_0);
NS_DEPRECATED_IOS(2_0, 6_0)這個(gè)宏中有兩個(gè)版本號(hào)。前面一個(gè)表明了這個(gè)方法被引入時(shí)的iOS版本,后面一個(gè)表明它被廢棄時(shí)的iOS版本。被廢棄并不是指這個(gè)方法就不存在了,只是意味著我們應(yīng)當(dāng)開始考慮將相關(guān)代碼遷移到新的API上去了。
還有類似形式的一些宏用在iOS和OS X共用的類上。比如NSArray中的這個(gè)方法:
-(void)setObject:(id)objatIndexedSubscript:(NSUInteger)idx ?NS_AVAILABLE(10_8,6_0);
這里的NS_AVAILABLE宏告訴我們這方法分別隨Mac OS 10.8和iOS 6.0被引入。和NS_DEPRECATED_IOS類似,也有個(gè)宏叫NS_DEPRECATED,但它的參數(shù)要稍微復(fù)雜些:
1-(void)removeObjectsFromIndices:(NSUInteger*)indicesnumIndices:(NSUInteger)cnt ?NS_DEPRECATED(10_0,10_6,2_0,4_0);
這里表示這個(gè)方法隨Mac OS 10.0和iOS 2.0被引入,在Mac OS 10.6和iOS 4.0后被廢棄。
Easy Come, Easy Go
?在iOS7和Mac OS 10.9 SDK中被新引入的Base64 API。有趣的是,有一組有相同功能的Base64方法,在被引入的同時(shí)也被廢棄掉了。為什么蘋果在引入一個(gè)API的同時(shí)又把它廢棄掉了?那不是毫無(wú)意義的嗎?好吧,其實(shí)也不是——它在下面這種情況下就非常有意義:
實(shí)際上,這些現(xiàn)在已經(jīng)廢棄的Base64方法從iOS4和Mac 0S 10.6開始就一直存在,只是它們是私有的。直到現(xiàn)在蘋果才把它們公開,大概是蘋果一直對(duì)它們的實(shí)現(xiàn)不滿意,一直都想把它們改寫。
果然,在iOS7中,蘋果選定了一個(gè)他們感到滿意的Base64 API,并且將它添加到了NSData的一個(gè)公有類別中。但現(xiàn)在,他們知道老方法已經(jīng)被取代,不會(huì)被改寫了,因此他們把它公開出來(lái)。當(dāng)開發(fā)者的app仍然需要支持iOS6及以前的版本時(shí),就有了一個(gè)系統(tǒng)內(nèi)置的Base64 api可以用。
這就是為什么,如果你查看這些新API的方法聲明,可以看到NS_DEPRECATED宏部分中的起始版本是4_0,雖然實(shí)際上直到iOS7之前,它從來(lái)都沒(méi)有被作為公有API被引入過(guò):
?-(NSString*)base64Encoding ? NS_DEPRECATED(10_6,10_9,4_0,7_0);
這告訴你,基于iOS7 SDK開發(fā)的app如果調(diào)用了這個(gè)方法,它同樣可以運(yùn)行在iOS4+或Mac OS 10.6+的系統(tǒng)上而不會(huì)崩潰。很有用的吧?
如何使用已廢棄的API
那么,如果我們有一個(gè)app需要同時(shí)支持iOS6和iOS7,想用內(nèi)置的Base64方法,我們?cè)撛趺醋瞿??事?shí)上,這相當(dāng)簡(jiǎn)單,你只需要調(diào)用這些廢棄的API就可以了。
那樣編譯器不是會(huì)產(chǎn)生警告嗎?不會(huì)——只有你的deployment target版本號(hào)設(shè)置成大于或等于方法被棄用的版本號(hào)的時(shí)候才會(huì)收到編譯器警告。只要你仍然在支持那些還沒(méi)有廢棄這個(gè)方法的iOS版本,都不會(huì)收到警告。
那么,如果蘋果決定在iOS8中移除已棄用的Base64方法,你的應(yīng)用程序會(huì)發(fā)生情況?簡(jiǎn)單來(lái)說(shuō),它肯定會(huì)崩潰,但是不要讓這把你嚇跑了:蘋果不可能只在幾個(gè)iOS版本后就將已廢棄的API給移除(絕大多數(shù)已廢棄的API在任何的iOS版本中都還沒(méi)有被移除),除非你決定不再更新你的app,否則在你放棄支持iOS6之前有很多機(jī)會(huì)都可以更新到新的API。
但是如果假定我們?cè)谧顗牡那闆r下(例如:我們不更新我們的app了,而蘋果突然宣布了一個(gè)零容忍的不再向下兼容的政策),怎樣讓我們的代碼保持永不過(guò)時(shí)并且仍然能夠支持舊的系統(tǒng)版本呢?
這其實(shí)很簡(jiǎn)單,我們只需要做一些運(yùn)行時(shí)的方法檢測(cè)。使用NSObject的respondsToSelector:方法,我們可以檢測(cè),如果新的API存在,我們就調(diào)用它。否則,我們退回到已廢棄的API。很簡(jiǎn)單:
?NSData*someData=...
NSString*base64String=nil;
// Check if new API is available
if([someData ?respondsToSelector:@selector(base64EncodedDataWithOptions:)])
{// It exists, so let's call it
base64String=[someData ?base64EncodedDataWithOptions:0];
}
else{// Use the old API
base64String=[someDatabase64Encoding];
}
此代碼在iOS4及以上版本中有效,并且如果蘋果在未來(lái)的iOS版本中移除base64Encoding方法后,同樣可以正常工作。
為其他開發(fā)者編碼的時(shí)候
如果你是在寫一個(gè)app,這一切都很好,但是如果你是在編寫一個(gè)給其他人使用的代碼庫(kù)呢?如果project的target是iOS4或iOS6的時(shí)候,上面的代碼會(huì)工作的很好。但是如果deployment target是iOS 7+的時(shí)候,你就會(huì)收到編譯器警告,說(shuō)你使用了已廢棄的base64Encoding方法。
該代碼實(shí)際上永遠(yuǎn)都可以正常工作,因?yàn)槟莻€(gè)方法在運(yùn)行時(shí)永遠(yuǎn)都不會(huì)被調(diào)用(因?yàn)閞espondsToSelector:那個(gè)檢查在iOS7上總是會(huì)返回YES)。但是可惜的是,編譯器還不是足夠的聰明能發(fā)現(xiàn)這點(diǎn)。而且,比如像我,你不會(huì)想用那些會(huì)產(chǎn)生編譯器警告的第三方庫(kù),你肯定也不想自己的庫(kù)中產(chǎn)生任何警告。?
那么,我們?nèi)绾胃膶懳覀兊拇a,以便它可以用于任何deployment target,而不會(huì)產(chǎn)生警告?幸好,有一個(gè)編譯器宏指令可以基于不同的deployment target做不同的代碼分支。取決于app是為哪個(gè)最小的iOS版本編譯的,我們可以用__IPHONE_OS_VERSION_MIN_REQUIRED這個(gè)宏來(lái)生成不同的代碼。
下面的代碼可以工作在任何的iOS版本上(不管是過(guò)去的還是將來(lái)的),而且不會(huì)產(chǎn)生任何警告:
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
// Check if new API is not available
if(![someData ?respondsToSelector:@selector(base64EncodedDataWithOptions:)])
{// Use the old API
base64String=[someData ?base64Encoding];
}else
#endif
{// Use the new API
base64String=[someData ?base64EncodedDataWithOptions:0];
}
看清楚我們?cè)谶@里做了什么嗎?我們變換了respondsToSelector:的用法:我們用它來(lái)測(cè)試是否新的API不可用,然后將整段代碼放到一個(gè)條件代碼塊中,這樣它就只會(huì)在deployment target比iOS7低的情況下才會(huì)被編譯。如果app是為iOS6編譯的,它就會(huì)先檢查新的API是否存在,如果不存在就調(diào)用舊的API。如果app是為iOS7編譯的,那一整塊邏輯代碼都會(huì)被跳過(guò),直接調(diào)用新的API。
補(bǔ)充閱讀
蘋果有一個(gè)關(guān)于廢棄API用法的實(shí)例和相關(guān)的編譯器警告的簡(jiǎn)單文檔,如果感興趣可以看看。