Skip to content

Commit 47de46c

Browse files
authored
Merge pull request #45 from roanutil/enable-different-request-for-frc-in-subscriptions
Enable different request for frc in subscriptions
2 parents e5ec1cc + 05c01ab commit 47de46c

10 files changed

Lines changed: 1276 additions & 51 deletions

Sources/CoreDataRepository/CoreDataRepository+Aggregate.swift

Lines changed: 314 additions & 0 deletions
Large diffs are not rendered by default.

Sources/CoreDataRepository/CoreDataRepository+Fetch.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,31 @@ extension CoreDataRepository {
3939
}
4040
}
4141

42+
/// Fetch items from the store with a ``NSFetchRequest`` and receive updates as the store changes.
43+
///
44+
/// This endpoint allows separate fetch requests for fetching and change tracking. There are times where CoreData
45+
/// will not recognize changes with a specific predicate. The fix, is to use a simplified predicate for change
46+
/// tracking and the full predicate for fetching.
47+
@inlinable
48+
public func fetchSubscription<Model: FetchableUnmanagedModel>(
49+
request: NSFetchRequest<Model.ManagedModel>,
50+
changeTrackingRequest: NSFetchRequest<Model.ManagedModel>,
51+
of _: Model.Type
52+
) -> AsyncStream<Result<[Model], CoreDataError>> {
53+
AsyncStream { continuation in
54+
let subscription = FetchSubscription(
55+
fetchRequest: request,
56+
fetchResultControllerRequest: changeTrackingRequest,
57+
context: context.childContext(),
58+
continuation: continuation
59+
)
60+
continuation.onTermination = { _ in
61+
subscription.cancel()
62+
}
63+
subscription.manualFetch()
64+
}
65+
}
66+
4267
/// Fetch items from the store with a ``NSFetchRequest`` and receive updates as the store changes.
4368
@inlinable
4469
public func fetchThrowingSubscription<Model: FetchableUnmanagedModel>(
@@ -58,6 +83,31 @@ extension CoreDataRepository {
5883
}
5984
}
6085

86+
/// Fetch items from the store with a ``NSFetchRequest`` and receive updates as the store changes.
87+
///
88+
/// This endpoint allows separate fetch requests for fetching and change tracking. There are times where CoreData
89+
/// will not recognize changes with a specific predicate. The fix, is to use a simplified predicate for change
90+
/// tracking and the full predicate for fetching.
91+
@inlinable
92+
public func fetchThrowingSubscription<Model: FetchableUnmanagedModel>(
93+
request: NSFetchRequest<Model.ManagedModel>,
94+
changeTrackingRequest: NSFetchRequest<Model.ManagedModel>,
95+
of _: Model.Type
96+
) -> AsyncThrowingStream<[Model], Error> {
97+
AsyncThrowingStream { continuation in
98+
let subscription = FetchThrowingSubscription(
99+
fetchRequest: request,
100+
fetchResultControllerRequest: changeTrackingRequest,
101+
context: context.childContext(),
102+
continuation: continuation
103+
)
104+
continuation.onTermination = { _ in
105+
subscription.cancel()
106+
}
107+
subscription.manualFetch()
108+
}
109+
}
110+
61111
/// Fetch items from the store with a ``NSFetchRequest`` and transform the results.
62112
@inlinable
63113
public func fetch<Managed: NSManagedObject, Output>(

Sources/CoreDataRepository/Internal/AggregateSubscription.swift

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ final class AggregateSubscription<Value: Numeric & Sendable>: Subscription<Value
4040
}
4141

