【iOS】OC中的一些宏

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++:C++中如果想实现常量 能够被外部文件使用,需要在定义 时使用extern修饰,外部文件使用时需要使用extern修饰声明这个常量才可以使用。
    • OC:OC中如果想实现常量 能够被外部文件使用,需要在声明 时使用FOUNDATION_EXPORT修饰,外部文件使用时导入声明这个常量的头文件,这个常量就可以使用。

注意: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_ENUMNS_EXTENSIBLE_STRING_ENUMde 区别就是不可扩展和可扩展。

也就是使用不可扩展的NS_STRING_ENUM,使用这个枚举时只能使用预设的ZZImageExtFormatPNG和ZZImageExtFormatJPG。

如果使用了可扩展的NS_EXTENSIBLE_STRING_ENUM,使用这个枚举时可以传入自定义的字符串值:@"avif"

使用了NS_STRING_ENUMNS_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中只能使用原名称,反之依然。

相关推荐
SuperEugene14 分钟前
Vue3 + Element Plus 表单开发实战:防重复提交、校验、重置、loading 统一|表单与表格规范篇
前端·javascript·vue.js
SuperEugene16 分钟前
Vue3 + Element Plus 中后台弹窗规范:开闭、传参、回调,告别弹窗地狱|Vue 组件与模板规范篇
开发语言·前端·javascript·vue.js·前端框架
桜吹雪17 分钟前
在前端运行Qwen3.5原生多模态模型
前端·人工智能·机器学习
孟祥_成都19 分钟前
前端下午茶:这 3 个网页特效建议收藏(送源码)
前端·javascript·css
SuperEugene19 分钟前
VXE-Table 4.x 实战规范:列配置 + 合并单元格 + 虚拟滚动,避坑卡顿 / 错乱 / 合并失效|表单与表格规范篇
开发语言·前端·javascript·vue.js·前端框架·vxetable
火车叼位32 分钟前
Volta 下 `corepack` 失踪之谜:问题不在 Node,而在命令入口
前端
cmd35 分钟前
别再用错!5种JS类型判断方法,从原理到实战一文吃透
前端·javascript
小江的记录本39 分钟前
【Redis】Redis常用命令速查表(完整版)
java·前端·数据库·redis·后端·spring·缓存
Csvn43 分钟前
状态管理方案对比(Context、Zustand、Jotai 选型指南)
前端
snow_yan44 分钟前
基于 json-render 的流式表单渲染方案
前端·react.js·llm