• 你知道Swift中一个“没有返回值”的函数至少有三种写法吗?
  • 发布于 21小时前
  • 15 热度
    0 评论
前言
昨晚跟同事 review 代码,看到一个函数写了个 -> Void ,有点意外。这事儿看起来小,但在 Swift 里还真有点门道:一个“没有返回值”的函数,至少有三种完全合法的写法。今天就把这个点掰开揉碎说清楚,顺带聊聊闭包里 Void 的那些易错点。

三种“无返回值”函数写法(都是对的)
先上结论,它们语义等价:
// 1) 显式写 Void
func sayHello() -> Void {
    print("Hello World!")
}
// 2) 用空元组 () 代替 Void
func sayHello() -> () {
    print("Hello World!")
}
// 3) 直接省略返回箭头与类型(最常见)
func sayHello() {
    print("Hello World!")
}
sayHello() // "Hello World!"
.Void 本质是 ()(空元组)的 typealias,所以 -> Void 和 -> () 是一回事。

.省略返回箭头是最常见的写法,说明“这个函数只做事,不给结果”。


推荐怎么选?我的团队实践
.日常函数:优先用第三种(省略返回类型)。更干净,语义明确。
.对外 API 或需要强调副作用时:可以显式写 -> Void,相当于给读者一个“别找返回值了”的提醒。

.文档/教程/示例代码:有时我会刻意写 -> Void 来教学,降低初学者的理解负担。


一句话总结:选择能让“未来的你”最快读懂的写法。

return 还能怎么写?
“无返回值”函数里可以出现裸 return,用于提前结束执行:
func process(_ items: [Int]) {
    guard !items.isEmpty else { return }
    // ... 下面是正式逻辑
}
这里的 return 只是终止,不携带值。

闭包里的 Void:新手最容易踩的坑
闭包签名经常要写返回类型,和函数一样也有三种:
let work1: () -> Void = { print("done") }
let work2: () -> ()   = { print("done") }
let work3: () ->      Void in // 不合法,必须写完整
// 正确的第三种:省略整个返回部分
let work3b: () -> Void = { print("done") }
更常见的是带参数、无返回的场景:
let transform: (Int) -> Void = { value in
    print("value = \(value)")
}
Swift 的类型推断很聪明,但在闭包里省略返回类型容易让签名变得不直观,特别是多层泛型或异步回调时。我的经验是:
对外暴露的属性、参数类型:写全 () -> Void,增强可读性;

局部一次性闭包:交给类型推断,代码更短。


异步/并发与 Void:Task 和 async 的组合
async 函数也可以“无返回值”:
func refresh() async {
    // 异步刷新,无需返回值
}
Task { await refresh() }
如果你习惯在 Task {} 中写匿名闭包,也可以显式标注它是 () -> Void 的异步版本:
@MainActor
func load() async -> Void {  // 允许写 -> Void,但多数情况下直接省略
    // ...
}
注意:async -> Void 与 async(省略返回)依旧等价;选择哪种仍然是“可读性优先”。

Result 和 Void:表示“只关心成功/失败”
有时我们只需要一个“成了/没成”的信号,不关心具体数据:
enum SaveError: Error { case diskFull }
// 堆代码 duidaima.com
func save(_ data: Data, completion: (Result<Void, SaveError>) -> Void) {
    let ok = Bool.random()
    if ok { completion(.success(())) } else { completion(.failure(.diskFull)) }
}
这里的 .success(()) 就是把“空元组”作为“成功但无数据”的语义承载体。Alamofire 也常用 Result<Void, Error> 表示“请求成功但无返回体”。

单元测试里的 Void
当被测方法没有返回值时,测试容易只断言“没崩”。更稳妥的做法是:
.断言副作用:检查依赖对象的状态更新、调用次数(Spy/Mock)
.断言通知/回调:如 Publisher 发出事件、NotificationCenter 收到消息
func test_save_emitsSuccess() {
    let exp = expectation(description: "save success")
    save(Data()) { result in
        if case .success = result { exp.fulfill() }
    }
    waitForExpectations(timeout: 1)
}
写在最后
Void 不神秘,它只是“我做事,但不回礼”。在函数里,三种写法都正确;在团队协作里,选一种最能传达意图的风格,坚持下去即可。至于闭包、异步、Result<Void, Error> 这些周边场景,记住“语义第一,可读优先”,你就不会踩坑。
用户评论