Skip to content

Commit 469eb70

Browse files
Auto-update blog content
1 parent de8e572 commit 469eb70

File tree

69 files changed

+17650
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+17650
-0
lines changed
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
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+
![](https://i-blog.csdnimg.cn/direct/a5b09e239cc642bbb5fe5f9b11ea3c71.png)
49+
50+
另外值得注意的是, 因为 NS_ENUM 是可扩展的, 所以所有的 case 有可能不只声明时的 3 种,因此  NS_ENUM 在引入 swift 时, swift5 和 swift6 会分别通过警告和报错强制要你在 switch-case 里加 default, 来应对除声明 NS_ENUM 以外的 case.
51+
52+
![](https://i-blog.csdnimg.cn/direct/b8b300631e934b9282bfb74805810c5f.png)
53+
54+
![](https://i-blog.csdnimg.cn/direct/d48aa4abaeb0491e944480eb9ac29c2e.png)
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+
![](https://i-blog.csdnimg.cn/direct/eff4c6b4782e475383ea80f1c1aaaee7.png)
75+
76+
例子:
77+
![](https://i-blog.csdnimg.cn/direct/83a8b3165cd1462694c3f3ff079f0b1c.png)
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+
![](https://i-blog.csdnimg.cn/direct/cb5b46849c5842f7b29647753895e19b.png)
101+
102+
#### 4、NS_TYPED_ENUM
103+
104+
如果你想用 raw value 为非整型的枚举, 而且你枚举的 case 在逻辑上 (语义上) 是不可扩展的, 那就推荐用 NS_TYPED_ENUM.
105+
106+
![](https://i-blog.csdnimg.cn/direct/f3b69772f15f42348d5a826b95ed59a1.png)
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+
![](https://i-blog.csdnimg.cn/direct/e85c983a3b304779a45b1ec905748477.png)
124+
125+
NS_TYPED_ENUM 是作为 swift 的 enum 引入的, 所以其实在代码上确实可以通过对 TrafficLightColor 加 extension 的方式来实现新增 case. 所以 NS_TYPED_ENUM 仅仅只是语义上不支持新增 case 而已.
126+
127+
![](https://i-blog.csdnimg.cn/direct/5c187e9999864d12bcb35c59600ecbd4.png)
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+
![](https://i-blog.csdnimg.cn/direct/b0d1adeec3a94885a43e82f5438679ba.png)
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+
![](https://i-blog.csdnimg.cn/direct/86f78c6de15e45c090ae1ee8c5b84ab6.png)
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+
![](https://i-blog.csdnimg.cn/direct/47898ded84b44b9c85f199cc59439aa7.png)
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

Comments
 (0)