我现在正在浏览Landmarks应用程序教程,我正在学习如何将JSON加载到一个Struct中。
参考:https://developer.apple.com/tutorials/swiftui/building-lists-and-navigation
import Foundation
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
我特别不理解T.self
引用,也不理解func开头的<T: Decodable>
。有人能分解这个函数吗?谢谢。
2条答案
按热度按时间rxztt3cl1#
为了回答这个问题,让我们看看
T
是什么,它在函数的签名中定义这意味着泛型函数
load(_:String) -> T
返回某种符合Decodable
的类型T
,因此T
是引用返回值类型的泛型方式,其中该类型将根据调用点的上下文确定,具体而言,根据接收返回值的任何对象的类型确定。被问到的线路是
这里
T.self
是一种引用类型T
本身的方式,如果你查看decode
的函数签名,你会发现它看起来像这样:注意,参数
type
的类型是T.Type
,这意味着参数type
将保存T
类型,而不是T
类型的值,这是Swift区分指定变量是一个类型的值与其值是类型本身的方法,这可能会让人有点困惑,但一旦你了解了泛型,它就有意义了。因为Swift是一种强类型语言,类型是在编译时定义的,所以
decode
需要知道它试图解码的东西的类型。在JavaScript或Python这样的无类型语言中,这是不必要的。在这些语言中,对象基本上是字典,所以它总是可以解码字典(或数组,或一小组基本类型之一)。在Swift中,struct
和class
不仅仅是字典的语法糖,它们更像是C风格的struct
--一堆二进制数据,其特定的内存布局由类型决定。为了正确地解码,decode
必须知道它正在解码什么,最常见的是,如何告诉它通过它的init(from: Decoder) throws
方法(可能已经由编译器合成)来解码自己,它调用NSImage.init(from: Decoder)
还是String.init(from: Decoder)
等等?如果你熟悉运行时多态在OOP中的实现方式,那么提供类型,即使是一般性的,也提供了一种获取类型的协议见证表的方法--这是类用于虚拟方法的运行时动态调度的“vtable”的协议等价物。所以让它做一些类似于在OOP中调用虚拟方法的事情。除了它通常可以在编译时计算出来之外,理解他的工作原理还可以让我们深入了解一些不同但相关的问题,比如为什么直接在协议中声明的必需方法的多态行为与仅在协议扩展中声明的方法不同(基本上,在协议中直接声明的那些在见证表中,因此它们可以被动态地分派,而仅在扩展中声明的那些不在见证表中,因此任何类型特定的实现都丢失了,取而代之的是,只有在协议的扩展中的实现可以被调用)。
下面的函数使用它来打印其参数的类型(有更好的方法来实现这一点,但这说明了
T.Type
和T.self
的作用):bvhaajcl2#
您缺少
var landMarks: [Landmark] = load("landmarkData.json")
这段代码,因为func load(_:)->T
返回值类型是[Landmark]类型,所以T是[Landmark]数组类型。这是我理解