swift - 在 Swift 中建模部分加载的相关对象

标签 swift

我正在重做一个模型库,希望比我聪明的人可以帮助我解决我之前实现中的一个烦恼。

假设我有一系列这样的相关模型:

class Pond {
  let pk: Int
  let depth: Int
}
class Frog {
  let pk: Int
  let name: String
  let pond: Pond
}

对象是从商店加载的。假设我捡到一只 Frog 。它有一个主键,它属于一个池塘。我会得到这样的 JSON:

{"pk": 42, "name": "Kermit", "pond_pk": 1}

所以,我知道 Frog 的一切,我知道它属于哪个池塘,但我不知道(也不需要知道)那个池塘的深度。这意味着我还不能实例化 Pond 对象。

以下是我考虑过的解决方案:

1) 将每个关系属性加倍。如:

class Frog {
  let pond: Pond?
  let pondPk: Int
}

这看起来一点都不好。

2) 与上面类似,但是创建一个 Optional-ish 结构来处理关系。

struct Relationship {
  let pk: Int
  let object: AnyObject?
}
class Frog {
  let pond: Relationship
}

然后测试 frog.pond.object 但这非常冗长。

3) 用“ stub ”版本将每个类加倍,这样关系就可以是 PondStub 或 Pond(如果已满载)。

class PondStub {
  let pk: Int
}
class Pond: PondStub {
  let depth: Int
}
class FrogStub {
  let pk: Int
}
class Frog: FrogStub {
  let name: String
  let pond: PondStub
}

4) 放弃并将每个非 pk 属性实现为隐式展开的 Optional 并在我不确定是否已加载相关项时检查 nil对象。

class Pond {
  let pk: Int
  let depth: Int!
}

这是我自 Swift 1 以来所做的事情。它奏效了,只是我失去了 Optional 类的所有好处。这就像我在与 Swift 作斗争,而不是正确地使用它。

任何更好的想法将不胜感激。

PS:这将用于 Linux 上的 Swift。

编辑:我一夜之间有了另一个想法,下面 dfri 的评论对此表示赞同(这应该是一个答案!)我喜欢它的一切,除了很难模拟对象之间的关系,如果它们不是' 保存到 Store(例如,在创建新对象时)。

// The model objects are still very simple.
struct Pond {
    let pk: Int?
    let depth: Int
}
struct Frog {
    let pk: Int?
    let name: String
    let pondPk: Int?

}

// There is a separate Store module which co-ordinates the objects
// and their relationships.
protocol Storable {
    static var hash: String { get }
    var pk: Int? { get }
}
extension Storable {
    var hash: String? {
        get {
            guard let pk = self.pk else { return nil }
            return self.dynamicType.hash + String(pk)
        }
    }
}
class Store {
    var contents: [String: Storable] = [:]
    func add(obj: Storable) {
        guard let hash = obj.hash else { return }
        contents[hash] = obj
    }
    func fetch(type: Storable.Type, withPk pk: Int) -> Storable? {
        let hash = type.hash + String(pk)
        return contents[hash]
    }
}
// This module also handles easy lookups between the models.
extension Pond: Storable {
    static let hash = "Pond"
}
extension Frog: Storable {
    static let hash = "Frog"
    func pond(fromStore store: Store) -> Pond? {
        guard let pk = pondPk else { return nil }
        return store.fetch(Pond.self, withPk: pk) as? Pond
    }
}

// Example usage.
let store = Store()
let pond = Pond(pk: 1, depth: 4)
store.add(pond)
let frog = Frog(pk: 1, name: "Kermit", pondPk: pond.pk!)
store.add(frog)
if let fetchedPond = frog.pond(fromStore: store) {
    print(fetchedPond.depth) // prints 4
}

最佳答案

一些笔记(NX)关于您的(好的)解决方案如下。

在上面编辑的示例用法 ( frog.pond(fromStore: store) ) 中,您使用 Frogstore 中具有自身副本 的实例实例。即

// Example usage.
let store = Store()
let pond = Pond(pk: 1, depth: 4) // (N1)
store.add(pond)
let frog = Frog(pk: 1, name: "Kermit", pondPk: pond.pk!)
store.add(frog)
if let fetchedPond = frog.pond(fromStore: store) { // (N2)
    print(fetchedPond.depth) // prints 4
}

注意事项:

  • (N1) : 添加 pond副本store .可能更好地初始化并将池塘一步添加到 store : store.add(Pond(pk: 1, depth: 4)) .
  • (N2) : 在这里你使用一个 frog 的实例store 中存在副本(赋值) ,但不使用 store 中的实际副本.

