|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "Objective-C-APIs-declaration-自定义" |
| 4 | +date: 2025-09-19T19:37:42+0800 |
| 5 | +description: "NS_REFINED_FOR_SWIFT 是把 Objective-C API 在导入到 swift 时,重命名成在第一个标签前加 _ _ 的函数——表示不建议 swift 侧直接调用. 然后你就可以在 swift 侧用 swift extension 里写一个更 Swifty 的替代方案.举个例子:__initWithCString(_:freeWhenDone:) // 有双下划线,不建议直接在类外调。" |
| 6 | +keywords: "Objective-C —— APIs declaration 自定义" |
| 7 | +categories: ['未分类'] |
| 8 | +tags: ['Swift', 'Ios', 'C'] |
| 9 | +artid: "151791591" |
| 10 | +arturl: "https://blog.csdn.net/m0_73093743/article/details/151791591" |
| 11 | +image: |
| 12 | + path: https://api.vvhan.com/api/bing?rand=sj&artid=151791591 |
| 13 | + alt: "Objective-C-APIs-declaration-自定义" |
| 14 | +render_with_liquid: false |
| 15 | +featuredImage: https://bing.ee123.net/img/rand?artid=151791591 |
| 16 | +featuredImagePreview: https://bing.ee123.net/img/rand?artid=151791591 |
| 17 | +cover: https://bing.ee123.net/img/rand?artid=151791591 |
| 18 | +image: https://bing.ee123.net/img/rand?artid=151791591 |
| 19 | +img: https://bing.ee123.net/img/rand?artid=151791591 |
| 20 | +--- |
| 21 | + |
| 22 | + |
| 23 | + |
| 24 | +# Objective-C —— APIs declaration 自定义 |
| 25 | + |
| 26 | +## 一、枚举 |
| 27 | + |
| 28 | +#### 1、NS_ENUM |
| 29 | + |
| 30 | +NS_ENUM 只是简单地把一些常量组成一组,然后构成枚举类型.但值得注意的是,在这 5 种枚举中,只有 NS_ENUM 和 NS_CLOSED_ENUM 在引入到 swift 后变成 enum. |
| 31 | + |
| 32 | +因为 NS_ENUM 在引入到 swift 后变成 enum: Int, 所以 NS_ENUM 在 swift 侧是允许用 swift 创建新值的.但在 oc 是不允许创建新值的. |
| 33 | + |
| 34 | +NS_ENUM 的 raw value 只支持整型家族(NSInteger, NSUInteger……) |
| 35 | + |
| 36 | +``` |
| 37 | +
|
| 38 | +typedef NS_ENUM(NSInteger, UITableViewCellStyle) { |
| 39 | + UITableViewCellStyleDefault, |
| 40 | + UITableViewCellStyleValue1, |
| 41 | + UITableViewCellStyleValue2, |
| 42 | + UITableViewCellStyleSubtitle |
| 43 | +}; |
| 44 | +``` |
| 45 | + |
| 46 | +引入 swift 时就会变成这样: |
| 47 | + |
| 48 | + |
| 49 | + |
| 50 | +另外值得注意的是, 因为 NS_ENUM 是可扩展的, 所以所有的 case 有可能不只声明时的 3 种,因此 NS_ENUM 在引入 swift 时, swift5 和 swift6 会分别通过警告和报错强制要你在 switch-case 里加 default, 来应对除声明 NS_ENUM 以外的 case. |
| 51 | + |
| 52 | + |
| 53 | + |
| 54 | + |
| 55 | + |
| 56 | +#### 2、NS_CLOSED_ENUM |
| 57 | + |
| 58 | +在了解了 NS_ENUM 后, NS_CLOSED_ENUM 就很通俗易懂了. 就是你只能在 NS_CLOSED_ENUM 声明时往大括号里加值,不能在大括号外创建新值. 所以在引入 swift 后, swift 的 switch-case 并不用强制写 @unknown default. |
| 59 | + |
| 60 | +当然, 我们也不能在声明 NS_CLOSED_ENUM 的大括号外创建新值. |
| 61 | + |
| 62 | +同样, NS_CLOSED_ENUM 的 raw value 只支持整型家族的. |
| 63 | + |
| 64 | +``` |
| 65 | +
|
| 66 | +typedef NS_CLOSED_ENUM(NSInteger, MyClosedEnum) { |
| 67 | + MyClosedEnumA = 0, |
| 68 | + MyClosedEnumB = 1, |
| 69 | +}; |
| 70 | +``` |
| 71 | + |
| 72 | +引入 swift 后会变成这样: |
| 73 | + |
| 74 | + |
| 75 | + |
| 76 | +例子: |
| 77 | + |
| 78 | + |
| 79 | +#### 3、NS_OPTIONS |
| 80 | + |
| 81 | +当你想让所有 case 都互斥,那么 NS_OPTIONS 就是很好的选择. NS_OPTIONS 会作为一个 Optional Set (也就是 bitmap) 引入 swift.这样 swift 就可以给一个整型变量赋多个枚举值, 然后做逻辑判断的时候可以用 |, & 来替代 ||, && . 因此一般 NS_OPTIONS 的值用在 if 组合判断比较多,一般不用于 switch-case. |
| 82 | + |
| 83 | +当然, NS_OPTIONS 的 raw value 依旧只能是整型家族的. |
| 84 | + |
| 85 | +``` |
| 86 | +
|
| 87 | +typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) { |
| 88 | + UIViewAutoresizingNone = 0, |
| 89 | + UIViewAutoresizingFlexibleLeftMargin = 1 << 0, |
| 90 | + UIViewAutoresizingFlexibleWidth = 1 << 1, |
| 91 | + UIViewAutoresizingFlexibleRightMargin = 1 << 2, |
| 92 | + UIViewAutoresizingFlexibleTopMargin = 1 << 3, |
| 93 | + UIViewAutoresizingFlexibleHeight = 1 << 4, |
| 94 | + UIViewAutoresizingFlexibleBottomMargin = 1 << 5 |
| 95 | +}; |
| 96 | +``` |
| 97 | + |
| 98 | +作为以下形式引入 swift: |
| 99 | + |
| 100 | + |
| 101 | + |
| 102 | +#### 4、NS_TYPED_ENUM |
| 103 | + |
| 104 | +如果你想用 raw value 为非整型的枚举, 而且你枚举的 case 在逻辑上 (语义上) 是不可扩展的, 那就推荐用 NS_TYPED_ENUM. |
| 105 | + |
| 106 | + |
| 107 | + |
| 108 | +> ⬆️ 把 NS_TYPED_ENUM 宏展开后, 可以看到 NS_TYPED_ENUM 是作为 swift 的 enum 引入 swift 的._ |
| 109 | +
|
| 110 | +``` |
| 111 | +
|
| 112 | +// Store the three traffic light color options as 0, 1, and 2. |
| 113 | +typedef long TrafficLightColor NS_TYPED_ENUM; |
| 114 | +
|
| 115 | +
|
| 116 | +TrafficLightColor const TrafficLightColorRed; |
| 117 | +TrafficLightColor const TrafficLightColorYellow; |
| 118 | +TrafficLightColor const TrafficLightColorGreen; |
| 119 | +``` |
| 120 | + |
| 121 | +作为以下形式引入 swift: |
| 122 | + |
| 123 | + |
| 124 | + |
| 125 | +NS_TYPED_ENUM 是作为 swift 的 enum 引入的, 所以其实在代码上确实可以通过对 TrafficLightColor 加 extension 的方式来实现新增 case. 所以 NS_TYPED_ENUM 仅仅只是语义上不支持新增 case 而已. |
| 126 | + |
| 127 | + |
| 128 | + |
| 129 | +#### 5、NS_TYPED_EXTENSIBLE_ENUM |
| 130 | + |
| 131 | +NS_TYPED_EXTENSIBLE_ENUM 同样支持 raw value 自定义, 而且同样可以在 swift 侧通过 extension 给 oc 的 enum 加新的 case. 但与 NS_TYPED_ENUM 不同的是, NS_TYPED_EXTENSIBLE_ENUM 在逻辑上 (语义上) 支持新增 case 的时候才用. |
| 132 | + |
| 133 | + |
| 134 | + |
| 135 | +> ⬆️ 把 NS_TYPED_EXTENSIBLE_ENUM 展开后, NS_TYPED_EXTENSIBLE_ENUM 是作为 swift 的 struct 引入 swift 的. |
| 136 | +
|
| 137 | +``` |
| 138 | +
|
| 139 | +typedef long FavoriteColor NS_TYPED_EXTENSIBLE_ENUM; |
| 140 | +FavoriteColor const FavoriteColorBlue; |
| 141 | +``` |
| 142 | + |
| 143 | +⬇️ OC 侧的 FavoriteColor 将作为以下形式引入到 swift 中: |
| 144 | + |
| 145 | + |
| 146 | + |
| 147 | +同时你也同样可以通过 extension 的方式给 FavoriteColor 加新的 case, 这点和 NS_TYPED_ENUM 是完全一样的. |
| 148 | + |
| 149 | +``` |
| 150 | +
|
| 151 | +extension FavoriteColor { |
| 152 | + static var green: FavoriteColor { |
| 153 | + return FavoriteColor(1) // blue is 0, green is 1, and new favorite colors could follow |
| 154 | + } |
| 155 | +} |
| 156 | +``` |
| 157 | + |
| 158 | +## 二、 oc 侧 API 桥接声明自定义 |
| 159 | + |
| 160 | +### 1、重命名 |
| 161 | + |
| 162 | +#### 1.1. NS_REFINED_FOR_SWIFT |
| 163 | + |
| 164 | +NS_REFINED_FOR_SWIFT 是把 Objective-C API 在导入到 swift 时,重命名成在第一个标签前加 _ _ 的函数——表示不建议 swift 侧直接调用. 然后你就可以在 swift 侧用 swift extension 里写一个更 Swifty 的替代方案. |
| 165 | + |
| 166 | +> 举个例子: |
| 167 | +> ObjC 里你写了一个很底层的初始化: |
| 168 | +> |
| 169 | +> ``` |
| 170 | +> |
| 171 | +> - (instancetype)initWithCString:(const char *)cString |
| 172 | +> freeWhenDone:(BOOL)freeWhenDone |
| 173 | +> NS_REFINED_FOR_SWIFT; |
| 174 | +> ``` |
| 175 | +> |
| 176 | +> Swift 里会变成: |
| 177 | +> |
| 178 | +> ``` |
| 179 | +> |
| 180 | +> __initWithCString(_:freeWhenDone:) // 有双下划线,不建议直接在类外调 |
| 181 | +> ``` |
| 182 | +> |
| 183 | +> 这时候你就可以在 Swift extension 里写一个更 Swift 化的替代方案: |
| 184 | +> |
| 185 | +> ``` |
| 186 | +> |
| 187 | +> extension MyString { |
| 188 | +> convenience init(_ string: String) { |
| 189 | +> self.init(cString: string.cString(using: .utf8), freeWhenDone: false) |
| 190 | +> } |
| 191 | +> } |
| 192 | +> ``` |
| 193 | +
|
| 194 | +#### 1.2. NS_SWIFT_NAME |
| 195 | +
|
| 196 | +NS_SWIFT_NAME 就是把类, 成员属性, 成员函数等重命名. 相当于告诉 swift: 在 Swift 里,这个函数 / 类型 / 方法应该显示成什么名字。NS_SWIFT_NAME 只影响 swift 侧的名字,不影响 oc 侧的名字. |
| 197 | +
|
| 198 | +``` |
| 199 | +
|
| 200 | +NS_SWIFT_NAME(Sandwich.Preferences) |
| 201 | +@interface SandwichPreferences : NSObject |
| 202 | + |
| 203 | + |
| 204 | +@property BOOL includesCrust NS_SWIFT_NAME(isCrusty); |
| 205 | + |
| 206 | + |
| 207 | +@end |
| 208 | + |
| 209 | + |
| 210 | +@interface Sandwich : NSObject |
| 211 | +@end |
| 212 | +``` |
| 213 | +
|
| 214 | +``` |
| 215 | + |
| 216 | +typedef NS_ENUM(NSInteger, SandwichBreadType) { |
| 217 | + brioche, pumpernickel, pretzel, focaccia |
| 218 | +} NS_SWIFT_NAME(SandwichPreferences.BreadType); |
| 219 | +``` |
| 220 | +
|
| 221 | +``` |
| 222 | + |
| 223 | +var preferences = Sandwich.Preferences() |
| 224 | +preferences.isCrusty = true |
| 225 | +``` |
| 226 | +
|
| 227 | +### 2、有效性自定义 |
| 228 | +
|
| 229 | +#### 2.1. API_AVAILABLE & @available |
| 230 | +
|
| 231 | +API_AVAILABLE 就是 oc 侧在声明函数时规定该函数应该对哪些版本的系统有效. 记住, |
| 232 | +
|
| 233 | +Objective-C 侧声明成员函数的有效性用 API_AVAILABLE: |
| 234 | +
|
| 235 | +``` |
| 236 | + |
| 237 | +@interface MyViewController : UIViewController |
| 238 | +- (void) newMethod API_AVAILABLE(ios(11), macosx(10.13)); |
| 239 | +@end |
| 240 | +``` |
| 241 | +
|
| 242 | +相当于 Swift 侧用 @available 修饰成员函数: |
| 243 | +
|
| 244 | +``` |
| 245 | + |
| 246 | +@available(iOS 11, macOS 10.13, *) |
| 247 | +func newMethod() { |
| 248 | + // Use iOS 11 APIs. |
| 249 | +} |
| 250 | +``` |
| 251 | +
|
| 252 | +Objective-C 侧用 @available 检查当前操作系统的版本: |
| 253 | +
|
| 254 | +``` |
| 255 | + |
| 256 | +if (@available(iOS 11, *)) { |
| 257 | + // Use iOS 11 APIs. |
| 258 | +} else { |
| 259 | + // Alternative code for earlier versions of iOS. |
| 260 | +} |
| 261 | +``` |
| 262 | +
|
| 263 | +等同于Swift 侧用 #available 检查当前操作系统的版本: |
| 264 | +
|
| 265 | +``` |
| 266 | + |
| 267 | +if #available(iOS 11, *) { |
| 268 | + // Use iOS 11 APIs. |
| 269 | +} else { |
| 270 | + // Alternative code for earlier versions of iOS. |
| 271 | +} |
| 272 | +``` |
| 273 | +
|
| 274 | +#### 2.2. NS_SWIFT_UNAVAILABLE |
| 275 | +
|
| 276 | +NS_SWIFT_UNAVAILABLE 就是在 Objective-C 侧函数声明的时候告诉 swift 这个函数对 swift 不开放. NS_SWIFT_UNAVAILABLE() 括号里填一段字符串, 如果 Swift 侧调了这个函数, 会编译出错,然后报错的内容就会打印出括号里的字符串. |
| 277 | +
|
| 278 | + |
| 279 | +
|
| 280 | +#### 2.3. NS_UNAVAILABLE |
| 281 | +
|
| 282 | +NS_UNAVAILABLE 就是声明这个 Objective-C 侧的函数对除 Objective-C 以外的其他所有语言都无效. NS_UNAVAILABLE() 括号里填一段字符串, 如果其他语言侧调了这个函数, 会编译出错,然后报错的内容就会打印出括号里的字符串. |
| 283 | +
|
| 284 | +## 三、Nullability |
| 285 | +
|
| 286 | +下面的可空和非空都是针对引入 Swift 而言的. 如果在 oc 侧对一个变量声明非空, 那么在引入到 Swift 后会变成普通类型(非 Optional), 而如果在 oc 侧对一个变量声明可空,那么在引入到 Swift 后会变成显式可选类型(Optional). |
| 287 | +
|
| 288 | +而下面的可空和非空对 oc 侧的代码都没有影响.因为 oc 在指针方面就和 C 语言一样, 无论怎么修饰,都是可以赋 nil 的; 同样如果在 oc 用空指针调函数或访问属性,那必然会报错的. |
| 289 | +
|
| 290 | +### 1、nonnull + nullable |
| 291 | +
|
| 292 | +用两个东西修饰的函数形参和一级指针都可以. 但 nonnull + nullable 对于标记多级指针和 id * 指针的非空性非常不方便. 所以在开发的角度来看, 尽量别用 nonnull & nullable; 而应该用 _Nullable & _Nonnull. |
| 293 | +
|
| 294 | +``` |
| 295 | + |
| 296 | +@interface MyClass : NSObject |
| 297 | + |
| 298 | +@property (nonatomic, strong) NSString *name; // nonnull,默认不可空 |
| 299 | +@property (nonatomic, strong, nullable) NSString *nickname; // nullable,可空 |
| 300 | + |
| 301 | +@end |
| 302 | + |
| 303 | + |
| 304 | +typedef void (^CompletionHandler)(nullable NSString *result, nullable NSError *error); |
| 305 | + |
| 306 | +@interface MyClass (Blocks) |
| 307 | + |
| 308 | +// 参数不可空,返回值可空 |
| 309 | +- (nullable NSString *)fetchDataWithParam:(nonnull NSString *)param |
| 310 | + completion:(nonnull CompletionHandler)completion; |
| 311 | + |
| 312 | +// 参数可空,返回值不可空 |
| 313 | +- (nonnull NSString *)processData:(nullable NSString *)data |
| 314 | + completion:(nullable CompletionHandler)completion; |
| 315 | + |
| 316 | +@end |
| 317 | +``` |
| 318 | +
|
| 319 | +### 2、_Nonnull + _Nullable |
| 320 | +
|
| 321 | +_Nonnull & _Nullable 除了和上面的 nonnull 和 nullable 功能一样外, 在多级指针方面也比上面的组合要方便不少: |
| 322 | +
|
| 323 | +``` |
| 324 | + |
| 325 | +// out 参数:必须传一个有指向的指针,但指着的内存可以没有值 |
| 326 | +- (void)fillString:(NSString * _Nullable * _Nonnull)outString; |
| 327 | +``` |
| 328 | +
|
| 329 | +需要注意的是: 如果你要指定 id* 的非空性, 只能用 _Nullable & _Nonnull, 不能用 nonnull 和 nullable. |
| 330 | +
|
| 331 | +### 3、某片区域默不可空 |
| 332 | +
|
| 333 | +使用 NS_ASSUME_NONNULL_BEGIN + NS_ASSUME_NONNULL_END 包围不可空区域,其他地方默认可空 |
| 334 | +
|
| 335 | +``` |
| 336 | + |
| 337 | +NS_ASSUME_NONNULL_BEGIN |
| 338 | + |
| 339 | + |
| 340 | +@interface MyList : NSObject |
| 341 | +- (nullable MyListItem *)itemWithName:(NSString *)name; |
| 342 | +- (nullable NSString *)nameForItem:(MyListItem *)item; |
| 343 | +@property (copy) NSArray<MyListItem *> *allItems; |
| 344 | +@end |
| 345 | + |
| 346 | + |
| 347 | +NS_ASSUME_NONNULL_END |
| 348 | +``` |
| 349 | +
|
| 350 | +需要注意的是: `typedef` 类型即使在 NS_ASSUME_NONNULL_BEGIN + NS_ASSUME_NONNULL_END 区域内也不会被假定为非空,因为它们本质上不是可空类型。 |
| 351 | +
|
| 352 | +
|
| 353 | +
|
0 commit comments