4242
@usableFromInline
43-
// swiftlint:disable:next function_body_length
4443
convenience init(
4544
function: CoreDataRepository.AggregateFunction,
4645
context: NSManagedObjectContext,
@@ -52,39 +51,77 @@ final class AggregateSubscription<Value: Numeric & Sendable>: Subscription<Value
5251
) {
5352
let request: NSFetchRequest<NSDictionary>
5453
do {
55-
request = try NSFetchRequest<NSDictionary>.request(
54+
request = try NSFetchRequest.request(
5655
function: function,
5756
predicate: predicate,
5857
entityDesc: entityDesc,
5958
attributeDesc: attributeDesc,
6059
groupBy: groupBy
6160
)
62-
} catch let error as CoreDataError {
61+
} catch {
6362
self.init(
6463
fetchRequest: NSFetchRequest(),
6564
fetchResultControllerRequest: NSFetchRequest(),
6665
context: context,
6766
continuation: continuation
6867
)
69-
self.fail(error)
68+
fail(error)
7069
return
71-
} catch let error as CocoaError {
70+
}
71+
guard entityDesc == attributeDesc.entity else {
7272
self.init(
7373
fetchRequest: NSFetchRequest(),
7474
fetchResultControllerRequest: NSFetchRequest(),
7575
context: context,
7676
continuation: continuation
7777
)
78-
self.fail(.cocoa(error))
78+
guard let entityName = entityDesc.name ?? entityDesc.managedObjectClassName else {
79+
fail(.propertyDoesNotMatchEntity(description: nil))
80+
return
81+
}
82+
guard let attributeEntityName = attributeDesc.entity.name ?? attributeDesc.entity.managedObjectClassName
83+
else {
84+
fail(.propertyDoesNotMatchEntity(description: entityName))
85+
return
86+
}
87+
fail(
88+
.propertyDoesNotMatchEntity(
89+
description: "\(entityName) != \(attributeDesc.name).\(attributeEntityName)"
90+
)
91+
)
7992
return
93+
}
94+
self.init(request: request, context: context, continuation: continuation)
95+
}
96+
97+
@usableFromInline
98+
convenience init(
99+
function: CoreDataRepository.AggregateFunction,
100+
context: NSManagedObjectContext,
101+
predicate: NSPredicate,
102+
changeTrackingRequest: NSFetchRequest<NSManagedObject>,
103+
entityDesc: NSEntityDescription,
104+
attributeDesc: NSAttributeDescription,
105+
groupBy: NSAttributeDescription? = nil,
106+
continuation: AsyncStream<Result<Value, CoreDataError>>.Continuation
107+
) {
108+
let request: NSFetchRequest<NSDictionary>
109+
do {
110+
request = try NSFetchRequest.request(
111+
function: function,
112+
predicate: predicate,
113+
entityDesc: entityDesc,
114+
attributeDesc: attributeDesc,
115+
groupBy: groupBy
116+
)
80117
} catch {
81118
self.init(
82119
fetchRequest: NSFetchRequest(),
83120
fetchResultControllerRequest: NSFetchRequest(),
84121
context: context,
85122
continuation: continuation
86123
)
87-
fail(.unknown(error as NSError))
124+
fail(error)
88125
return
89126
}
90127
guard entityDesc == attributeDesc.entity else {
@@ -110,6 +147,11 @@ final class AggregateSubscription<Value: Numeric & Sendable>: Subscription<Value
110147
)
111148
return
112149
}
113-
self.init(request: request, context: context, continuation: continuation)
150+
self.init(
151+
fetchRequest: request,
152+
fetchResultControllerRequest: changeTrackingRequest,
153+
context: context,
154+
continuation: continuation
155+
)
114156
}
115157
}

Sources/CoreDataRepository/Internal/AggregateThrowingSubscription.swift

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,51 +44,88 @@ final class AggregateThrowingSubscription<Value: Numeric & Sendable>: ThrowingSu
4444
}
4545

4646
@usableFromInline
47-
// swiftlint:disable:next function_body_length
4847
convenience init(
4948
function: CoreDataRepository.AggregateFunction,
5049
context: NSManagedObjectContext,
5150
predicate: NSPredicate,
5251
entityDesc: NSEntityDescription,
5352
attributeDesc: NSAttributeDescription,
5453
groupBy: NSAttributeDescription? = nil,
55-
continuation: AsyncThrowingStream<Value, Error>.Continuation
54+
continuation: AsyncThrowingStream<Value, any Error>.Continuation
5655
) {
5756
let request: NSFetchRequest<NSDictionary>
5857
do {
59-
request = try NSFetchRequest<NSDictionary>.request(
58+
request = try NSFetchRequest.request(
6059
function: function,
6160
predicate: predicate,
6261
entityDesc: entityDesc,
6362
attributeDesc: attributeDesc,
6463
groupBy: groupBy
6564
)
66-
} catch let error as CoreDataError {
65+
} catch {
6766
self.init(
6867
fetchRequest: NSFetchRequest(),
6968
fetchResultControllerRequest: NSFetchRequest(),
7069
context: context,
7170
continuation: continuation
7271
)
73-
self.fail(error)
72+
fail(error)
7473
return
75-
} catch let error as CocoaError {
74+
}
75+
guard entityDesc == attributeDesc.entity else {
7676
self.init(
7777
fetchRequest: NSFetchRequest(),
7878
fetchResultControllerRequest: NSFetchRequest(),
7979
context: context,
8080
continuation: continuation
8181
)
82-
self.fail(.cocoa(error))
82+
guard let entityName = entityDesc.name ?? entityDesc.managedObjectClassName else {
83+
fail(.propertyDoesNotMatchEntity(description: nil))
84+
return
85+
}
86+
guard let attributeEntityName = attributeDesc.entity.name ?? attributeDesc.entity.managedObjectClassName
87+
else {
88+
fail(.propertyDoesNotMatchEntity(description: entityName))
89+
return
90+
}
91+
fail(
92+
.propertyDoesNotMatchEntity(
93+
description: "\(entityName) != \(attributeDesc.name).\(attributeEntityName)"
94+
)
95+
)
8396
return
97+
}
98+
self.init(request: request, context: context, continuation: continuation)
99+
}
100+
101+
@usableFromInline
102+
convenience init(
103+
function: CoreDataRepository.AggregateFunction,
104+
context: NSManagedObjectContext,
105+
predicate: NSPredicate,
106+
changeTrackingRequest: NSFetchRequest<NSManagedObject>,
107+
entityDesc: NSEntityDescription,
108+
attributeDesc: NSAttributeDescription,
109+
groupBy: NSAttributeDescription? = nil,
110+
continuation: AsyncThrowingStream<Value, any Error>.Continuation
111+
) {
112+
let request: NSFetchRequest<NSDictionary>
113+
do {
114+
request = try NSFetchRequest.request(
115+
function: function,
116+
predicate: predicate,
117+
entityDesc: entityDesc,
118+
attributeDesc: attributeDesc,
119+
groupBy: groupBy
120+
)
84121
} catch {
85122
self.init(
86123
fetchRequest: NSFetchRequest(),
87124
fetchResultControllerRequest: NSFetchRequest(),
88125
context: context,
89126
continuation: continuation
90127
)
91-
fail(.unknown(error as NSError))
128+
fail(error)
92129
return
93130
}
94131
guard entityDesc == attributeDesc.entity else {
@@ -114,6 +151,11 @@ final class AggregateThrowingSubscription<Value: Numeric & Sendable>: ThrowingSu
114151
)
115152
return
116153
}
117-
self.init(request: request, context: context, continuation: continuation)
154+
self.init(
155+
fetchRequest: request,
156+
fetchResultControllerRequest: changeTrackingRequest,
157+
context: context,
158+
continuation: continuation
159+
)
118160
}
119161
}

