关系 0.0.4

关系 0.0.4

测试已测试
语言语言 SwiftSwift
许可证 MIT
发布上次发布2017年4月
SwiftSwift 版3.0
SPM支持 SPM

Anton Bronnikov 维护。



关系 0.0.4

  • Anton Bronnikov

关系

Swift辅助框架,用于简化对象之间的关系。

目前支持的以下类型关系:

向下滚动以查看安装指南

弱,1:1

在两个对象之间建立弱一对一对关系的促进者是泛型类Weak1to1Relation

考虑以下示例

import Relations

class Tile : CustomStringConvertible {

    private var address: UnsafeMutableRawPointer { 
        return Unmanaged.passUnretained(self).toOpaque() 
    }

    var description: String { 
        return "tile.\(address)" 
    }

    private (set) var _tileForComponent: Weak1to1Relation<Tile, Component>! = nil

    var component: Component? {
        get { return _tileForComponent.counterpart?.side }
        set { _tileForComponent.relate(to: newValue?._componentForTile) }
    }

    init() {
        _tileForComponent = Weak1to1Relation(side: self)
    }

}
class Component : CustomStringConvertible {

    private var address: UnsafeMutableRawPointer { 
        return Unmanaged.passUnretained(self).toOpaque() 
    }

    var description: String { 
        return "component.\(address)" 
    }

    private (set) var _componentForTile: Weak1to1Relation<Component, Tile>! = nil

    var tile: Tile? {
        get { return _componentForTile.counterpart?.side }
        set { _componentForTile.relate(to: newValue?._tileForComponent) }
    }

    init() {
        _componentForTile = Weak1to1Relation(side: self, { [unowned self] in 
            self.tileDidChange()
        })
    }

    private func tileDidChange() {
        print("[\(self)].[tile] -> [\(tile?.description ?? "nil")]")
    }

}

关系两边的自动更新

现在,ComponentTile类型的对象可以通过同步更新两端的弱引用链接相互关联。

例如,以下代码不会导致断言失败:

let tile = Tile()
let component = Component()

tile.component = component

assert(component.tile === tile, 
    "The other side of the relation is automatically set")

此外,以下也不会失败:

let tile2 = Tile()

component.tile = tile2

assert(tile2.component === component, 
    "Yet again, the other side of relation is also set")
assert(tile.component == nil, 
    "And the object that was on the other side previously is updated")

更新也会在关系的一边被销毁时触发

var component2: Component! = Component()

component2.tile = tile

assert(tile.component === component2, 
    "Both sides of the relation are set")

component2 = nil // Dispose component2

assert(tile.component == nil, 
    "Consequently, the tile's side of the relation is set to nil")

通知

可以注册闭包以接收关系更新的通知。

例如,请注意上述示例中这两种初始化之间的差异:

_tileForComponent = Weak1to1Relation(side: self)

vs.

_componentForTile = Weak1to1Relation(side: self, { [unowned self] in
    self.tileDidChange()
})

后一种将自身方法注册为通知处理程序在关系每次更改时触发。此类通知将在关系更改的所有更改全部完成后运行,因此您永远不会陷在中间。

注意:请确保使用[unowned self]捕获列表来避免不可避免的强引用循环。

弱,1:N

泛型类Weak1toNRelationWeakNto1Relation配对,用于促进弱1:N关系管理。

用法与1:1情况类似

class File : CustomStringConvertible {

    let name: String
    var description: String { return "\"\(name)\"" }

    private (set) var _fileInFolder: WeakNto1Relation<File, Folder>! = nil

    var folder: Folder? {
        get { return _fileInFolder.counterpart?.side }
        set { _fileInFolder.relate(to: newValue?._folderForFiles) }
    }

    init(name: String) {
        self.name = name
        _fileInFolder = WeakNto1Relation(side: self)
    }

}
class Folder : CustomStringConvertible {

    let name: String
    var description: String { return "\"\(name)\"" }

    private (set) var _folderForFiles: Weak1toNRelation<Folder, File>! = nil

    var files: [File] {
        return _folderForFiles.counterparts
            .map({ $0.side })
            .sorted(by: { $0.name < $1.name })
    }

    private func filesDidChange() {
        print("Files in \(self)")
        files.forEach({
            print("- \($0)")
        })
    }

    init(name: String) {
        self.name = name
        _folderForFiles = Weak1toNRelation(side: self, { [unowned self] in
            self.filesDidChange()
        })
    }

    func insert(file: File) {
        _folderForFiles.relate(to: file._fileInFolder)
    }

    func insert(files: File...) {
        _folderForFiles.relate(to: files.map({ $0._fileInFolder }))
    }

    func remove(file: File) {
        _folderForFiles.unrelate(from: file._fileInFolder)
    }

    func remove(files: File...) {
        _folderForFiles.unrelate(from: files.map({ $0._fileInFolder }))
    }

}

…然后

let etc = Folder(name: "etc")
let hosts = File(name: "hosts")
let services = File(name: "services")

etc.insert(files: hosts, services)

索引,弱,1:N

IndexedWeak1toNRelationIndexedWeakNto1Relation配对的辅助类促进了索引弱1:N关系的建立。

简而言之,这与上面提到的1:N关系几乎相同。区别在于,N侧的对象使用了索引值(参数类型泛型列表中的第3种类型)进行注册。这个值可以在关系的单值侧用来查找特定的多值对象。如果关系中新插入的多值对象与已有对象索引相同,将会有一个踢除行为。在这种情况下,之前的对象会被踢出。

为了快速说明,以下是一个Folder/File的例子,可以重写为:

class File {

    let name: String

    private (set) var _fileInFolder: IndexedWeakNto1Relation<File, Folder, String>! = nil

    init(name: String) {
        self.name = name
        _fileInFolder = IndexedWeakNto1Relation(side: self, index: name)
    }

}

class Folder {

    private (set) var _folderForFiles: IndexedWeak1toNRelation<Folder, File, String>! = nil

    var fileNames: [String] {
        return _folderForFiles.indices.sorted()
    }

    init(name: String) {
        _folderForFiles = IndexedWeak1toNRelation(side: self)
    }

    func getFile(withName name: String) -> File? {
        return _folderForFiles.lookup(index: name)?.side
    }

}

通知

关于1:N关系的重要特性注意点是其通知,如果注册了的话,总是只触发一次,并且是在所有更改完成后。所以,例如,如果你将一批N侧对象从一个1侧对象转移到了另一个对象,那么每个对象只会收到一次通知。

安装

Swift包管理器

将以下依赖项添加到你的Package.swift

.Package(url: "https://github.com/courteouselk/Relations.git", majorVersion: 0)