【问题标题】:What is the best way to map REST URL Patterns to Model Objects for the Siesta framework?将 REST URL 模式映射到 Siesta 框架的模型对象的最佳方法是什么?
【发布时间】:2016-02-06 20:03:42
【问题描述】:

我想使用 ResponseTransformer(或其中的一系列)自动将我的对象模型类映射到从 Siesta 服务返回的响应,以便我的 Siesta 资源是我的模型类的实例。我有一个类的工作实现,但在为每种类型的资源(模型)构建单独的 ResponseTransformer 之前,我想知道是否有更安全、更智能或更有效的方法来做到这一点。

这是一个示例模型类:

import SwiftyJSON

class Challenge {
    var id:String?
    var name:String?

    init(fromDictionary:JSON) {
        if let challengeId = fromDictionary["id"].int {
            self.id = String(challengeId)
        }
        self.name = fromDictionary["name"].string
    }
}

extension Challenge {

    class func parseChallengeList(fromJSON:JSON) -> [Challenge] {
        var list = [Challenge]()

        switch fromJSON.type {
        case .Array:
            for itemDictionary in fromJSON.array! {
                let item = Challenge(fromDictionary: itemDictionary)
                list.append(item)
            }
        case .Dictionary:
            list.append(Challenge(fromDictionary: fromJSON))
        default: break
        }

        return list
    }
}

这是我构建的 ResponseTransformer,用于映射来自任何端点的响应,该端点返回此模型类型的集合或此模型类型的单个实例:

public func ChallengeListTransformer(transformErrors: Bool = true) -> ResponseTransformer {
    return ResponseContentTransformer(transformErrors: transformErrors)
        {
            (content: NSJSONConvertible, entity: Entity) throws -> [Challenge] in        
            let itemJSON = JSON(content)        
            return Challenge.parseChallengeList(itemJSON)
    }
}

最后,这是我在配置 Siesta 服务时所做的 URL 模式映射:

class _GFSFAPI: Service {

    ...

    configure("/Challenge/*")    { $0.config.responseTransformers.add(ChallengeListTransformer()) }
}

我计划为每种模型类型构建一个单独的 ResponseTransformer,然后将每个 URL 模式单独映射到该转换器。这是最好的方法吗?顺便说一句,我对新的 Siesta 框架感到非常兴奋。我喜欢面向资源的 REST 网络库的想法。

【问题讨论】:

  • 直到现在我还没有找到比手动映射每个模型更好的方法。

标签: ios swift rest siesta siesta-swift


【解决方案1】:

你的方法很可靠!你基本上已经明白了。您可以采取一些措施来简化转换器。

大图

听起来您已经掌握了这种权衡,但对于找到此答案的其他人……您有两种通用方法可供选择:

  1. 在 Siesta 观察者中构​​建模型对象,或
  2. 在转换器中构建您的模型对象。

选项 1 更容易设置 - 只需在现场制作模型,就完成了!

func resourceChanged(resource: Resource, event: ResourceEvent) {
    let challenges = Challenge.parseChallengeList(
        JSON(resource.latestData?.jsonDict))
    ...
}

这适用于许多项目。但是,它也有缺点:

  • 选项 1 为每个事件乘以每个观察者实例化一个新模型对象;选项 2 仅根据“新数据”响应实例化模型对象。
  • 选项 1 中没有中心位置来跟踪哪些路由映射到哪些模型对象。
  • 如果服务器没有返回您期望的内容类型,选项 2 会产生更好的错误。

如果(且仅当)项目小且模型轻量级时,我更喜欢选项 1。

您将在Pipeline section of the user guide 中找到有关选项 2 的大量文档。这是一个快速概述。

使用configureTransformer

您可以使用configureTransformer(...) 简化您的ChallengeListTransformer

configureTransformer("/Challenge/*") {
    (content: NSJSONConvertible, entity: Entity) throws -> [Challenge] in        
    let itemJSON = JSON(content)        
    return Challenge.parseChallengeList(itemJSON)
}

但是等等,还有更多!观看 Swift 为您提供的惊人的类型推理切片和骰子:

configureTransformer("/Challenge/*") {
    Challenge.parseChallengeList(
        JSON($0.content as NSJSONConvertible))
}

(请注意,configureTransformertransformErrors 设置为 false。这几乎肯定是您想要的……除非您的服务器发送 JSON“挑战”模型作为错误响应的主体!transformErrors 选项通常只有用于与内容类型相关联的通用转换器,例如文本和 JSON 解析,而不是附加到路由的转换器。)

全球 SwiftyJSON 转换器

如果您使用的是 SwiftyJSON(我也喜欢它,顺便说一句),那么您可以将它整体应用于所有 JSON 响应:

private let SwiftyJSONTransformer =
    ResponseContentTransformer(skipWhenEntityMatchesOutputType: false)
        { JSON($0.content as AnyObject) }

……然后:

service.configure {
    $0.config.responseTransformers.add(
        SwiftyJSONTransformer, contentTypes: ["*/json"])
}

...这进一步简化了每个路由的内容转换器:

configureTransformer("/Challenge/*") {
    Challenge.parseChallengeList($0.content)
}

请注意,Swift 的类型推断告诉 Siesta 这个转换器需要一个 JSON 结构作为输入,如果它没有以这种方式从转换器管道中出来,Siesta 会使用它来将其标记为错误。与 JSON 相关的转换器都附加到 */json 内容类型,所以如果服务器返回任何意外的东西,你的观察者会看到一个很好的整洁的“嘿,这不是 JSON!”错误。

See the user guide 了解有关这一切的更深入信息。

从资源中获取模型

就目前的 Siesta API 而言,您需要降低模型的内容:

func resourceChanged(resource: Resource, event: ResourceEvent) {
    let challenges = resource.latestData?.content as? [Challenge]
    ...
}

或者,您可以使用TypedContentAccessors 协议扩展方法同时进行转换并在数据尚不存在时获取默认值转换失败。例如,如果没有挑战,则此代码默认为空数组:

func resourceChanged(resource: Resource, event: ResourceEvent) {
    let challenges = resource.typedContent(ifNone: [Challenge]())
    ...
}

Siesta 目前不提供将模型类型绑定到资源的静态类型化方式;你必须做演员。这是因为 Swift 类型系统的限制阻止了泛型资源类型(例如Resource<[Challenge]>)在实践中的可操作性。希望 Swift 3 能够解决这些问题,以便将来的 Siesta 版本能够提供这些问题。 更新:必要的泛型改进已针对 Swift 3 进行,希望在 Swift 4 中......

【讨论】:

  • 感谢 Paul 继续推进框架,并花时间分享您的优化。
  • Paul,我缺少一个示例,说明您如何以及在何处实际访问转换后的资源。它一定在外面,但我在任何地方都没有看到它记录在案……
  • @Rhuantavan:更新答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-02
  • 1970-01-01
  • 2010-09-15
  • 1970-01-01
  • 2019-04-16
  • 1970-01-01
相关资源
最近更新 更多