Swift concurrency 1 — Do Try Catch Throws理解与使用

大家好,从本篇文章开始,将连续用几篇文章介绍一下我对新的Swift并发框架的学习与理解,记录在博客中,供大家分享,对于某些大佬,内容可能会有点肤浅,还望见谅。

本篇文章还是从基础的一些东西开始,首先回顾一下Do Try Catch Throws这几个几个关键字,以及为什么推荐使用这几个关键字。

首先先看一个普通的操作,我们在SwiftUI界面点击后去获取一个title,模拟这个title是从网络获取的。

import SwiftUI

class DoTryCatchThrowsDemoDataManager {
    
    private var isError: Bool = false
    
    func fetchTitle(_ completion: (String?, Error?) -> Void) {
        if !isError {
            completion("New title", nil)
        } else {
            completion(nil, URLError(.badURL))
        }
    }
    
}

class DoTryCatchThrowsDemoViewModel: ObservableObject {
    
    @Published var title: String = "Original title"
    
    private var dataManager = DoTryCatchThrowsDemoDataManager()
    
    func getTitle() {
        dataManager.fetchTitle { [weak self] title, error in
            guard let self else { return }
            if let title {
                self.title = title
            } else if let error {
                self.title = error.localizedDescription
            }
        }
    }
}

struct DoTryCatchThrowsDemo: View {
    
    @StateObject private var viewModel = DoTryCatchThrowsDemoViewModel()
        
    var body: some View {
        Text(viewModel.title)
            .font(.largeTitle)
            .frame(width: 300, height: 300)
            .background(Color.cyan)
            .onTapGesture {
                viewModel.getTitle()
            }
    }
}

在上面的代码中,还是老样子有个DoTryCatchThrowsDemoViewModel,同时还新增了一个DoTryCatchThrowsDemoDataManager类模拟网络层,在这里进行网络请求。

DoTryCatchThrowsDemoDataManagerfetchTitle方法里面有可能返回正确数据,也有可能返回error,这里面采用了一个元组返回信息,isError属性只是为了模拟失败情况用的。

上面的写法还算比较简单,但是在实际的项目中这么简单的不多,如果遇到下面这种嵌套比较多的,那就很乱了。

func fetchTitle(_ completion: (String?, Error?) -> Void) {
    getToken { token, error in
        guard let token else {
            completion(nil, URLError(.badServerResponse))
            return
        }
        getProfile(token) { title, error in
            if let error {
                completion(nil, URLError(.badServerResponse))
            } else {
                completion("New title", nil)
            }
        }
    }
}

func getToken(_ completion: (String?, Error?) -> Void) {
    completion("token", nil)
}

func getProfile(_ token: String, completion: (String?, Error?) -> Void) {
    completion("title", nil)
}

fetchTitle的时候,需要先去请求token,然后那返回的token再去请求getProfile,然后用getProfile返回的内容,最终显示在UI上。

上面每个请求的闭包里面都要判断一下是否请求到数据,是否有error发生,有时候不同的请求的error信息还可能不太一样,回传的话不见得上一级就处理了error,嵌套的多了,很难维护。

为了避免上面的情况,在遇到错误的时候,我们可以直接抛出异常错误,修改上面的代码如下:

func fetchTitle(_ completion: (String?) -> Void) throws {
        do {
            try getToken { token in
                if let token {
                    do {
                        try getProfile(token) { title in
                            completion(title)
                        }
                    } catch let error {
                        completion(error.localizedDescription)
                    }
                }
            }
        } catch let error {
            completion(error.localizedDescription)
        }
        
    }

    func getToken(_ completion: (String?) -> Void) throws {
        let success = true
        if success {
            completion("token")
        } else {
            throw URLError(.badServerResponse)
        }
        
    }

    func getProfile(_ token: String, completion: (String?) -> Void) throws {
        let success = true
        if success {
            completion("title")
        } else {
            throw URLError(.badURL)
        }
    }

哈哈,感觉代码反而多了起来,不过逻辑上看似更清楚了一些,毕竟嵌套了两层呢。
ViewModel中,修改成如下:

func getTitle() {
    do {
        try dataManager.fetchTitle { [weak self] title in
            guard let self else { return }
            if let title {
                self.title = title
            }
        }
    } catch let error {
        self.title = error.localizedDescription
    }
}

当抛出异常的时候,比如有执行步骤1 2 3 4,每个步骤都能抛出异常,比如2抛出了一场,那么3 4两个步骤就不执行了,也就是异常之后的代码不会执行了。

如果你对有些异常错误不关心,无关紧要,那么我们可以使用try?忽略这次的异常。

比如在DoTryCatchThrowsDemoDataManager中,将fetchTitle方法修改如下:

func fetchTitle(_ completion: (String?) -> Void) throws {
    try? getToken { token in
        if let token {
            try? getProfile(token) { title in
                completion(title)
            }
        }
    }
}

采用try?的方式,忽略了每次请求可能抛出的异常,在ViewModel中调用也改一下:

func getTitle() {
    try? dataManager.fetchTitle { [weak self] title in
        guard let self else { return }
        if let title {
            self.title = title
        }
    }
}

这样代码就简洁多了,不过如果是用户点击触发的一系列请求,全都忽略错误的话,会造成点击无反应的情况,所以说还是要适当的处理。

说了这么多,简单总结一下吧。

do 块:包含可能抛出错误的代码。
try 关键字:用于调用可能抛出错误的函数或方法。
catch 块:捕获并处理错误。

在方法中加入throws,可以在方法中向上抛出一个异常,交给调用者去处理。
如果方法中可能抛出异常,那么在调用的地方需要加try或者try?。
如果用try,那么需要结合do-catch组合去处理error信息。
如果采用try?,那么意味着忽略异常错误,也就不需要do-catch组合了。

在 Swift 中,采用do-catch语句可以有效地处理可能出现的异常错误,提高应用的稳定性和用户体验。在适当的地方捕获和处理错误,是异常处理的关键步骤。
在实际应用开发中,处理网络请求、数据解析等操作时,通常会用到异常处理机制。通过合理的异常处理,可以使应用更稳定,更易于维护。

本文只是针对Swift并发框架的学习与理解这个系列文章的一个前期铺垫,后期用到async/await组合的时候,上面的代码会更加的简洁易懂。

最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值