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

相关推荐
芊寻(嵌入式)11 分钟前
C转C++学习笔记--基础知识摘录总结
开发语言·c++·笔记·学习
一颗松鼠19 分钟前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
有梦想的咸鱼_21 分钟前
go实现并发安全hashtable 拉链法
开发语言·golang·哈希算法
海阔天空_201327 分钟前
Python pyautogui库:自动化操作的强大工具
运维·开发语言·python·青少年编程·自动化
天下皆白_唯我独黑34 分钟前
php 使用qrcode制作二维码图片
开发语言·php
夜雨翦春韭38 分钟前
Java中的动态代理
java·开发语言·aop·动态代理
小远yyds39 分钟前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
何曾参静谧1 小时前
「C/C++」C/C++ 之 变量作用域详解
c语言·开发语言·c++
q567315231 小时前
在 Bash 中获取 Python 模块变量列
开发语言·python·bash
许野平2 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono