#Swift :回调地狱 的解决 —— 通过 task/await 来替代 nested mutiple trailing closure 来进行 回调的解耦

前言:本质是通过 task/await 来替代 nested mutiple trailing closure 来进行 回调的解耦

回调地狱的特点

  1. 多层嵌套:异步函数调用嵌套在另一个异步函数的回调中,层数可能非常深。
  2. 代码可读性差:由于闭包和函数调用深度嵌套,代码的流程难以一目了然。
  3. 错误处理复杂:在多层嵌套中处理错误变得复杂,每一层都可能需要错误处理逻辑。。

在这个例子中,loadPicture(from:completion:onFailure:) 函数通过使用多个完成处理器(completion handlers)来处理网络任务的成功和失败情况。这种设计可以让代码更加清晰地分离网络任务的处理逻辑:一部分处理成功结果,另一部分处理失败情况。相比于使用一个同时处理成功和失败的闭包,使用两个不同的闭包能让代码逻辑更具可读性和模块化。

示例解释

这个函数的核心目的是进行网络任务(如下载图片),并根据任务的结果调用相应的闭包进行处理。通过这种方式,可以将成功和失败的逻辑分开,从而简化每个闭包的逻辑。

函数签名
swift 复制代码
func loadPicture(from url: String, completion: @escaping (UIImage) -> Void, onFailure: @escaping (Error) -> Void) {
    // 模拟网络请求
    DispatchQueue.global().async {
        // 模拟成功或失败的情况
        let success = Bool.random()
        if success {
            // 模拟下载成功,返回图片
            let image = UIImage()
            DispatchQueue.main.async {
                completion(image)  // 成功时调用
            }
        } else {
            // 模拟网络错误
            let error = NSError(domain: "NetworkError", code: 1, userInfo: nil)
            DispatchQueue.main.async {
                onFailure(error)  // 失败时调用
            }
        }
    }
}

代码分析

  1. 函数参数

    • url:这是图片的下载地址。
    • completion:这是一个在下载成功时执行的闭包,接收一个 UIImage 类型的参数,用于更新 UI 或处理成功结果。
    • onFailure:这是一个在下载失败时执行的闭包,接收一个 Error 类型的参数,用于处理错误情况,如显示错误消息等。
  2. 异步任务

    • 函数使用 DispatchQueue.global().async 模拟了后台网络任务,这意味着下载操作会在后台线程中执行,以避免阻塞主线程。
    • 使用 Bool.random() 来模拟任务的成功或失败。
  3. 成功和失败的处理

    • 如果任务成功,模拟返回一个 UIImage,并在主线程调用 completion 闭包来更新 UI。
    • 如果任务失败,生成一个 Error,并在主线程调用 onFailure 闭包来处理错误。

调用示例

调用该函数时,可以提供不同的处理逻辑来处理成功和失败的情况:

swift 复制代码
loadPicture(from: "https://example.com/picture.jpg", completion: { image in
    print("Image downloaded successfully: \(image)")
}, onFailure: { error in
    print("Failed to download image: \(error.localizedDescription)")
})

在这个示例中,成功时会打印图片对象,失败时则打印错误信息。

Completion Handlers 和嵌套闭包

当你需要处理多层异步任务时(例如:多次网络请求或者依赖关系的任务),嵌套多个 completion handlers 可能会使代码变得复杂且难以阅读。这种情况被称为回调地狱,因为多个嵌套闭包会让代码层级不断增加,影响可读性和可维护性。

swift 复制代码
// 嵌套多个异步任务的例子
loadPicture(from: "https://example.com/picture1.jpg", completion: { image1 in
    loadPicture(from: "https://example.com/picture2.jpg", completion: { image2 in
        loadPicture(from: "https://example.com/picture3.jpg", completion: { image3 in
            print("All images downloaded successfully")
        }, onFailure: { error in
            print("Failed to download third image")
        })
    }, onFailure: { error in
        print("Failed to download second image")
    })
}, onFailure: { error in
    print("Failed to download first image")
})

这种多层嵌套的代码会很快变得难以维护和调试。

异步/等待作为解决方案

Swift 的 async/await 模式提供了一种更为简洁和直观的方式来处理异步操作,无需深层嵌套。它使得异步代码的编写几乎像同步代码一样直接。例如:

swift 复制代码
func loadAllPictures() async {
    do {
        let image1 = try await loadPicture(from: "https://example.com/picture1.jpg")
        let image2 = try await loadPicture(from: "https://example.com/picture2.jpg")
        let image3 = try await loadPicture(from: "https://example.com/picture3.jpg")
        print("All images downloaded successfully")
    } catch {
        print("Failed to download images: \(error.localizedDescription)")
    }
}

在这里,三次加载图片的调用是线性的,看起来和处理同步代码一样简单。所有的错误处理可以集中在一个地方,大大提高了代码的可读性和维护性。

总结

  • 多个完成处理器 :在 loadPicture(from:completion:onFailure:) 函数中,使用多个闭包分别处理成功和失败,增强了代码的模块化和清晰度。
  • 回调地狱问题:当嵌套多个异步任务时,completion handlers 会导致代码变得难以维护。
  • 并发解决方案 :Swift 通过 async/await 提供了更好的异步编程模型,使代码更易读、更线性,解决了嵌套闭包的问题。

Completion handlers 适合简单的异步操作,但对于更复杂的场景,使用 Swift 并发功能可以大大提高代码的可维护性和简洁度。

提到的"死亡嵌套"通常指的是回调地狱(Callback Hell),这是在处理多层异步操作时常见的问题,特别是当每个异步操作的结果都依赖于前一个操作的完成时。这种模式在使用传统的回调方式(如多个完成处理器)进行异步编程时尤其明显。回调地狱不仅使得代码难以阅读和维护,还增加了调试的复杂性,因为错误处理和流程控制散布在多个不同层级的闭包中。

相关推荐
yufei-coder1 小时前
掌握C#核心概念:类、继承、泛型等
服务器·开发语言·c#
安冬的码畜日常2 小时前
【玩转 JS 函数式编程_003】1.3 JavaScript 是函数式编程语言吗?
开发语言·javascript·ecmascript·functional·vanillajs
TLucas2 小时前
js采用覆盖键、覆盖鼠标滑动事件实现禁止网页通过 ctrl + +/- 和 ctrl + 滚轮 对页面进行缩放
开发语言·javascript
@qike2 小时前
【C++】——类和对象(上)
java·开发语言·jvm·数据结构·c++·笔记·算法
fieldsss2 小时前
JAVA基础语法 Day11
java·开发语言·python
小小娥子2 小时前
Spring的热部署工具和数据库密码加盐操作
java·开发语言·spring boot·spring
Death2002 小时前
深入掌握 Qt 中的数据库操作:从基础到高级技巧
开发语言·数据库·c++·qt·opencv
十一29283 小时前
C语言--编译链接详解
c语言·开发语言
Mr. zhihao3 小时前
Java 静态代理详解:为什么代理类和被代理类要实现同一个接口?
java·开发语言
sz66cm3 小时前
Python基础 -- enumerate()的作用与用法
linux·开发语言·python