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
Major Refactor and improvements
- Change to storing the URL instead of the `NSManagedObjectID` on value types
- Add batch operations by NSManagedObjectID URL in addition to NSBatch*Request
- Add subscription for reading a single item
- Make all edits to the store in a 'scratchpad' context that can be thrown out if there's a problem
- Move all functionality into a single repository type `CoreDataRepository` which helps with internal code reuse and only needing the one repository for all uses.
- Add `CombineCommunity/CombineExt` as dependency and avoid inlining the `Create` publisher manually
- Overhaul Failure types to concrete CoreDataRepositoryError
- Cleanup various internal code for less possible error branches and readability
- Fix lots of wrong doc comments
- Fix sporadic test failures
CoreDataRepository is a reactive library (Combine) for using CoreData on a background queue. It features endpoints for CRUD, batch, fetch multiple, and aggregate operations. Also, it offers a stream like subscription function for wrapping a fetch multiple call that will send updates that match the fetch request.
7
+
CoreDataRepository is a reactive library (Combine) for using CoreData on a background queue. It features endpoints for CRUD, batch, fetch, and aggregate operations. Also, it offers a stream like subscription for fetch and read.
8
8
9
9
Since ```NSManagedObject```s are not thread safe, a value type model must exist for each ```NSMangaedObject``` subclass.
10
10
11
11
12
-
## Why the hell did you make this?
13
-
When I started learning more about application architecture, I ran into things like Clean Architecture that insist that the models, business logic, and views should be far away from platform specific frameworks. Your view should have no concern over the implementation details of persistence. When I compared that to how things are usually done on iOS, I noticed a big difference.
12
+
## Motivation
14
13
15
-
After some time passed I came across Composable Architecture which is a Swift library and seemingly meant for iOS. I was really confused how anybody could take it seriously since all of the app state is value types and the Apple frameworks are object oriented. Finally I found somebody discussing CoreData and ComposableArchitecture on the Swift Forums and they seemed to be mapping NSManagedObjects to structs which seemed insane but clever. After reading that, I did my best to suppress my inner rage at the inefficiency of it all and got to work.
14
+
CoreData is a great framework for local persistence on Apple's platforms. However, it can be tempting to create strong dependencies on it throughout an app. Even worse, the `viewContext` runs on the main `DispatchQueue` along with the UI. Even fetching data from the store can be enough to cause performance problems.
16
15
17
-
The result is this library which in some form is actually used in production for my app. Going forward, when given the choice, I will always use this library rather than the old way. NSManagedObjects can be tricky. Fetching any real number of them on the main queue freezes the UI.
16
+
The goals of `CoreDataRepository` are:
17
+
- Ease isolation of `CoreData` related code away from the rest of the app.
18
+
- Improve ergonomics by providing an asynchronous API with `Combine`.
19
+
- Improve usability of private contexts to relieve load from the main `DispatchQueue`.
20
+
- Make local persistence with `CoreData` feel more 'Swift-like' by allowing the model layer to use value types.
21
+
22
+
### Mapping `NSManagedObject`s to value types
23
+
24
+
It may feel convoluted to add this layer of abstraction over local persistence and the overhead of mapping between objects and value types. Similar to the motivation for only exposing views to the minimum data they need, why should the model layer be concerned with the details of the persistence layer? `NSManagedObject`s are complicated types that really should be isolated as much as possible.
18
25
19
26
To give some weight to this idea, here's a quote from the Q&A portion of [this](https://academy.realm.io/posts/andy-matuschak-controlling-complexity/) talk by Andy Matuschak:
20
27
@@ -31,47 +38,62 @@ There are two protocols that handle bridging between the value type and managed
let publisher: AnyPublisher<(success: [Movie], failed: [Movie]), Never> = repository.create(movies)
237
+
_= publisher
238
+
.subscribe(on: backgroundQueue)
239
+
.receive(on: mainQueue)
240
+
.sink(
241
+
receiveCompletion: { completion in
242
+
switch completion {
243
+
case .finished:
244
+
os_log("Finished inserting A LOT of movies")
245
+
default:
246
+
fatalError("Failed to insert a lot of movies")
247
+
}
248
+
},
249
+
receiveValue: { createdMovies in
250
+
os_log("Created these movies: \(createdMovies)")
251
+
}
252
+
)_
253
+
```
254
+
204
255
205
256
## TODO
206
257
- Add a subscription feature for aggregate functions
207
258
208
259
209
260
## Contributing
210
-
I welcome any feedback or contributions. I'm not eager to mess with the API a lot but let's be honest, it could probably be better. As always more tests wouldn't hurt.
261
+
I welcome any feedback or contributions. It's probably best to create an issue where any possible changes can be discussed before doing the work and creating a PR.
0 commit comments