Swift 5.5新增功能:现在可以定义协议api,让我们使用Swift非常方便的“点语法”来创建符合要求的实例,这反过来可以使某些协议更像枚举,同时仍然保留协议给我们的所有灵活性。
例如,当应用一个样式到SwiftUI列表时,我们目前(在Swift 5.4中)必须拼出该样式类型的整个名称-像这样:
struct ItemList: View {
var items: [Item]
var body: some View {
List(items) { item in
...
}
.listStyle(InsetGroupedListStyle())
}
}
但是现在,我们可以通过输入.insetGrouped来引用上面的样式,这对许多Swift开发人员来说应该更直观,因为这是我们通常在其他类型的配置api中使用的语法类型:
struct ItemList: View {
var items: [Item]
var body: some View {
List(items) { item in
...
}
.listStyle(.insetGrouped)
}
}
好消息是,上述新的API风格甚至完全向后兼容苹果操作系统的早期版本(只要我们使用Xcode 13来构建我们的应用)。
那么,我们如何在自己的代码中使用这个新特性呢? 举个例子,让我们假设一个我们正在开发的应用程序包含如下的ImageLoader API,它被建模为一个协议,然后有独立的实现来加载内部或外部图像:
protocol ImageLoader {
func loadImage(from url: URL) async throws -> Image
}
class ExternalImageLoader: ImageLoader {
...
func loadImage(from url: URL) async throws -> Image {
// Load the image from an external server.
...
}
}
class AuthenticatedImageLoader: ImageLoader {
private let token: AccessToken
...
func loadImage(from url: URL) async throws -> Image {
// Load the image from our app's own server, using
// the loader's access token.
...
}
}
为了能够使用点语法引用上述两个ImageLoader实现,我们所要做的就是为每一个定义一个受类型约束的扩展——它反过来包含一个静态API来创建该类型的实例:
extension ImageLoader where Self == ExternalImageLoader {
static var external: Self { Self() }
}
extension ImageLoader where Self == AuthenticatedImageLoader {
static func authenticated(with token: AccessToken) -> Self {
Self(token: token)
}
}
有了上面的内容,我们现在可以很容易地引用.external或.authenticated,这取决于我们要创建的图像加载器的类型:
let searchListVC = ItemListViewController(
dataSource: makeSearchListDataSource(),
imageLoader: .external
)
let friendsListVC = ItemListViewController(
dataSource: makeFriendsListDataSource(),
imageLoader: .authenticated(with: user.accessToken)
)
虽然上述技术要求更多的代码比当使用enum(或结构体与静态api),它让我们保留使用协议的主要好处,比如能够实现独立功能使用独立的类型,同时还使我们调用网站简单得多。
就我个人而言,我认为这个新的语言特性在处理具有有限实现集的协议时特别有用——比如SwiftUI的各种样式协议,以及上面的ImageLoader示例。