问题描述
本篇文章主要是讲述如何在实践中学习并使用try关键词。
首先,我在学习JSONEncoder时,想要尝试输出经过JSONEncoder编码后的内容,因此,我写了下面这段输出json的代码:
struct User: Codable {
let firstName: String
let lastName: String
}
struct iExpense: View {
@State private var user = User(firstName: "fang", lastName: "junyu")
func decode(tmp: User) {
print(JSONEncoder().encode(user))
}
var body: some View {
VStack {
Button("Button"){
decode(tmp: user)
}
}
}
}
但是上面的print(JSONEncoder().encode(user))会报错:
Call can throw, but it is not marked with 'try' and the error is not handled
这个报错的原因为:JSONEncoder().encode(tmp) 是一个可能会抛出错误的函数。在Swift中,当你调取可能会抛出错误的函数时,必须使用try关键词,并且需要处理可能的错误。
不理解try的人,可能会想,为什么必须使用try关键词,我只想尝试print打印输出这个内容,然后尝试使用三元运算符,当JSONEncoder().encode()为nil时,输出对应的内容:
func decode(tmp: User) {
JSONEncoder().encode(tmp) ? print(encodedData) : print("Failed to encode user")
}
如果使用三元运算符,会报如下错误:
Cannot convert value of type 'Data' to expected condition type 'Bool'
Cannot find 'encodedData' in scope
这是因为三元运算符不支持错误抛出,因为encode方法可能会抛出错误。
这里可能会存在疑问,那就是很多方法都是可以直接调用的,为什么这里必须使用try关键词来捕获错误。
这里我们需要理解一个Swift的理念:在Swift中,一些方法被设计为“throwing functions(抛出函数)”。这意味着,它们在遇到问题时可以抛出错误,而不是返回正常的结果或者nil。对应这种设计,Swift明确表示该方法可能会抛出错误,并提供错误处理机制,因此,我们必须使用try来捕获可能抛出的错误。
以当前代码为案例,JSONEncoder().encode()可能会抛出如下错误:
- 无效的数据类型:当尝试编码的对象不符合Codable协议时,或结构不适合被JSON编码,encode()就会抛出错误。
- 编码报错:如果数据结构非常复杂,也可能会在编码过程中出错。
- 不兼容数据:可能存在数据类型无法正确转换为JSON格式,例如某些嵌套或不支持的类型。
因此,我们在本案例中,调用抛出函数时,Swift会强制要求你使用try,来调用可能抛出错误的函数。
我们的解决方案为:
func decode(tmp: User) {
if let encodedData = try? JSONEncoder().encode(tmp) {
print(encodedData)
} else {
print("Failed to encode user")
}
}
我们给JSONEncoder().encode()方法前面添加一个try?关键词,然后尝试输出这个赋值的内容。
最终的结果为,输出对应字符串的大小:
39 bytes
try和do-catch
当我们已经了解到抛出函数必须使用try?时,我们可以进一步学习一下try和do-catch的三类用法。
用法一:try和do-catch
我们需要了解到当你使用try时,必须使用do-catch来处理可能抛出的错误,当你尝试使用try来调取上面的问题代码时:
func decode(tmp: User) {
if let encodedData = try JSONEncoder().encode(tmp) {
print(encodedData)
} else {
print("Failed to encode user")
}
}
你会发现存在这样的报错:
Errors thrown from here are not handled
Initializer for conditional binding must have Optional type, not 'Data'
这是因为,我们虽然使用的是try,但是try必须和do-catch搭配使用,因为Swift要求你明确处理可能抛出的错误,同时try不会自动将错误转换为nil,因此如果函数抛出错误并且没有处理,程序会直接崩溃。
因此,我们应该使用do-catch在try的外面捕获可能抛出的报错并进行处理:
func decode(tmp: User) {
do {
let encodedData = try JSONEncoder().encode(tmp)
print(encodedData)
} catch {
print("Failed to encode user")
}
}
所以,我们的try和do-catch是搭配使用的,当try调取抛出函数并可能抛出错误时,由do-catch捕获问题并进行相关的处理。
用法二:try?
回到我们本次的解决方案,为什么使用try?可行,而try却报错呢?
func decode(tmp: User) {
if let encodedData = try? JSONEncoder().encode(tmp) {
print(encodedData)
} else {
print("Failed to encode user")
}
}
这是因为,try?会将抛出的错误结果转换为可选类型,简单的理解为,当try?调取抛出函数时,如果存在错误,try?就会自动将错误转换为nil。
因此,我们可以直接通过print函数调取try函数:
func decode(tmp: User) {
print(try? JSONEncoder().encode(tmp))
}
当发生报错时,try就会将错误转换为nil。
我们也可以使用上面的if else语句进行判断,当try?遇到错误并转船为nil后,输出else语句。
用法三:try!
当你明确认为不会发生错误时,可以使用try!关键词:
func decode(tmp: User) {
print(try! JSONEncoder().encode(tmp))
}
当你使用try!时,因为一旦发生错误,程序就会崩溃。所以通常都不推荐使用try!
问题总结
我本打算将本次遇到的问题和try单独分开来写,但感觉单独写一篇try的内容又太少,因此做了一个汇总的问题。
我们先是解决了一个涉及try的报错,又温习或学习了try的三个用法,同时,你也了解到do-catch必须和try组合使用。
总体来说,我之前对应try的学习也很简单,只是知道try可以处理问题,但实际上只有真正的遇到问题并尝试解决时,才会真正的学会如何来运用它们。 全部代码:
import SwiftUI
struct User: Codable {
let firstName: String
let lastName: String
}
struct iExpense: View {
@State private var user = User(firstName: "fang", lastName: "junyu")
func decode(tmp: User) {
if let encodedData = try? JSONEncoder().encode(tmp) {
print(encodedData)
} else {
print("Failed to encode user")
}
}
var body: some View {
VStack {
Button("Button"){
decode(tmp: user)
}
}
}
}
#Preview {
iExpense()
}