Sources/CoreDataRepository/Internal/CountSubscription.swift

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ final class CountSubscription<Value: Numeric & Sendable>: Subscription<Value, NS
1414
{
1515
@usableFromInline
1616
override func fetch() {
17-
frc.managedObjectContext.perform { [weak self, frc] in
17+
frc.managedObjectContext.perform { [weak self, frc, request] in
1818
if (frc.fetchedObjects ?? []).isEmpty {
1919
self?.start()
2020
}
2121
do {
22-
let count = try frc.managedObjectContext.count(for: frc.fetchRequest)
22+
let count = try frc.managedObjectContext.count(for: request)
2323
self?.send(Value(exactly: count) ?? Value.zero)
2424
} catch let error as CocoaError {
2525
self?.fail(.cocoa(error))
@@ -38,38 +38,49 @@ final class CountSubscription<Value: Numeric & Sendable>: Subscription<Value, NS
3838
) {
3939
let request: NSFetchRequest<NSDictionary>
4040
do {
41-
request = try NSFetchRequest<NSDictionary>.countRequest(
41+
request = try NSFetchRequest.countRequest(
4242
predicate: predicate,
4343
entityDesc: entityDesc
4444
)
45-
} catch let error as CoreDataError {
46-
self.init(
47-
fetchRequest: NSFetchRequest(),
48-
fetchResultControllerRequest: NSFetchRequest(),
49-
context: context,
50-
continuation: continuation
51-
)
52-
self.fail(error)
53-
return
54-
} catch let error as CocoaError {
45+
} catch {
5546
self.init(
5647
fetchRequest: NSFetchRequest(),
5748
fetchResultControllerRequest: NSFetchRequest(),
5849
context: context,
5950
continuation: continuation
6051
)
61-
self.fail(.cocoa(error))
52+
fail(error)
6253
return
54+
}
55+
self.init(request: request, context: context, continuation: continuation)
56+
}
57+
58+
@usableFromInline
59+
convenience init(
60+
context: NSManagedObjectContext,
61+
predicate: NSPredicate,
62+
changeTrackingRequest: NSFetchRequest<NSManagedObject>,
63+
entityDesc: NSEntityDescription,
64+
continuation: AsyncStream<Result<Value, CoreDataError>>.Continuation
65+
) {
66+
let request: NSFetchRequest<NSDictionary>
67+
do {
68+
request = try NSFetchRequest.countRequest(predicate: predicate, entityDesc: entityDesc)
6369
} catch {
6470
self.init(
6571
fetchRequest: NSFetchRequest(),
6672
fetchResultControllerRequest: NSFetchRequest(),
6773
context: context,
6874
continuation: continuation
6975
)
70-
fail(.unknown(error as NSError))
76+
fail(error)
7177
return
7278
}
73-
self.init(request: request, context: context, continuation: continuation)
79+
self.init(
80+
fetchRequest: request,
81+
fetchResultControllerRequest: changeTrackingRequest,
82+
context: context,
83+
continuation: continuation
84+
)
7485
}
7586
}

0 commit comments

Comments
 (0)