如你所知,已廢棄(Deprecated)的API指的是那些已通過時的而且在未來某個時間最終會被移除掉的方法或類。一般,蘋果在引入一個更優秀的API後就會把原來的API給廢棄掉。由於,新引入的API一般意味着能夠更好的發揮新硬件或操做系統的性能,或者可使用一些在構建原有API時根本尚未的語言特性(e.g. blocks)。api
每當蘋果添加新方法的時候,他們都會在方法聲明的後面用一個很特殊的宏來標明哪些iOS版本支持它們。例如,在UIViewController中,蘋果引入了一個使用block來處理回調的方法用來展現一個模態controller,它的聲明是這樣的:app
1
|
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^)(void))completion NS_AVAILABLE_IOS(5_0); |
注意到NS_AVAILABLE_IOS(5_0)了嗎?這就告訴咱們這個方法能夠在iOS5.0及之後的版本中使用。若是咱們在比指定版本更老的版本中調用這個方法,就會引發崩潰。性能
那被這個方法替換了的那個舊方法又怎麼樣了呢?一樣,它的聲明後面也帶了一個相似的語法,表示它已經被廢棄了:測試
1
|
- (void)presentModalViewController:(UIViewController *)modalViewController animated:(BOOL)animated NS_DEPRECATED_IOS(2_0, 6_0); |
NS_DEPRECATED_IOS(2_0, 6_0)
這個宏中有兩個版本號。前面一個代表了這個方法被引入時的iOS版本,後面一個代表它被廢棄時的iOS版本。被廢棄並非指這個方法就不存在了,只是意味着咱們應當開始考慮將相關代碼遷移到新的API上去了。編碼
還有相似形式的一些宏用在iOS和OS X共用的類上。好比NSArray中的這個方法:spa
1
|
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx NS_AVAILABLE(10_8, 6_0); |
這裏的NS_AVAILABLE宏告訴咱們這方法分別隨Mac OS 10.8和iOS 6.0被引入。和NS_DEPRECATED_IOS相似,也有個宏叫NS_DEPRECATED,但它的參數要稍微複雜些:操作系統
1
|
- (void)removeObjectsFromIndices:(NSUInteger *)indices numIndices:(NSUInteger)cnt NS_DEPRECATED(10_0, 10_6, 2_0, 4_0); |
這裏表示這個方法隨Mac OS 10.0和iOS 2.0被引入,在Mac OS 10.6和iOS 4.0後被廢棄。code
上週咱們討論了在iOS7和Mac OS 10.9 SDK中被新引入的Base64 API。有趣的是,有一組有相同功能的Base64方法,在被引入的同時也被廢棄掉了。爲何蘋果在引入一個API的同時又把它廢棄掉了?那不是毫無心義的嗎?好吧,其實也不是——它在下面這種狀況下就很是有意義:ip
實際上,這些如今已經廢棄的Base64方法從iOS4和Mac 0S 10.6開始就一直存在,只是它們是私有的。直到如今蘋果才把它們公開,大概是蘋果一直對它們的實現不滿意,一直都想把它們改寫。開發
果真,在iOS7中,蘋果選定了一個他們感到滿意的Base64 API,而且將它添加到了NSData的一個公有類別中。但如今,他們知道老方法已經被取代,不會被改寫了,所以他們把它公開出來。當開發者的app仍然須要支持iOS6及之前的版本時,就有了一個系統內置的Base64 api能夠用。
這就是爲何,若是你查看這些新API的方法聲明,能夠看到NS_DEPRECATED宏部分中的起始版本是4_0,雖然實際上直到iOS7以前,它歷來都沒有被做爲公有API被引入過:
1
|
- (NSString *)base64Encoding NS_DEPRECATED(10_6, 10_9, 4_0, 7_0); |
這告訴你,基於iOS7 SDK開發的app若是調用了這個方法,它一樣能夠運行在iOS4+或Mac OS 10.6+的系統上而不會崩潰。頗有用的吧?
那麼,若是咱們有一個app須要同時支持iOS6和iOS7,想用內置的Base64方法,咱們該怎麼作呢?事實上,這至關簡單,你只須要調用這些廢棄的API就能夠了。
那樣編譯器不是會產生警告嗎?不會——只有你的deployment target版本號設置成大於或等於方法被棄用的版本號的時候纔會收到編譯器警告。只要你仍然在支持那些尚未廢棄這個方法的iOS版本,都不會收到警告。
那麼,若是蘋果決定在iOS8中移除已棄用的Base64方法,你的應用程序會發生狀況?簡單來講,它確定會崩潰,可是不要讓這把你嚇跑了:蘋果不可能只在幾個iOS版本後就將已廢棄的API給移除(絕大多數已廢棄的API在任何的iOS版本中都尚未被移除),除非你決定再也不更新你的app,不然在你放棄支持iOS6以前有不少機會均可以更新到新的API。
可是若是假定咱們在最壞的狀況下(例如:咱們不更新咱們的app了,而蘋果忽然宣佈了一個零容忍的再也不向下兼容的政策),怎樣讓咱們的代碼保持永不過期而且仍然可以支持舊的系統版本呢?
這其實很簡單,咱們只須要作一些運行時的方法檢測。使用NSObject的respondsToSelector:
方法,咱們能夠檢測,若是新的API存在,咱們就調用它。不然,咱們退回到已廢棄的API。很簡單:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 |
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 = [someData base64Encoding]; } |
此代碼在iOS4及以上版本中有效,而且若是蘋果在將來的iOS版本中移除base64Encoding方法後,一樣能夠正常工做。
若是你是在寫一個app,這一切都很好,可是若是你是在編寫一個給其餘人使用的代碼庫呢?若是project的target是iOS4或iOS6的時候,上面的代碼會工做的很好。可是若是deployment target是iOS 7+的時候,你就會收到編譯器警告,說你使用了已廢棄的base64Encoding方法。
該代碼實際上永遠均可以正常工做,由於那個方法在運行時永遠都不會被調用(由於respondsToSelector:那個檢查在iOS7上老是會返回YES)。可是惋惜的是,編譯器還不是足夠的聰明能發現這點。並且,好比像我,你不會想用那些會產生編譯器警告的第三方庫,你確定也不想本身的庫中產生任何警告。
那麼,咱們如何改寫咱們的代碼,以便它能夠用於任何deployment target,而不會產生警告?幸虧,有一個編譯器宏指令能夠基於不一樣的deployment target作不一樣的代碼分支。取決於app是爲哪一個最小的iOS版本編譯的,咱們能夠用__IPHONE_OS_VERSION_MIN_REQUIRED
這個宏來生成不一樣的代碼。
下面的代碼能夠工做在任何的iOS版本上(不論是過去的仍是未來的),並且不會產生任何警告:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#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]; } |
看清楚咱們在這裏作了什麼嗎?咱們變換了respondsToSelector:的用法:咱們用它來測試是否新的API不可用,而後將整段代碼放到一個條件代碼塊中,這樣它就只會在deployment target比iOS7低的狀況下才會被編譯。若是app是爲iOS6編譯的,它就會先檢查新的API是否存在,若是不存在就調用舊的API。若是app是爲iOS7編譯的,那一整塊邏輯代碼都會被跳過,直接調用新的API。