MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C中的@available语法与API版本控制

2021-10-315.2k 阅读

一、@available 语法基础

在Objective-C中,@available 是一种非常重要的语法,用于指定API在哪些操作系统版本及以上可用。它的基本语法形式如下:

@available(<platforms> <version>, ...)

其中,<platforms> 代表平台,比如 iOSmacOSwatchOStvOS 等,<version> 则是具体的版本号。例如:

@available(iOS 10.0, *)
@interface MyViewController : UIViewController

@end

在上述代码中,@available(iOS 10.0, *) 表示 MyViewController 类从iOS 10.0版本开始可用,* 在这里表示在其他平台(如果有相关代码的话)上任何版本都可用。

二、@available 在方法声明中的应用

  1. 简单方法声明示例 假设我们有一个自定义的工具类 MyUtils,其中有一个方法 newFeatureMethod 是从iOS 11.0开始引入的新功能。代码如下:
@interface MyUtils : NSObject

@available(iOS 11.0, *)
+ (void)newFeatureMethod;

@end

@implementation MyUtils

+ (void)newFeatureMethod {
    NSLog(@"This is a new feature method available from iOS 11.0");
}

@end

在使用这个方法时,我们需要确保运行环境是iOS 11.0及以上版本。例如:

if (@available(iOS 11.0, *)) {
    [MyUtils newFeatureMethod];
} else {
    // 处理低版本的逻辑
    NSLog(@"This device is running an older version, new feature is not available");
}
  1. 带参数方法的应用 再看一个稍微复杂点的带参数方法的例子。假设我们的 MyUtils 类还有一个方法 processData:withOptions:,其中 withOptions: 参数是从iOS 12.0开始引入的新特性。
@interface MyUtils : NSObject

@available(iOS 11.0, *)
+ (void)processData:(NSData *)data;

@available(iOS 12.0, *)
+ (void)processData:(NSData *)data withOptions:(NSUInteger)options;

@end

@implementation MyUtils

+ (void)processData:(NSData *)data {
    NSLog(@"Processing data without options on older iOS versions");
}

@available(iOS 12.0, *)
+ (void)processData:(NSData *)data withOptions:(NSUInteger)options {
    NSLog(@"Processing data with options on iOS 12.0 and later, options: %lu", (unsigned long)options);
}

@end

在调用这个方法时,同样要根据系统版本来选择合适的调用方式:

NSData *sampleData = [@"Sample data" dataUsingEncoding:NSUTF8StringEncoding];
if (@available(iOS 12.0, *)) {
    [MyUtils processData:sampleData withOptions:1];
} else if (@available(iOS 11.0, *)) {
    [MyUtils processData:sampleData];
} else {
    NSLog(@"Device is running too old iOS version, cannot process data");
}

三、@available 在属性声明中的使用

  1. 属性可用性声明 在一个自定义视图类 MyCustomView 中,我们可能有一个属性 newStyleProperty 是从iOS 13.0开始引入的新样式相关属性。
@interface MyCustomView : UIView

@property (nonatomic, strong) NSString *oldStyleProperty;

@available(iOS 13.0, *)
@property (nonatomic, strong) NSString *newStyleProperty;

@end

@implementation MyCustomView

@end

在使用这个属性时,我们也需要考虑版本兼容性:

MyCustomView *customView = [[MyCustomView alloc] init];
customView.oldStyleProperty = @"Old style value";
if (@available(iOS 13.0, *)) {
    customView.newStyleProperty = @"New style value";
}
  1. 属性的读写权限与可用性结合 有时候,属性的读写权限也可能在不同版本有变化。比如在iOS 14.0之前,某个属性是只读的,从iOS 14.0开始变为可读写。
@interface AnotherCustomView : UIView

@property (nonatomic, readonly) NSString *versionDependentProperty;

@available(iOS 14.0, *)
@property (nonatomic, readwrite) NSString *versionDependentProperty;

@end

@implementation AnotherCustomView

@end

在代码中使用时:

AnotherCustomView *anotherView = [[AnotherCustomView alloc] init];
NSString *readValue = anotherView.versionDependentProperty;
if (@available(iOS 14.0, *)) {
    anotherView.versionDependentProperty = @"New value";
}

