Slate

Swift 5.3

Core Data 的不可变数据模型

Slate 是一个位于 Core Data 对象图之上的中间件,提供

示例

使用你典型的 Core Data NSManagedObjects

class CoreDataBook: NSManagedObject {
  @NSManaged public var id: UUID
  @NSManaged public var pageCount: Int64
}

Slate 自动生成不可变表示

/* Auto-generated */
struct Book {
  let id: UUID
  let pageCount: Int
}

从只读上下文中查询,该上下文提供 Core Data 模型的这些不可变版本

func fetchBooksWithAtLeast(pageCount: Int, completion: ([Book]) -> Void) {
  slate.queryAsync { readContext in
    // Run queries on a Core Data object graph proxy that returns immutable objects.
    // Slate handles the conversion behind the scenes.
    let books = try readContext[Book.self].filter("pageCount > \(pageCount)").fetch()
    
    // You can now pass `books` around wherever you want in a thread-safe manner.
    // They are fully immutable and thread-safe.    
    completion(books)

  }.catch { error in
    // The optional trailing catch method allows you to batch all try-based calls inside
    // of the transaction (similar to PromiseKit)
    print(error)
  }
}

继续使用 NSManagedObjectContext 进行写入,但在安全的单写/多读队列中操作

func updateBookPageCount(id: UUID, newPageCount: Int) {
  slate.mutateAsync { moc in
    // Mutate NSManagedObjects in a single-writer MOC. Insert/delete/updates are 
    // announced to all registered listeners on completion of the mutation block. 
    if let book = try moc[CoreDataBook.self].filter("id = %@", id).fetchOne() {
      book.pageCount = newPageCount
    }

    // An optional Any? return value is passed along to transaction listeners
    // to help indicate the context of the transaction.
    return ExampleEnum.updatePageCount(id: id)
  }
}

监听事务

class SomeClass: SlateMutationListener {
  func slateMutationHandler(result: SlateMutationResult) {
    // Handle Slate Mutation
  }
}

...
let myClass = SomeClass()
slate.addListener(myClass)

为什么你想要为你的 Core Data 对象图使用不可变数据模型访问模式?

线程安全

不可变模型无法改变。它们可以在后台线程上查询/创建,并在发送到主线程进行 UI 更新之前,用于任何复杂的排序/确定逻辑(因此主线程保持流畅)。不可变模型属性不必同步,可以直接访问。

受保护的快照

不可变模型的作用类似于快照。如果你有多个功能使用相同的底层对象图,你的功能可以防止其他代码在没有严格知情的情况下改变其快照。 这扩展到关系——如果另一个功能删除了对象关系,功能的快照不会改变。相反,你会被通知更改,并可以在准备好时刷新/查询关系。

单向信息流

不可变模型有助于强制执行单向信息流。你不能编写就地“更新”不可变模型的方法。 相反,对象图的突变必须以强制对对象图进行事务更新的方式发生,然后依次向监听器宣布更改,监听器可以以受控方式重新获取其快照。

在 Core Data 之上使用不可变数据模型的缺点是什么?

不再有 Faulting

Core Data 具有延迟加载托管对象(faulting)的能力。这与不可变数据模型的概念相互排斥。 在 Slate 中,所有查询的不可变对象都将被完全加载并存储在内存中。

这意味着如果你的应用程序不断查询/更新数万个托管对象,并且你需要 faulting 来保持效率,那么 Slate 将不是一个好的解决方案。

不再有动态关系

在 Core Data 中,你可以访问托管对象的关系来动态查询相关对象。 在 Slate 中,你必须预先获取这些关系作为不可变对象的数组,因为它们是快照的一部分。 关系不能在 Slate 查询上下文之外获取。

了解此存储库

Slate 不是一个独立的 Cocoapod/Carthage 库。 它是一套代码,你可以选择如何将其集成到你的应用程序中。

SlateLib

在当前的 repo 中,这只包含 Slate 的 Swift + Core Data 实现。未来可能会支持其他语言和底层存储。

整个实现位于一个 swift 文件中。 除了编译速度更快之外,即使你将代码放在顶级应用程序中,这也允许 Slate 使用 fileprivate 来强制执行跨类保护。

你可以简单地将 Slate.swift 放入你的应用程序中,或者创建一个单独的框架并导入它。

SlateGenerator

这是一个单独的应用程序,用于生成 Core Data 模型的不可变版本。 它读取你的 xcdatamodel XML 文件并输出所需的类/结构。 有关详细信息和用法,请查看 SlateGenerator 目录中的 README。

UpdatableListNode

这是一个简单的协议,可以帮助生成更新/删除/移动/重新加载索引,以将一个列表更新到另一个列表。 这主要用于 UITableView/UICollectionView 的 performBatchUpdates 方法,并且由于 NSFetchedResultsController 不能与 Slate 结合使用,因此提供了该协议。