You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+55-15Lines changed: 55 additions & 15 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,15 +4,13 @@ Helpful tools for your Core Data
4
4
5
5
## What Is It?
6
6
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.
8
8
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.
10
10
11
11
## Why Bother With Core Data?
12
12
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:
16
14
17
15
1. It’s a native Apple tool, which means it will probably stay available to anyone writing iOS/MacOS apps for a long time.
18
16
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
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.
58
57
59
-
### CoreDataStack Protocol
58
+
### DataStack Protocol
60
59
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.
62
61
63
62
```swift
64
-
let myStack: CoreDataStack// has associatedType `Authors`
63
+
let myStack: DataStack// has associatedType `Authors`
65
64
66
-
// Get the viewContext -- a NSManagedObjectContext with
65
+
// Get the viewContext -- a NSManagedObjectContext where
// Get a background context with transactionAuthor == localEditing.name
71
70
let bgContext = myStack.backgroundContext(author: .localEditing)
72
71
```
73
72
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
+
74
77
## Model Helpers:
75
78
76
79
### PersistentStoreVersion Protocol
77
80
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.
79
82
80
83
With the `PersistentStoreVersion` protocol, I’ve built a bunch of useful helpers for getting your migrations set up.
- Alternately, just pass your `PersistentStoreVersion` into the initializer for `SingleStoreStack`:
139
+
140
+
```swift
141
+
let stack =trySingleStoreStack(
142
+
versionKey: ModelVersions.self,
143
+
mainAuthor: Authors.iOSViewContext)
144
+
```
145
+
135
146
### PersistentHistoryTracker Actor
136
147
137
148
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(
145
156
tracker.startMonitoring()
146
157
```
147
158
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 =trySingleStoreStack(
163
+
versionKey: ModelVersions.self,
164
+
mainAuthor: Authors.iOSViewContext,
165
+
persistentHistoryOptions: .init())
166
+
167
+
stack.historyTracker?.startMonitoring()
168
+
```
149
169
150
170
## CRUD Helpers:
151
171
@@ -157,11 +177,11 @@ Make your `NSManagedObject` subclass conform to the `Updatable` protocol to get
157
177
let rc =Person(...)
158
178
159
179
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!
161
181
rc.update(\.age, value: 16, minimumChange: 2) // still 15, because I only want to age in 2-year increments.
162
182
163
183
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.
165
185
rc.remove(\.friend, relation: dan) // dan's not my friend anymore.
0 commit comments