大家好,从本篇文章开始,将连续用几篇文章介绍一下我对新的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
类模拟网络层,在这里进行网络请求。
在DoTryCatchThrowsDemoDataManager
的fetchTitle
方法里面有可能返回正确数据,也有可能返回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
组合的时候,上面的代码会更加的简洁易懂。
最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。