当前位置: 首页 > news >正文

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 区域内也不会被假定为非空,因为它们本质上不是可空类型。

http://www.dtcms.com/a/390622.html

相关文章:

  • 【XTDrone】笔记5:control文件详解
  • 抓包的那些事,抓包的原理、常见场景、工具比较与实战排查流程(抓包步骤、iOS 抓包、HTTPS 抓包技巧)
  • 软件工程实践八:Web 前端项目实战(SSE、Axios 与代理)
  • 【常见集合】ArrayList与LinkedList
  • IPD流程实战:如何跨领域应用IPD思维?
  • Archery:开源、一站式的数据库 SQL 审核与运维平台
  • 北斗GNSS在地质灾害监测中的变形监测技术与应用解析
  • C语言题目:用“*”作为元素打印菱形
  • Redis的java客户端(SpringDataRedis)
  • Amazon Aurora DSQL:分布式无服务器数据库的下一场革命
  • TVS管频繁损坏,是参数错选还是布局出问题?-ASIM阿赛姆
  • 论文阅读:TEMPORAL GRAPH NETWORKS FOR DEEP LEARNING ON DYNAMIC GRAPHS
  • 医疗行业淘汰赛开始了?医疗器械售后维修是否会有影响?
  • 第二部分:VTK核心类详解(第40章 vtkIdList ID列表类)
  • Elasticsearch的自定义score评分
  • 【软考-系统架构设计师】架构权衡分析方法(ATAM)
  • 信息系统项目的成本管理
  • Python进阶指南7:排序算法和树
  • 深入理解 HashMap的数据结构
  • ArcGIS前后两期数据库对比工具
  • React18学习笔记(三) ReactRouter----React中的路由
  • [cesium] vue3 安装cesium方法
  • 埃文科技亮相华为全联接大会2025 联合鲲鹏发布AI使能平台解决方案 共筑AI产业新生态
  • Linux 桌面环境GNOME 49 释出
  • react/umi,浏览器tab设置
  • langchain-PipelinePromptTemplate
  • git 本地仓库与远程仓库链接
  • 绘想 - 百度推出的AI视频创作平台
  • 穿越像素的凝视:深度解析视频中的人物与动物识别算法技术
  • OpenHarmony 4.0 Release源码下载、编译及烧录