原文: Everything You Need to Know about iOS and OS X Deprecated APIs
如你所知,已废弃(Deprecated)的API指的是那些已通过时的而且在未来某个时间最终会被移除掉的方法或类。一般,苹果在引入一个更优秀的API后就会把原来的API给废弃掉。由于,新引入的API一般意味着能够更好的发挥新硬件或操做系统的性能,或者可使用一些在构建原有API时根本尚未的语言特性(e.g. blocks)。
每当苹果添加新方法的时候,他们都会在方法声明的后面用一个很特殊的宏来标明哪些iOS版本支持它们。例如,在UIViewController中,苹果引入了一个使用block来处理回调的方法用来展现一个模态controller,它的声明是这样的:
html
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^)(void))completion NS_AVAILABLE_IOS(5_0);
注意到NS_AVAILABLE_IOS(5_0)了吗?这就告诉咱们这个方法能够在iOS5.0及之后的版本中使用。若是咱们在比指定版本更老的版本中调用这个方法,就会引发崩溃。
那被这个方法替换了的那个旧方法又怎么样了呢?一样,它的声明后面也带了一个相似的语法,表示它已经被废弃了:
ios
- (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中的这个方法:
- (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,但它的参数要稍微复杂些:
api
- (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后被废弃。app
上周咱们讨论了在iOS7和Mac OS 10.9 SDK中被新引入的Base64 API。有趣的是,有一组有相同功能的Base64方法,在被引入的同时也被废弃掉了。为何苹果在引入一个API的同时又把它废弃掉了?那不是毫无心义的吗?好吧,其实也不是——它在下面这种状况下就很是有意义:
实际上,这些如今已经废弃的Base64方法从iOS4和Mac 0S 10.6开始就一直存在,只是它们是私有的。直到如今苹果才把它们公开,大概是苹果一直对它们的实现不满意,一直都想把它们改写。
果真,在iOS7中,苹果选定了一个他们感到满意的Base64 API,而且将它添加到了NSData的一个公有类别中。但如今,他们知道老方法已经被取代,不会被改写了,所以他们把它公开出来。当开发者的app仍然须要支持iOS6及之前的版本时,就有了一个系统内置的Base64 api能够用。
这就是为何,若是你查看这些新API的方法声明,能够看到NS_DEPRECATED宏部分中的起始版本是4_0,虽然实际上直到iOS7以前,它历来都没有被做为公有API被引入过:
性能
- (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。很简单:
ui
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版本上(不论是过去的仍是未来的),并且不会产生任何警告:
spa
#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。操作系统
苹果有一个关于废弃API用法的实例和相关的编译器警告的简单文档,若是感兴趣能够看看。
原文连接(戳这里):