四、@available 与条件编译的区别

  1. 条件编译(#if)基础 条件编译是通过 #if#ifdef#ifndef 等预处理器指令来实现的。例如:
#define USE_NEW_FEATURE 1

#if USE_NEW_FEATURE
// 这里写新功能代码
NSLog(@"Using new feature code");
#else
// 这里写旧功能代码
NSLog(@"Using old feature code");
#endif
  1. @available 与条件编译的区别
    • 编译时与运行时:条件编译是在编译阶段根据预处理器指令决定哪些代码参与编译。而 @available 是在运行时根据设备的操作系统版本来决定是否执行特定代码。这意味着,使用条件编译时,不同版本的代码会被完全分开编译,最终生成的二进制文件可能包含不同版本的代码逻辑。而 @available 则是在运行时动态判断,二进制文件中包含所有代码,但在低版本系统上不会执行高版本相关代码。
    • 灵活性与维护性@available 语法更加灵活,因为它不需要重新编译应用就可以在不同版本系统上正确运行。而条件编译如果要支持新的系统版本,可能需要修改预处理器指令并重新编译。从维护性角度看,@available 使得代码结构更清晰,因为它将版本相关的逻辑集中在 @available 声明处,而条件编译可能导致代码中到处都是预处理器指令,使代码可读性变差。
    • 应用场景:条件编译更适合在不同平台或版本之间有较大架构差异的代码,比如某些功能在低版本系统上完全不支持,需要完全替换实现。而 @available 则适合在功能逐步演进,高版本在低版本基础上增加新特性的场景,这样可以保证应用在不同版本系统上的兼容性,同时代码改动相对较小。

五、@available 语法的高级特性

  1. 指定废弃版本 除了指定API的可用起始版本,@available 还可以指定废弃版本。例如,某个方法在iOS 15.0开始被标记为废弃,建议使用新的替代方法。
@interface DeprecatedClass : NSObject

@available(iOS, introduced=10.0, deprecated=15.0)
+ (void)deprecatedMethod;

@available(iOS 15.0, *)
+ (void)newReplacementMethod;

@end

@implementation DeprecatedClass

+ (void)deprecatedMethod {
    NSLog(@"This method is deprecated since iOS 15.0");
}

+ (void)newReplacementMethod {
    NSLog(@"This is the new replacement method available from iOS 15.0");
}

@end

当使用 deprecatedMethod 时,Xcode会发出警告,提示该方法已被废弃。在代码中使用时:

if (@available(iOS 15.0, *)) {
    [DeprecatedClass newReplacementMethod];
} else {
    [DeprecatedClass deprecatedMethod];
}
  1. 多平台及不同版本组合 @available 可以同时指定多个平台及其对应的版本。比如一个通用的工具类,在iOS 12.0及以上、macOS 10.15及以上可用。
@interface UniversalUtils : NSObject

@available(iOS 12.0, macOS 10.15, *)
+ (void)universalMethod;

@end

@implementation UniversalUtils

+ (void)universalMethod {
    NSLog(@"This method is available on iOS 12.0+ and macOS 10.15+");
}

@end

在不同平台的代码中使用时:

// iOS代码
if (@available(iOS 12.0, *)) {
    [UniversalUtils universalMethod];
}

// macOS代码
if (@available(macOS 10.15, *)) {
    [UniversalUtils universalMethod];
}

六、@available 在实际项目中的最佳实践

  1. 版本兼容性测试 在项目开发过程中,要充分利用Xcode的模拟器和真机测试不同版本系统下的应用。对于使用了 @available 语法的代码,确保在低版本系统上不会因为调用高版本API而崩溃,在高版本系统上新功能能正常使用。例如,可以在Xcode的Scheme设置中,选择不同的iOS或macOS版本进行运行和测试。
  2. 代码结构优化 为了提高代码的可读性和可维护性,尽量将与版本相关的代码逻辑集中在一起。比如,可以将所有高版本相关的功能封装在一个独立的类或方法组中,通过 @available 进行统一控制。例如:
@interface HighVersionFeatures : NSObject

@available(iOS 13.0, *)
+ (void)highVersionMethod1;

@available(iOS 14.0, *)
+ (void)highVersionMethod2;

@end

@implementation HighVersionFeatures

@available(iOS 13.0, *)
+ (void)highVersionMethod1 {
    NSLog(@"This is a high version method available from iOS 13.0");
}

@available(iOS 14.0, *)
+ (void)highVersionMethod2 {
    NSLog(@"This is a high version method available from iOS 14.0");
}

@end

在主代码中调用时:

if (@available(iOS 14.0, *)) {
    [HighVersionFeatures highVersionMethod2];
} else if (@available(iOS 13.0, *)) {
    [HighVersionFeatures highVersionMethod1];
}
  1. 文档化 对于使用 @available 声明的API,一定要在代码注释中详细说明其可用性及废弃情况(如果有)。这样可以帮助其他开发者在阅读和维护代码时,快速了解版本相关信息。例如:
/**
 *  This method is used to perform a specific task.
 *  @available(iOS 12.0, *)
 *  @param data The data to be processed.
 */
+ (void)processData:(NSData *)data;
  1. 应对版本升级 当苹果发布新的操作系统版本,并且我们的应用需要支持新功能时,要逐步更新代码。首先,在项目中使用 @available 语法引入新功能API,并在低版本系统上保持原有功能正常运行。同时,要及时关注苹果官方文档中关于API废弃和替代的信息,及时更新代码以使用新的推荐API,避免在未来版本中出现兼容性问题。

七、@available 语法可能遇到的问题及解决方法

  1. 误判问题 有时候,由于 @available 语法使用不当,可能会导致在低版本系统上误判为可以调用高版本API。例如,在条件判断中逻辑错误:
// 错误示例
if (!@available(iOS 11.0, *)) {
    // 这里不应该调用高版本API,但误判导致可能调用
    [MyUtils newFeatureMethod];
}

解决方法是仔细检查 @available 的条件判断逻辑,确保条件正确。正确的写法应该是:

if (@available(iOS 11.0, *)) {
    [MyUtils newFeatureMethod];
}
  1. 兼容性与性能平衡 在使用 @available 引入高版本API时,可能会在低版本系统上因为额外的条件判断和代码分支导致性能下降。例如,如果在一个频繁调用的方法中进行复杂的版本判断:
- (void)frequentMethod {
    if (@available(iOS 13.0, *)) {
        // 高版本代码
    } else {
        // 低版本代码
    }
}

解决方法是尽量将版本相关的判断放在初始化阶段或者不频繁调用的地方。如果无法避免在频繁调用的方法中进行判断,可以考虑使用缓存或者提前计算好版本相关的标志,减少每次调用时的判断开销。

  1. Xcode 版本兼容性 不同版本的Xcode对 @available 语法的支持可能存在差异。较旧版本的Xcode可能无法正确识别某些新的平台或版本声明。例如,在Xcode 10中可能无法正确处理iOS 13.0相关的 @available 声明。

解决方法是及时更新Xcode到最新版本,以确保对最新的 @available 语法和系统版本支持的正确性。同时,在项目开发中要注意设置项目的最低支持版本,确保与所使用的Xcode版本和 @available 语法的兼容性。

八、@available 与其他编程语言类似特性的对比

  1. 与Swift的对比 在Swift中,也有类似的版本控制语法,即 #available。它的基本用法和Objective-C的 @available 类似,但语法略有不同。例如,在Swift中声明一个从iOS 10.0开始可用的函数:
@available(iOS 10.0, *)
func newFeatureFunction() {
    print("This is a new feature function available from iOS 10.0")
}

Swift的 #available 语法在类型推断等方面更加简洁,与Swift的强类型系统结合得更加紧密。而且,Swift在处理版本相关的代码时,由于其更现代的语法和特性,代码往往更加清晰和易读。例如,在条件判断中,Swift可以使用更简洁的语法:

if #available(iOS 13.0, *) {
    // 使用iOS 13.0+的API
} else {
    // 低版本处理
}
  1. 与Java的对比 Java中并没有直接类似 @available 的语法来控制API在不同操作系统版本的可用性。在Java开发中,通常通过检查系统属性或者使用特定的库来检测运行环境。例如,要检测当前运行的Android版本:
import android.os.Build;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    // 使用Android 6.0(API 23)及以上的API
} else {
    // 低版本处理
}

这种方式相对来说比较繁琐,而且需要针对不同的平台(如Android、Java SE等)采用不同的检测方法。与Objective-C的 @available 相比,Java缺乏一种统一、简洁的语法来声明API的版本可用性,使得代码在处理版本兼容性时可能更加复杂。

  1. 与C++的对比 C++ 本身也没有直接类似于 @available 的语法来处理平台和版本相关的API控制。在C++开发跨平台应用时,通常使用预处理器指令(如 #ifdef)结合不同平台的宏定义来处理。例如,要在Windows和Linux上使用不同的文件操作函数:
#ifdef _WIN32
#include <windows.h>
// Windows相关文件操作函数
#else
#include <unistd.h>
// Linux相关文件操作函数
#endif

这种方式与Objective-C的 @available 相比,更侧重于平台差异,而不是版本差异。而且预处理器指令的使用可能导致代码可读性变差,尤其是在处理复杂的版本和平台组合时。而 @available 语法通过运行时判断,提供了一种更灵活、更专注于版本控制的方式。

通过对不同编程语言类似特性的对比,可以看出Objective-C的 @available 语法在iOS和macOS开发中为开发者提供了一种高效、简洁的API版本控制手段,有助于提高应用在不同系统版本上的兼容性和稳定性。