var frog = Frog(pk: 1, name: "Kermit", pondPk: pond.pk!)
store.add(frog)
frog.name = "Hermit" // <-- will not effect Kermit frog in store

您可以让您的 Frog 成为类类型而不是值类型,或者您可以问问自己 Frog 本身是否真的需要存储(或者这是否只是池塘需要的?)。


我将添加您在上面的编辑中发布的“可存储”解决方案的变体。在此,Frog实例不存储在中央存储中,但可以“本地”使用并访问 frog:s 关联 Pond (存储在中央商店中)通过使用对商店的引用。根据您问题的原始细节,这是可以接受的(“需要访问 PondFrog 实例,但反之则不行”);然而,如果你一定要存储所有不同的东西,这将是一个交易破坏者 Frog您商店中的实例。

如果 Frog 这可能会有用:s 是“动态” 对象,而池塘是“更大的静态实体”,例如;有限数量的池塘将在初始化时添加到中央商店,而 Frog对象将在运行时动态弹出并超出范围。

商店模块

这适用于此替代解决方案以及您上面的解决方案:不需要属性 pk是可选的,因为所有现有 PondFrog实例应始终具有自己的 ID。

protocol Storable {
    static var hash: String { get }
    var pk: Int { get }
}

extension Storable {
    var hash: String {
        get {
            return self.dynamicType.hash + String(pk)
        }
    }
}

class Store {
    var contents: [String: Storable] = [:]
    func add(obj: Storable) {
        contents[obj.hash] = obj
    }
    func fetch(type: Storable.Type, withPk pk: Int) -> Storable? {
        let hash = type.hash + String(pk)
        return contents[hash]
    }
}

池塘和 Frog 类

由于中央商店应该始终被初始化(即使它是空的),我们可以添加一个商店引用到您的 Frog对象(在初始化时)。我们在只有 Pond 的替代解决方案的上下文中提出这一点:s 是可存储的,而 Frog :s 包含对中央商店的引用,通过它,可以巧妙地访问 Pond frog.pond?.depth 池塘的属性(通过可选链接,例如 Frog ) :s pondPk属性(property)。

struct Pond : Storable {
    static let hash = "Pond"
    let pk: Int
    let depth: Int
}

struct Frog {
    let pk: Int
    let name: String
    let pondPk: Int?
    weak var store: Store? // reference to central Store()

    var pond: Pond? {
        guard let pondPk = pondPk else { return nil }
        return store?.fetch(Pond.self, withPk: pondPk) as? Pond
    }
}

示例用法

func printPondDepth(frog: Frog) {
    if let _ = frog.pondPk {
        print(frog.pond?.depth ?? "No pond with id \(frog.pondPk ?? 0) in frog \(frog.name):s associated store")
    }
    else {
        print("No pond id associated with frog \(frog.name)")
    }
}

/* Central store */
let store = Store()

/* Some frogs read prior to any Ponds being added to the store */
let frog = Frog(pk: 1, name: "Kermit", pondPk: 1, store: store)
let anotherFrog = Frog(pk: 2, name: "Mitker", pondPk: 3, store: store)

/* Adding ponds to the store */
store.add(Pond(pk: 1, depth: 4))
store.add(Pond(pk: 4, depth: 25))

/* another frog and some attempts to get the depth of the 
   pond in which a frog possibly resides */
let thirdFrog = Frog(pk: 3, name: "Kerker", pondPk: nil, store: store)

printPondDepth(frog)        /* 4 */
printPondDepth(anotherFrog) /* No pond with id 3 in frog Mitker:s associated store */
printPondDepth(thirdFrog)   /* No pond id associated with frog Kerker */

关于swift - 在 Swift 中建模部分加载的相关对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35100938/

相关文章:

swift - 通过键盘扩展在 UINavigationController 中呈现 subview

ios - 快速重新加载 PickerView 的特定行

ios - 从两侧在 UIView 上绘制对角线

ios - 在 UIView 之间委派 - 将接收者的委托(delegate)放在哪里?

ios - NSLocale NSLocaleCalendar 导致 EXC_BAD_ACCESS

ios - Storyboard:删除派生数据中的文件夹后仍然是 "Unable to write to path"

swift - 如何在 Swift4+ 中作为标准 POST 方法发送数组数组

ios - Swift 中 UIViewcontroller 的实例

ios - 当应用程序被杀死而不在IOS后台运行时如何获取通知有效负载

ios - 我在尝试将 json api 数据解码为可编码类时遇到错误