Swift @escaping标记闭包参数
Swift @escaping标记闭包参数

Swift @escaping标记闭包参数

在 Swift 中,@escaping 用于标记闭包参数,表明该闭包在函数执行完之后 仍可能被调用。换句话说,带有 @escaping 标记的闭包会在函数作用域之外执行,比如在异步操作、回调中使用的闭包通常是 @escaping 的。

为什么需要 @escaping

默认情况下,闭包参数是非逃逸(non-escaping)的,意味着闭包会在函数执行完毕之前被调用。如果要在函数返回后延迟调用闭包(例如异步网络请求的回调),需要将闭包声明为 @escaping,否则编译器会报错

使用 @escaping 的场景

1、异步操作:比如网络请求、数据库查询等,需要延迟执行的操作。

2、存储闭包供后续调用:如果需要将闭包存储在一个变量或数组中,以便在函数返回后再调用,这种情况下也需要 @escaping。

这里是一个带有 @escaping 闭包的简单示例:

func fetchData(completion: @escaping (String) -> Void) {
    DispatchQueue.global().async {
        // 模拟耗时的网络请求
        sleep(2) // 模拟延迟
        let result = "网络请求完成,获取数据成功"
        
        // 调用完成后的回调,把结果传给调用者
        completion(result)
    }
}
fetchData { result in
    print(result)
    // 更新 UI,例如显示获取的数据
    print("在主线程更新 UI")
}

在这个例子中:

completion 闭包参数被标记为 @escaping,因为闭包会在 performAsyncTask 返回后被调用。

DispatchQueue.global().async 创建了一个异步任务,2秒后执行 completion 闭包。这种情况下,闭包必须是 @escaping。

理解 completion 的作用

fetchData 是一个异步函数,它可能需要 2 秒才能获取数据。但我们不希望阻塞主线程。

completion 闭包的作用是:在异步任务完成后执行一些额外操作。在这个例子中,就是将获取的数据更新到 UI 上。

如果不调用 completion,我们不会知道数据获取何时完成,UI 就无法更新。这种情况下,completion 是一种通知机制,让调用者知道异步任务已经完成,并且可以根据需求执行特定操作。

例如,我们根据上面的代码,再添加一个sleep(2):

func fetchData(completion: @escaping (String) -> Void) {
    DispatchQueue.global().async {
        // 模拟耗时的网络请求
        sleep(2) // 模拟延迟
        let result = "网络请求完成,获取数据成功"
        sleep(2) // 模拟延迟
        // 调用完成后的回调,把结果传给调用者
        completion(result)
    }
}

这段代码会先延迟2秒,然后定义result,再延迟两秒钟。最后调用completion(result)方法进行回调。

fetchData { result in
    print(result)
    // 更新 UI,例如显示获取的数据
    print("在主线程更新 UI")
}

当completion(result)回调完成后,我们才能看到fetchData输出 result 以及“在主线程更新UI”的提示。

画图理解流程

首先,这是我们的fetchData方法以及调用fetchData的代码。我们需要明白fetchData方法的completion是一个闭包:

completion: @escaping (String) -> Void

这个闭包表示一个以String为参数,返回Void的闭包。因此,我们在调用的时候,是将fetchData { result in …}传递给 completion参数:

将这个闭包传递给completion参数,注意的是,闭包的类型与completion参数 (String) -> Void 一致。

然后在fetchData内部开始调用异步任务。

在异步任务执行完毕后,completion(result)会被调用。

Completion(result)实际上是调用了fetchData调用者传入的闭包 {result in …},并将result参数传递给它。

fetchData { result in   // result 为 变量 result
    print(result)   // "网络请求完成,获取数据成功"
    // 更新 UI,例如显示获取的数据
    print("在主线程更新 UI")
}

后,completion(result)调用的就是fetchData闭包,在 { result in … } 闭包内部,result 是传入的字符串(即 “网络请求完成,获取数据成功”)。

print(result) 会输出这个字符串。

@escaping 的必要性

在 Swift 中,闭包默认是 非逃逸(non-escaping)的,这意味着闭包会在函数内部执行完毕,不会离开函数的作用范围。而 @escaping 标记的闭包表示这个闭包可能在函数返回之后才会执行。

由于 completion 闭包是在异步任务内调用的,而这个任务在 fetchData 返回后还在执行中,意味着闭包没有在 fetchData 函数内完成调用,而是“逃逸”到了函数之外。

@escaping 的关键在于函数作用域和生命周期管理。在这里,fetchData函数会立即返回,但异步任务在全局队列中执行,可能需要一段时间才能完成。

因为闭包会在异步任务完成后才被调用,这意味着闭包的生命周期要比 fetchData的生命周期更长。使用 @escaping 明确指出这一点,确保闭包在异步任务中能正常存活并执行,而不会被过早释放。

为什么complection会被调用?

在 fetchData 函数中,completion 被作为参数传入,所以在函数内部,completion 可以像普通变量一样被使用。这意味着当 fetchData 被调用时,可以直接在函数体中通过 completion() 来调用该闭包。

因为 completion 是作为参数传入的闭包,我们可以在异步任务完成后直接调用它,类似于调用一个函数来执行传入的操作。

@escaping 和非 @escaping 的区别

非逃逸闭包:闭包在函数执行完之前就会被调用。这是闭包的默认行为。

逃逸闭包(@escaping):闭包可以在函数返回之后被调用。

总结

@escaping 表示闭包逃逸出当前函数,在函数返回之后还可能会被执行。它适用于需要延迟执行、异步操作、或在函数返回后调用闭包的情况。

同时确保闭包生命周期延长,允许在函数返回后调用它,从而避免函数返回后闭包被释放。

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注