Objective-C —— APIs declaration 自定义
一、枚举
1、NS_ENUM
NS_ENUM 只是简单地把一些常量组成一组,然后构成枚举类型.但值得注意的是,在这 5 种枚举中,只有 NS_ENUM 和 NS_CLOSED_ENUM 在引入到 swift 后变成 enum.
因为 NS_ENUM 在引入到 swift 后变成 enum: Int, 所以 NS_ENUM 在 swift 侧是允许用 swift 创建新值的.但在 oc 是不允许创建新值的.
NS_ENUM 的 raw value 只支持整型家族(NSInteger, NSUInteger……)
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {UITableViewCellStyleDefault,UITableViewCellStyleValue1,UITableViewCellStyleValue2,UITableViewCellStyleSubtitle
};
引入 swift 时就会变成这样:
另外值得注意的是, 因为 NS_ENUM 是可扩展的, 所以所有的 case 有可能不只声明时的 3 种,因此 NS_ENUM 在引入 swift 时, swift5 和 swift6 会分别通过警告和报错强制要你在 switch-case 里加 default, 来应对除声明 NS_ENUM 以外的 case.
2、NS_CLOSED_ENUM
在了解了 NS_ENUM 后, NS_CLOSED_ENUM 就很通俗易懂了. 就是你只能在 NS_CLOSED_ENUM 声明时往大括号里加值,不能在大括号外创建新值. 所以在引入 swift 后, swift 的 switch-case 并不用强制写 @unknown default.
当然, 我们也不能在声明 NS_CLOSED_ENUM 的大括号外创建新值.
同样, NS_CLOSED_ENUM 的 raw value 只支持整型家族的.
typedef NS_CLOSED_ENUM(NSInteger, MyClosedEnum) {MyClosedEnumA = 0,MyClosedEnumB = 1,
};
引入 swift 后会变成这样:
例子:
3、NS_OPTIONS
当你想让所有 case 都互斥,那么 NS_OPTIONS 就是很好的选择. NS_OPTIONS 会作为一个 Optional Set (也就是 bitmap) 引入 swift.这样 swift 就可以给一个整型变量赋多个枚举值, 然后做逻辑判断的时候可以用 |, & 来替代 ||, && . 因此一般 NS_OPTIONS 的值用在 if 组合判断比较多,一般不用于 switch-case.
当然, NS_OPTIONS 的 raw value 依旧只能是整型家族的.
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {UIViewAutoresizingNone = 0,UIViewAutoresizingFlexibleLeftMargin = 1 << 0,UIViewAutoresizingFlexibleWidth = 1 << 1,UIViewAutoresizingFlexibleRightMargin = 1 << 2,UIViewAutoresizingFlexibleTopMargin = 1 << 3,UIViewAutoresizingFlexibleHeight = 1 << 4,UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
作为以下形式引入 swift:
4、NS_TYPED_ENUM
如果你想用 raw value 为非整型的枚举, 而且你枚举的 case 在逻辑上 (语义上) 是不可扩展的, 那就推荐用 NS_TYPED_ENUM.
⬆️ 把 NS_TYPED_ENUM 宏展开后, 可以看到 NS_TYPED_ENUM 是作为 swift 的 enum 引入 swift 的._
// Store the three traffic light color options as 0, 1, and 2.
typedef long TrafficLightColor NS_TYPED_ENUM;TrafficLightColor const TrafficLightColorRed;
TrafficLightColor const TrafficLightColorYellow;
TrafficLightColor const TrafficLightColorGreen;
作为以下形式引入 swift:
NS_TYPED_ENUM 是作为 swift 的 enum 引入的, 所以其实在代码上确实可以通过对 TrafficLightColor 加 extension 的方式来实现新增 case. 所以 NS_TYPED_ENUM 仅仅只是语义上不支持新增 case 而已.
5、NS_TYPED_EXTENSIBLE_ENUM
NS_TYPED_EXTENSIBLE_ENUM 同样支持 raw value 自定义, 而且同样可以在 swift 侧通过 extension 给 oc 的 enum 加新的 case. 但与 NS_TYPED_ENUM 不同的是, NS_TYPED_EXTENSIBLE_ENUM 在逻辑上 (语义上) 支持新增 case 的时候才用.
⬆️ 把 NS_TYPED_EXTENSIBLE_ENUM 展开后, NS_TYPED_EXTENSIBLE_ENUM 是作为 swift 的 struct 引入 swift 的.
typedef long FavoriteColor NS_TYPED_EXTENSIBLE_ENUM;
FavoriteColor const FavoriteColorBlue;
⬇️ OC 侧的 FavoriteColor 将作为以下形式引入到 swift 中:
同时你也同样可以通过 extension 的方式给 FavoriteColor 加新的 case, 这点和 NS_TYPED_ENUM 是完全一样的.
extension FavoriteColor {static var green: FavoriteColor {return FavoriteColor(1) // blue is 0, green is 1, and new favorite colors could follow}
}
二、 oc 侧 API 桥接声明自定义
1、重命名
1.1. NS_REFINED_FOR_SWIFT
NS_REFINED_FOR_SWIFT 是把 Objective-C API 在导入到 swift 时,重命名成在第一个标签前加 _ _ 的函数——表示不建议 swift 侧直接调用. 然后你就可以在 swift 侧用 swift extension 里写一个更 Swifty 的替代方案.
举个例子:
ObjC 里你写了一个很底层的初始化:- (instancetype)initWithCString:(const char *)cStringfreeWhenDone:(BOOL)freeWhenDoneNS_REFINED_FOR_SWIFT;
Swift 里会变成:
__initWithCString(_:freeWhenDone:) // 有双下划线,不建议直接在类外调
这时候你就可以在 Swift extension 里写一个更 Swift 化的替代方案:
extension MyString {convenience init(_ string: String) {self.init(cString: string.cString(using: .utf8), freeWhenDone: false)} }
1.2. NS_SWIFT_NAME
NS_SWIFT_NAME 就是把类, 成员属性, 成员函数等重命名. 相当于告诉 swift: 在 Swift 里,这个函数 / 类型 / 方法应该显示成什么名字。NS_SWIFT_NAME 只影响 swift 侧的名字,不影响 oc 侧的名字.
NS_SWIFT_NAME(Sandwich.Preferences)
@interface SandwichPreferences : NSObject@property BOOL includesCrust NS_SWIFT_NAME(isCrusty);@end@interface Sandwich : NSObject
@end
typedef NS_ENUM(NSInteger, SandwichBreadType) {brioche, pumpernickel, pretzel, focaccia
} NS_SWIFT_NAME(SandwichPreferences.BreadType);
var preferences = Sandwich.Preferences()
preferences.isCrusty = true
2、有效性自定义
2.1. API_AVAILABLE & @available
API_AVAILABLE 就是 oc 侧在声明函数时规定该函数应该对哪些版本的系统有效. 记住,
Objective-C 侧声明成员函数的有效性用 API_AVAILABLE:
@interface MyViewController : UIViewController
- (void) newMethod API_AVAILABLE(ios(11), macosx(10.13));
@end
相当于 Swift 侧用 @available 修饰成员函数:
@available(iOS 11, macOS 10.13, *)
func newMethod() {// Use iOS 11 APIs.
}
Objective-C 侧用 @available 检查当前操作系统的版本:
if (@available(iOS 11, *)) {// Use iOS 11 APIs.
} else {// Alternative code for earlier versions of iOS.
}
等同于Swift 侧用 #available 检查当前操作系统的版本:
if #available(iOS 11, *) {// Use iOS 11 APIs.
} else {// Alternative code for earlier versions of iOS.
}
2.2. NS_SWIFT_UNAVAILABLE
NS_SWIFT_UNAVAILABLE 就是在 Objective-C 侧函数声明的时候告诉 swift 这个函数对 swift 不开放. NS_SWIFT_UNAVAILABLE() 括号里填一段字符串, 如果 Swift 侧调了这个函数, 会编译出错,然后报错的内容就会打印出括号里的字符串.
2.3. NS_UNAVAILABLE
NS_UNAVAILABLE 就是声明这个 Objective-C 侧的函数对除 Objective-C 以外的其他所有语言都无效. NS_UNAVAILABLE() 括号里填一段字符串, 如果其他语言侧调了这个函数, 会编译出错,然后报错的内容就会打印出括号里的字符串.
三、Nullability
下面的可空和非空都是针对引入 Swift 而言的. 如果在 oc 侧对一个变量声明非空, 那么在引入到 Swift 后会变成普通类型(非 Optional), 而如果在 oc 侧对一个变量声明可空,那么在引入到 Swift 后会变成显式可选类型(Optional).
而下面的可空和非空对 oc 侧的代码都没有影响.因为 oc 在指针方面就和 C 语言一样, 无论怎么修饰,都是可以赋 nil 的; 同样如果在 oc 用空指针调函数或访问属性,那必然会报错的.
1、nonnull + nullable
用两个东西修饰的函数形参和一级指针都可以. 但 nonnull + nullable 对于标记多级指针和 id * 指针的非空性非常不方便. 所以在开发的角度来看, 尽量别用 nonnull & nullable; 而应该用 _Nullable & _Nonnull.
@interface MyClass : NSObject@property (nonatomic, strong) NSString *name; // nonnull,默认不可空
@property (nonatomic, strong, nullable) NSString *nickname; // nullable,可空@endtypedef void (^CompletionHandler)(nullable NSString *result, nullable NSError *error);@interface MyClass (Blocks)// 参数不可空,返回值可空
- (nullable NSString *)fetchDataWithParam:(nonnull NSString *)paramcompletion:(nonnull CompletionHandler)completion;// 参数可空,返回值不可空
- (nonnull NSString *)processData:(nullable NSString *)datacompletion:(nullable CompletionHandler)completion;@end
2、_Nonnull + _Nullable
_Nonnull & _Nullable 除了和上面的 nonnull 和 nullable 功能一样外, 在多级指针方面也比上面的组合要方便不少:
// out 参数:必须传一个有指向的指针,但指着的内存可以没有值
- (void)fillString:(NSString * _Nullable * _Nonnull)outString;
需要注意的是: 如果你要指定 id* 的非空性, 只能用 _Nullable & _Nonnull, 不能用 nonnull 和 nullable.
3、某片区域默不可空
使用 NS_ASSUME_NONNULL_BEGIN + NS_ASSUME_NONNULL_END 包围不可空区域,其他地方默认可空
NS_ASSUME_NONNULL_BEGIN@interface MyList : NSObject
- (nullable MyListItem *)itemWithName:(NSString *)name;
- (nullable NSString *)nameForItem:(MyListItem *)item;
@property (copy) NSArray<MyListItem *> *allItems;
@endNS_ASSUME_NONNULL_END
需要注意的是: typedef
类型即使在 NS_ASSUME_NONNULL_BEGIN + NS_ASSUME_NONNULL_END 区域内也不会被假定为非空,因为它们本质上不是可空类型。