OC中的一些宏
- OC中的一些宏
-
- [1. FOUNDATION_EXPORT](#1. FOUNDATION_EXPORT)
- [2. Nullability 注解体系](#2. Nullability 注解体系)
- [3. NS_STRING_ENUM / NS_EXTENSIBLE_STRING_ENUM](#3. NS_STRING_ENUM / NS_EXTENSIBLE_STRING_ENUM)
- [4. NS_SWIFT_NAME / @objc](#4. NS_SWIFT_NAME / @objc)
OC中的一些宏
1. FOUNDATION_EXPORT
FOUNDATION_EXPORT能够将一个常量暴露给文件外供使用,有区分于C++。
-
变量:
- C++/OC: C++/OC中如果想实现变量 能够被外部文件使用,直接在全局作用域定义,外部文件使用时需要使用
extern修饰声明这个变量才可以使用
- C++/OC: C++/OC中如果想实现变量 能够被外部文件使用,直接在全局作用域定义,外部文件使用时需要使用
-
常量:
- C++:C++中如果想实现常量 能够被外部文件使用,需要在定义 时使用
extern修饰,外部文件使用时需要使用extern修饰声明这个常量才可以使用。 - OC:OC中如果想实现常量 能够被外部文件使用,需要在声明 时使用
FOUNDATION_EXPORT修饰,外部文件使用时导入声明这个常量的头文件,这个常量就可以使用。
- C++:C++中如果想实现常量 能够被外部文件使用,需要在定义 时使用
注意:OC中不能使用FOUNDATION_EXPORT修饰定义,只能修饰声明。C++中可以使用extern修饰定义。
2. Nullability 注解体系
OC没有像SWift的可选类型语法,导致在处理空指针问题时劣于SWift,于是使用这套体系补充OC的空值语义。
整个体系包含 3 个基础注解 + 2 个批量标注宏,覆盖所有指针类型的空值场景:
| 注解 / 宏 | 含义 | Swift 映射类型 | 适用场景 |
|---|---|---|---|
_Nonnull |
指针必须非空 | 非可选类型(String) |
核心参数、必选属性、非空返回值 |
_Nullable |
指针可以为空 | 可选类型(String?) |
可选参数、可选属性、可能返回 nil 的方法 |
_Null_unspecified |
指针未指定空值状态(编译器不做校验,默认行为) | 隐式可选类型(String!) |
兼容旧代码,不推荐新代码使用 |
NS_ASSUME_NONNULL_BEGIN/END |
批量标注宏:块内所有未显式标注的指针默认 _Nonnull |
- | 类 / 分类的大部分接口为非空时,减少重复标注 |
NS_REFINED_FOR_SWIFT |
(进阶)细化 Swift 混编的空值语义(如 OC 标 _Nonnull,Swift 侧仍可处理 nil) |
- | 混编场景的精细控制 |
注意:
- 仅作用于指针类型 :
_Nonnull/_Nullable只对 OC 对象指针(NSString */UIView *)、C 指针(char *)、Block 指针生效,基本类型(int/CGFloat/BOOL)标注无意义(编译器忽略); - 注解是 "约定" 而非 "强制" :
_Nonnull仅做编译期静态校验,若通过运行时(如 KVC、强制类型转换)给_Nonnull变量赋值nil,编译器无法拦截,运行时仍可能崩溃;
2.1NS_REFINED_FOR_SWIFT
先说一个场景,OC中定义了一个返回值为非空的方法,Swift中调用OC中的这个方法,但是这个方法返回了一个nil,虽然返回值定义了是非空,但是由于混编到Swift中,_Nonnull类型会被映射成非可选类型(String),此时返回了一个nil,此时就会导致Swift中崩溃。
NS_REFINED_FOR_SWIFT的出现就是为了解决这个问题。
哎,那如果是OC中调用这个方法会出现什么现象呢?
: _Nonnull约束的是编译器,运行期返回了nil是不会有什么崩溃报错的,因为OC语言的设计上对空指针的严格性天然的就是小于Swift的,所以Swift中会崩溃,OC中不会,所以才需要NS_REFINED_FOR_SWIFT。
修饰是这样的:
objective-c
@interface ZZTool : NSObject
// 1. OC 对外暴露:标 _Nonnull,承诺非空(OC 调用者看到的是这个)
- (NSString * _Nonnull)getUserName NS_REFINED_FOR_SWIFT;
@end
使用场景:
- 当OC中定义一个返回值为
_Nonnull的方法,这个方法可能会返回一个nil时,并且SWift中会调用是,最好(必须)使用NS_REFINED_FOR_SWIFT进行修饰。
核心别名对照表(官方定义,语义完全等价):
| 原注解 / 宏 | 官方别名(宏定义) | 等价关系 | 备注 |
|---|---|---|---|
_Nonnull |
nonnull |
#define nonnull _Nonnull |
最常用的别名 |
_Nullable |
nullable |
#define nullable _Nullable |
最常用的别名 |
_Null_unspecified |
null_unspecified |
#define null_unspecified _Null_unspecified |
极少用,仅兼容旧代码 |
NS_ASSUME_NONNULL_BEGIN/END |
无官方别名 | - | 无别名,但有对称宏(见下文) |
NS_REFINED_FOR_SWIFT |
无任何别名 / 变体 | - |
3. NS_STRING_ENUM / NS_EXTENSIBLE_STRING_ENUM
这两个宏是为了解决:
- OC 仅支持「整型枚举」(底层只能是数字),语法上无法直接定义 "字符串枚举",传统方式只能用零散的
NSString *常量模拟,存在类型不安全、语义不清晰的问题; - Swift 原生支持「字符串底层的枚举」,而 OC 模拟的字符串常量会被 Swift 映射为裸
String(失去枚举语义),二者混编时体验差、易出错;
NS_STRING_ENUM使用方法:
objective-c
// 头文件:可扩展的字符串枚举
typedef NSString *ZZImageExtFormat NS_STRING_ENUM;
FOUNDATION_EXPORT ZZImageExtFormat const ZZImageExtFormatPNG; // 预设:png
FOUNDATION_EXPORT ZZImageExtFormat const ZZImageExtFormatJPG; // 预设:jpg
// 实现文件:赋值
ZZImageExtFormat const ZZImageExtFormatPNG = @"png";
ZZImageExtFormat const ZZImageExtFormatJPG = @"jpg";
// 使用时
// ✅ 合法:用预设的 png
[self processImage:ZZImageFixedFormatPNG];
// ❌ 非法:传自定义的 avif(编译器直接报错!)
[self processImage:@"avif"];
NS_EXTENSIBLE_STRING_ENUM使用方法:
objective-c
// 头文件:可扩展的字符串枚举
typedef NSString *ZZImageExtFormat NS_EXTENSIBLE_STRING_ENUM;
FOUNDATION_EXPORT ZZImageExtFormat const ZZImageExtFormatPNG; // 预设:png
FOUNDATION_EXPORT ZZImageExtFormat const ZZImageExtFormatJPG; // 预设:jpg
// 实现文件:赋值
ZZImageExtFormat const ZZImageExtFormatPNG = @"png";
ZZImageExtFormat const ZZImageExtFormatJPG = @"jpg";
// 使用时
// ✅ 合法:用预设的 png
[self processImage:ZZImageExtFormatPNG];
// ✅ 合法:传自定义的 avif(编译器不报错!)
[self processImage:@"avif"];
从上面可以发现NS_STRING_ENUM和NS_EXTENSIBLE_STRING_ENUMde 区别就是不可扩展和可扩展。
也就是使用不可扩展的NS_STRING_ENUM,使用这个枚举时只能使用预设的ZZImageExtFormatPNG和ZZImageExtFormatJPG。
如果使用了可扩展的NS_EXTENSIBLE_STRING_ENUM,使用这个枚举时可以传入自定义的字符串值:@"avif"。
使用了NS_STRING_ENUM和NS_EXTENSIBLE_STRING_ENUM映射到Swift中时,就会被映射为:
swift
enum ZZNetworkStatus: String {
...
...
}
4. NS_SWIFT_NAME / @objc
混编的时候,OC中的方法/变量在Swift中使用时希望能够重定义方法/变量名,就可以使用NS_SWIFT_NAME,反之可以使用@objc。
NS_SWIFT_NAME的使用方法:
objective-c
// OC中:
@interface ZZPerson : NSObject NS_SWIFT_NAME(Person);
@property (nonatomic, copy) NSString *name;
- (NSString *)getName;
@end
// Swift中使用时
// 直接用 Person,无需写 ZZPerson
let person = Person()
person.name = "张三"
print(person.getName())
@objc的使用方法:
objective-c
// Swift 代码
class Person: NSObject {
// OC 中属性名变为 userName(而非 name)
@objc(userName)
var name: String = ""
}
// OC 代码
person.userName = @"李四"; // 用自定义的 OC 属性名
NSLog(@"%@", person.userName);
注意:重命名后仅能使用重命名后的名称(对应语言侧),原名称会失效,就是OC中重命名后在Swift中只能使用重命名后的名称,不能使用原名称,但是OC中只能使用原名称,反之依然。