Swift 如何在任意抽象层中使用泛型?
摘要 我试图理解如何重构代码和使用Swift泛型。我能够跨几个概念上相似的类型识别通用的实现,但我遇到了困难,因为代码构建的层不是泛型的 背景 以下是我对一些我想利用的概念的理解 协议 用于声明接口并具有该接口的许多“具体”实现。这允许您在运行时交换实现 仿制药 当实现相同或相似,但类型不同时,此功能非常有用。似乎典型的示例是交换两个值或Swift 如何在任意抽象层中使用泛型?,swift,generics,protocols,Swift,Generics,Protocols,摘要 我试图理解如何重构代码和使用Swift泛型。我能够跨几个概念上相似的类型识别通用的实现,但我遇到了困难,因为代码构建的层不是泛型的 背景 以下是我对一些我想利用的概念的理解 协议 用于声明接口并具有该接口的许多“具体”实现。这允许您在运行时交换实现 仿制药 当实现相同或相似,但类型不同时,此功能非常有用。似乎典型的示例是交换两个值或map 问题 我有一个客户端类(APIClient),负责创建URL请求。客户机只知道主要数据类型(整数、字符串、数组、JSON等)。客户端类上方有几个类抽象域
map
问题
我有一个客户端类(APIClient
),负责创建URL请求。客户机只知道主要数据类型(整数、字符串、数组、JSON等)。客户端类上方有几个类抽象域对象(FruitDownloader
,TrafficDownloader
,以及WildlifeDownloader
)。此类知道如何从域类型解包主数据类型并调用正确的客户端方法。这个类使用映射器类(groutmapper
、TrafficMapper
、和WildlifeMapper
)从JSON构建域对象
…Dowloader
类的实现非常相似:
class FruitDownloader {
init(client: APIClient, mapper: FruitMapper)
{
self.client = client
self.mapper = mapper
}
func downloadFruit(location: Location, season: Season, successHandler: (([ Fruit ]) -> Void)?, failureHandler: ((NSError) -> Void)?)
{
client.getFruitsForRegionIdentifier(location.identifier,
seasonIdentifier: season.identifier,
successHandler: { objectNotation in
if let instances = objectNotation["fruit_data"] as? [ ObjectNotation ] {
do {
let fruits = try instances.map(self.mapper.fruit)
successHandler?(fruits)
}
catch let error as NSError {
failureHandler?(error)
}
}
else {
failureHandler?(NSError(domain: "com.fruit.downloader", code: 1000, userInfo: nil))
}
},
failureHandler: failureHandler)
}
private let client: APIClient
private let mapper: FruitMapper
}
class TrafficDownloader {
init(client: APIClient, mapper: TrafficMapper)
{
self.client = client
self.mapper = mapper
}
func downloadTraffic(location: Location, season: Season, successHandler: (([ Traffic ]) -> Void)?, failureHandler: ((NSError) -> Void)?)
{
client.getHistoricalTrafficReportForRegionIdentifier(location.identifier,
seasonIdentifier: season.identifier,
successHandler: { objectNotation in
if let instances = objectNotation["data"] as? [ ObjectNotation ] {
do {
let trafficReport = try instances.map(self.mapper.traffic)
successHandler?(trafficReport)
}
catch let error as NSError {
failureHandler?(error)
}
}
else {
failureHandler?(NSError(domain: "com.traffic.downloader", code: 1000, userInfo: nil))
}
},
failureHandler: failureHandler)
}
private let client: APIClient
private let mapper: TrafficMapper
}
class WildlifeDownloader {
init(client: APIClient, mapper: WildlifeMapper)
{
self.client = client
self.mapper = mapper
}
func downloadWildlife(location: Location, successHandler: (([ Wildlife ]) -> Void)?, failureHandler: ((NSError) -> Void)?)
{
client.getWildlifeForRegionIdentifier(location.identifier,
successHandler: { objectNotation in
if let instances = objectNotation["content"] as? [ ObjectNotation ] {
do {
let wildlife = try instances.map(self.mapper.wildlife)
successHandler?(wildlife)
}
catch let error as NSError {
failureHandler?(error)
}
}
else {
failureHandler?(NSError(domain: "com.wildlife.downloader", code: 1000, userInfo: nil))
}
},
failureHandler: failureHandler)
}
private let client: APIClient
private let mapper: WildlifeMapper
}
好的,这看起来适合于下载程序类,它是T
上的泛型类。好的,现在有一些紧迫的问题:
ObjectNotation
中摸索一个特殊的键Mappable
。对于许多不同的类型,可映射协议需要是通用的,因此我们可以通过使用关联的类型来实现这一点
protocol MapperType {
typealias ObjectType
func object(objectNotation: ObjectNotation) throws -> ObjectType
}
2:对象表示法操作
嗯,我真的不确定有什么好办法来处理这件事。但是,我可以声明另一个协议Mappable
,并使我的模型对象一致
protocol Mappable {
static func objectNotationRoot() -> String
}
3:下载程序参数
一些下载者选择一个位置
和一个季节
另一个只选择位置
。通用下载程序可以在不需要时同时使用这两个实例并丢弃Season
实例,但这感觉像是泄漏的抽象
4:根据类型调用不同的方法
好吧,现在我真的被卡住了。我如何打电话给客户?检查T
的类型,并据此做出决定,感觉真的很恶心。类型信息在downloader中,客户端不获取或使用此信息。我应该认为我需要在这些层之间插入一些东西吗
下面是我的半生不熟的通用下载程序的样子:
class Downloader<T: Mappable, U: MapperType where U.ObjectType == T> {
init(client: APIClient, mapper: U)
{
self.client = client
self.mapper = mapper
}
func download(location: Location, season: Season, successHandler: (([ T ]) -> Void)?, failureHandler: ((NSError) -> Void)?)
{
client._________(location.identifier,
seasonIdentifier: season.identifier,
successHandler: { objectNotation in
if let instances = objectNotation[T.objectNotationRoot()] as? [ ObjectNotation ] {
do {
let objects = try instances.map(self.mapper.object)
successHandler?(objects)
}
catch let error as NSError {
failureHandler?(error)
}
}
else {
failureHandler?(NSError(domain: "com.fruit.downloader", code: 1000, userInfo: nil))
}
},
failureHandler: failureHandler)
}
private let client: APIClient
private let mapper: U
}
类下载程序{
init(客户端:APIClient,映射程序:U)
{
self.client=client
self.mapper=mapper
}
func下载(位置:位置,季节:季节,successHandler:(([T])->Void)?,failureHandler:((N错误)->Void)?)
{
客户端.uuuuuuuuuuuuuuuuuuu(location.identifier,
seasonIdentifier:season.identifier,
successHandler:{中的objectNotation
如果let instances=objectNotation[T.objectNotationRoot()]作为?[objectNotation]{
做{
let objects=try instances.map(self.mapper.object)
successHandler?(对象)
}
将let错误捕获为NSError{
failureHandler?(错误)
}
}
否则{
failureHandler?(N错误(域:“com.fruit.downloader”,代码:1000,用户信息:nil))
}
},
failureHandler:failureHandler)
}
私人出租客户:APIClient
私人出租映射器:U
}
您可以这样做,但实际上并不需要所有协议。一个结构就足够了,还有一些函数和“一个奇怪的技巧”
“一个奇怪的技巧”是,方法实际上是以对象作为第一个参数的curried函数。鉴于此:
struct Foo {
func bar() {}
}
有一个函数,Foo.bar(self:Foo)
返回一个函数()->Void
。这可能还不太合理,但我们稍后会用到它
首先,我们的下载程序
(当前设计的)需要四样东西:一个客户端、一种获取东西的方法、一种找到树顶部的方法,以及一种将您找到的东西映射到对象的方法。因此,我们提出:
struct Downloader<ObjectType> {
let client: APIClient
let fetcher: Fetcher
let rootKey: String
let mapper: (ObjectNotation) throws -> ObjectType
}
什么样的函数具有这种类型?嗯,APIClient.getFruitsForRegionIdentifier
就是这个类型(我就是从那里复制的)。所以我们可以将其作为获取程序传递
有了这些工具,下载
非常简单:
func download(location: Location, season: Season, successHandler: (([ ObjectType ]) -> Void)?, failureHandler: ((NSError) -> Void)?) {
fetcher(client)(
locationIdentifier: location.identifier,
seasonIdentifier: season.identifier,
successHandler: self.successWrapper(successHandler: successHandler, failureHandler: failureHandler),
failureHandler: failureHandler)
}
为了让它更容易阅读,我把那个成功的障碍拖了出来。它接受一个successHandler和一个failureHandler,并返回一个新的成功处理程序
func successWrapper(successHandler successHandler: (([ ObjectType ]) -> Void)?, failureHandler: ((NSError) -> Void)?)
-> ((objectNotation: ObjectNotation) -> Void) {
return { objectNotation in
if let instances = objectNotation[self.rootKey] as? [ ObjectNotation ] {
do {
let objects = try instances.map(self.mapper)
successHandler?(objects)
}
catch let error as NSError {
failureHandler?(error)
}
}
else {
failureHandler?(NSError(domain: "com.fruit.downloader", code: 1000, userInfo: nil))
}
}
}
好的,那里有很多东西。我们如何使用它?我们可以创建一个水果下载器,如下所示:
let fruitDownloader = Downloader<Fruit>(
client: APIClient(baseURL: NSURL()),
fetcher: APIClient.getFruitsForRegionIdentifier, // <- That curried function we talked about
rootKey: "fruit_data",
mapper: simpleMapper("fruit")
)
我们差不多完成了。这里的关键是高阶函数。接受函数并返回新函数的函数
使用高阶函数,您可能可以简化其中的更多内容。查看APIClient
,不清楚为什么我们真的需要所有这些不同的方法。看起来唯一不同的是URL模板。也许我们只需要一个函数,并将其连接到下载程序
你可能会注意到,我们并不是真正的夏娃
let fruitDownloader = Downloader<Fruit>(
client: APIClient(baseURL: NSURL()),
fetcher: APIClient.getFruitsForRegionIdentifier, // <- That curried function we talked about
rootKey: "fruit_data",
mapper: simpleMapper("fruit")
)
extension String: ErrorType {} // For sloppy errors
func simpleMapper<T>(key: String) -> (ObjectNotation) throws -> T {
return { objectNotation in
guard let value = objectNotation[key] as? T else {
throw "Could not!" // FIXME
}
return value
}
}