Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/17.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Swift 如何在任意抽象层中使用泛型?_Swift_Generics_Protocols - Fatal编程技术网

Swift 如何在任意抽象层中使用泛型?

Swift 如何在任意抽象层中使用泛型?,swift,generics,protocols,Swift,Generics,Protocols,摘要 我试图理解如何重构代码和使用Swift泛型。我能够跨几个概念上相似的类型识别通用的实现,但我遇到了困难,因为代码构建的层不是泛型的 背景 以下是我对一些我想利用的概念的理解 协议 用于声明接口并具有该接口的许多“具体”实现。这允许您在运行时交换实现 仿制药 当实现相同或相似,但类型不同时,此功能非常有用。似乎典型的示例是交换两个值或map 问题 我有一个客户端类(APIClient),负责创建URL请求。客户机只知道主要数据类型(整数、字符串、数组、JSON等)。客户端类上方有几个类抽象域

摘要 我试图理解如何重构代码和使用Swift泛型。我能够跨几个概念上相似的类型识别通用的实现,但我遇到了困难,因为代码构建的层不是泛型的

背景 以下是我对一些我想利用的概念的理解

协议

用于声明接口并具有该接口的许多“具体”实现。这允许您在运行时交换实现

仿制药

当实现相同或相似,但类型不同时,此功能非常有用。似乎典型的示例是交换两个值或
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
上的泛型类。好的,现在有一些紧迫的问题:

  • 每个downloader类都使用特定于域的映射器
  • 每个下载程序都必须在
    ObjectNotation
    中摸索一个特殊的键
  • 客户端方法的参数略有不同
  • 每个下载程序调用一个特定的客户端方法
  • 让我们依次处理这些问题:

    1:具有关联类型的协议 与其使用特定的映射器下载,不如让它使用类型为
    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
        }
    }