Skip to content

Commit 6d4bb89

Browse files
committed
Documentation updates
1 parent b4f62bb commit 6d4bb89

3 files changed

Lines changed: 58 additions & 24 deletions

File tree

README.md

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@ Helpful tools for your Core Data
44

55
## What Is It?
66

7-
Core Data is a mature and powerful tool, but it takes plenty of time to get the hang of its intricacies. I’ve been using it heavily for years, and building a bunch of helper tools to make my life easier. RCDataKit is a bunch of my personal tools that have been cleaned up enough for me to feel they’re worth sharing with the world.
7+
Core Data is a mature and powerful tool, but it takes some time to get used to its intricacies. I’ve been using it heavily for years, and building a bunch of helper tools to make my life easier. RCDataKit is the latest version of those tools.
88

9-
There are a lot of tools out there (CoreStore, PredicateKit) that are a lot more than what I’ve put here, and take most of the scary, non-type-safe work out of your hands. RCDataKit is intended to be a helper for your Core Data implementation, not a massive wrapper around it. It won’t prevent you from breaking things if you don’t understand Core Data, but hopefully it will help you with that understanding if you are interested in learning more. I’m still learning, and I’d love if my experience can help you, too.
9+
There are really amazing tools already out there for Core Data ([CoreStore](https://github.com/JohnEstropia/CoreStore), [PredicateKit](https://github.com/ftchirou/PredicateKit) to name a few of my favorites) that take most of the scary, non-type-safe work out of your hands. RCDataKit isn't intended to replace your use of Core Data tools as much as those libraries, though. It won’t prevent you from breaking things if you don’t understand Core Data, but hopefully it will help you with that understanding if you are interested in learning more.
1010

1111
## Why Bother With Core Data?
1212

13-
Core Data stinks! SwiftData is newer and the way of the future! Or Realm, or all kinds of other options!
14-
15-
Yes! Core Data is old and can be annoying to work with, and maybe will be replaced permanently by SwiftData some day. But for now, I’m still using Core Data in my own projects because:
13+
Sure. Core Data is old and can be annoying to work with, and maybe will be replaced permanently by SwiftData some day. But for now, I’m still using Core Data in my own projects because:
1614

1715
1. It’s a native Apple tool, which means it will probably stay available to anyone writing iOS/MacOS apps for a long time.
1816
2. SwiftData, while very exciting and new, is seriously lacking in some finer controls that I’ve gotten used to with Core Data.
@@ -23,7 +21,7 @@ I still assume SwiftData will be the way of the future, but until I can make it
2321

2422
- [Stack Helpers](#stack-helpers)
2523
- [TransactionAuthor Protocol](#transactionauthor-protocol)
26-
- [CoreDataStack Protocol](#coredatastack-protocol)
24+
- [DataStack Protocol](#datastack-protocol)
2725
- [Model Helpers](#model-helpers)
2826
- [PersistentStoreVersion Protocol](#persistentstoreversion-protocol)
2927
- [PersistentHistoryTracker Actor](#persistenthistorytracker-actor)
@@ -32,6 +30,7 @@ I still assume SwiftData will be the way of the future, but until I can make it
3230
- [Persistable Protocol](#persistable-protocol)
3331
- [NSManagedObjectContext Helpers](#nsmanagedobjectcontext-helpers)
3432
- [NSFetchRequest Helpers](#nsfetchrequest-helpers)
33+
- [NSPredicate Helpers](#nspredicate-helpers)
3534

3635
## Stack Helpers:
3736

@@ -56,26 +55,30 @@ enum Authors: String, TransactionAuthor {
5655

5756
The idea here is that you’ll want one case for each of your main-thread contexts that access your data store (view context from your app), and as many named background contexts as you like to help keep track of who or what is writing to your store.
5857

59-
### CoreDataStack Protocol
58+
### DataStack Protocol
6059

61-
Another simple protocol. This one wraps your `NSPersistentContainer` (and I’ll add some pre-made implementations eventually), and assigns a `TransactionAuthor` type to it so you can get preconfigured contexts from the container.
60+
Another simple protocol, this one wraps your `NSPersistentContainer` and assigns a `TransactionAuthor` type to it so you can get preconfigured contexts from the container.
6261

6362
```swift
64-
let myStack: CoreDataStack // has associatedType `Authors`
63+
let myStack: DataStack // has associatedType `Authors`
6564

66-
// Get the viewContext -- a NSManagedObjectContext with
65+
// Get the viewContext -- a NSManagedObjectContext where
6766
// transactionAuthor == myStack.viewContextID.name
6867
let viewContext = myStack.viewContext
6968

7069
// Get a background context with transactionAuthor == localEditing.name
7170
let bgContext = myStack.backgroundContext(author: .localEditing)
7271
```
7372

73+
There are a few implementations of `DataStack` available here:
74+
- `PreviewStack` is an in-memory store for use in SwiftUI previews or other non-persisted environments.
75+
- `SingleStoreStack` is a SQLite-backed stack with a single store, and initialization options for Persistent History Tracking and Staged Migrations.
76+
7477
## Model Helpers:
7578

7679
### PersistentStoreVersion Protocol
7780

78-
Migrating your Model from one version to the next used to be such a pain— Lightweight Migrations are easy enough, but Custom Migrations not so much. Setting up your environment to perform either was confusing, and [Apple’s documentation](https://developer.apple.com/documentation/coredata/staged_migrations) is even sparser than for the old migrations system. But now we have [Staged Migrations](https://developer.apple.com/videos/play/wwdc2022/10120/)! Unfortunately, Apple’d documentation is practically nonexistent once again. Thanks to [Pol Piela](https://www.polpiella.dev/staged-migrations) and [FatBobMan](https://fatbobman.com/en/posts/what-s-new-in-core-data-in-wwdc23/) for picking up the slack.
81+
Migrating your Model from one version to the next used to be such a pain— Lightweight Migrations are easy enough, but Custom Migrations not so much. Setting up your environment to perform either was confusing, and [Apple’s documentation](https://developer.apple.com/documentation/coredata/staged_migrations) is even sparser than for the old migrations system. But now we have [Staged Migrations](https://developer.apple.com/videos/play/wwdc2022/10120/)! Unfortunately, Apple’s documentation is practically nonexistent once again. Thanks to [Pol Piela](https://www.polpiella.dev/staged-migrations) and [FatBobMan](https://fatbobman.com/en/posts/what-s-new-in-core-data-in-wwdc23/) for picking up the slack.
7982

8083
With the `PersistentStoreVersion` protocol, I’ve built a bunch of useful helpers for getting your migrations set up.
8184

@@ -132,6 +135,14 @@ container.persistentStoreDescriptions
132135
forKey: NSPersistentStoreStagedMigrationManagerOptionKey)
133136
```
134137

138+
- Alternately, just pass your `PersistentStoreVersion` into the initializer for `SingleStoreStack`:
139+
140+
```swift
141+
let stack = try SingleStoreStack(
142+
versionKey: ModelVersions.self,
143+
mainAuthor: Authors.iOSViewContext)
144+
```
145+
135146
### PersistentHistoryTracker Actor
136147

137148
Persistent History Tracking can be really confusing. `PersistentHistoryTracker` is an actor that attaches to your `NSPersistentContainer` in order to manage all that tracking for you. It borrows very heavily from tutorials and projects by [Antoine Van Der Lee](https://www.avanderlee.com/swift/persistent-history-tracking-core-data/) and [FatBobMan](https://fatbobman.com/en/posts/persistenthistorytracking/) (especially FatBobMan’s [PersistentHistoryTrackingKit](https://github.com/fatbobman/PersistentHistoryTrackingKit/tree/main), thank you!), with some added helpers based on the `TransactionAuthor` protocol.
@@ -145,7 +156,16 @@ let tracker = PersistentHistoryTracker(
145156
tracker.startMonitoring()
146157
```
147158

148-
I’ll work on adding more relevant helpers that deal with persistent history transactions in the future.
159+
You can also enable tracking in `SingleStoreStack` by passing in an instance of `PersistentHistoryTrackingOptions` to the initializer:
160+
161+
```swift
162+
let stack = try SingleStoreStack(
163+
versionKey: ModelVersions.self,
164+
mainAuthor: Authors.iOSViewContext,
165+
persistentHistoryOptions: .init())
166+
167+
stack.historyTracker?.startMonitoring()
168+
```
149169

150170
## CRUD Helpers:
151171

@@ -157,11 +177,11 @@ Make your `NSManagedObject` subclass conform to the `Updatable` protocol to get
157177
let rc = Person(...)
158178

159179
rc.update(\.age, value: 15) // now I'm 15 years old!
160-
rc.updateIfAvailable(\.age, value: Optional<Int>.none) // still 15, not nil!
180+
rc.updateIfAvailable(\.age, value: nil) // still 15, not nil!
161181
rc.update(\.age, value: 16, minimumChange: 2) // still 15, because I only want to age in 2-year increments.
162182

163183
rc.add(\.friend, relation: dan) // dan is now my friend
164-
rc.add(\.friend, relation: Optional<Person>.none) // nothing happens, because nobody's there.
184+
rc.add(\.friend, relation: nil) // nothing happens, because nobody's there.
165185
rc.remove(\.friend, relation: dan) // dan's not my friend anymore.
166186
```
167187

@@ -235,7 +255,27 @@ try context.removeInstances(of: Person.self, matching: someNSPredicate)
235255

236256
And some functions in extensions of `NSPredicate` , `NSFetchRequest`, `NSSortDescriptor`
237257

238-
- `NSPredicates` can be compounded with `&&`, `||`, and `!=`
258+
- `NSPredicate`:
259+
- Combine with `&&`, `||`, and `!=`.
260+
- Create with KeyPath comparators like `==`, `>`, `!=` and so on.
261+
```swift
262+
let olderThanDirt = \Person.age > 1000
263+
let notFred = \Person.name != "Fred"
264+
```
265+
- Also create with `in` or `between` for number properties:
266+
```swift
267+
// note the parentheses around the KeyPath
268+
let isTeenager = (\Person.age).between(13, and: 19)
269+
let isOddTeen = (\Person.age).in([11, 13, 15, 17, 19])
270+
```
271+
- For `String` properties, add options to the predicate like so:
272+
```swift
273+
let definitelyNotFred = \(Person.name).notEqual(
274+
to: "Fred",
275+
options: [.caseInsensitive, .diacriticInsensitive])
276+
```
277+
- For a more robust, type-safe `NSPredicate` system, check out [PredicateKit](https://github.com/ftchirou/PredicateKit)
278+
239279
- `NSFetchRequest`s can be built with chaining methods like:
240280

241281
```swift

Sources/RCDataKit/Data Stack/DataStack Implementations/PreviewStack.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public struct PreviewStack: DataStack {
2121
public let container: NSPersistentContainer
2222

2323
public init(
24-
bundle: Bundle,
24+
bundle: Bundle = .main,
2525
modelName: String
2626
) {
2727
self.container = NSPersistentContainer(bundle: bundle, modelName: modelName, versionName: nil)

Tests/RCDataKitTests/Core Data Test Setup/PersistentStoreTest.swift

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,8 @@ class PersistentStoreTest: XCTestCase {
3737
// MARK: - Persistent Container Factories
3838

3939
static func makeContainer() throws -> NSPersistentContainer {
40-
let description = NSPersistentStoreDescription()
41-
description.type = NSInMemoryStoreType
42-
43-
let container = NSPersistentContainer(name: modelName, managedObjectModel: mergedModel)
44-
container.persistentStoreDescriptions = [description]
45-
46-
try container.loadStores()
47-
return container
40+
let stack = PreviewStack(bundle: .module, modelName: modelName)
41+
return stack.container
4842
}
4943

5044
static func makeContainerWithPersistentTracking() throws -> NSPersistentContainer {

0 commit comments

Comments
 (0)