1+ // AggregateRepository.swift
2+ // CoreDataRepository
13//
2- // AggregateRepository.swift
34//
4- // Created by Andrew Roan on 1/18/21.
5+ // MIT License
56//
7+ // Copyright © 2021 Andrew Roan
68
7- import CoreData
89import Combine
10+ import CoreData
911
1012/// A CoreData repository with functions for getting aggregate values
1113public final class AggregateRepository {
1214 // MARK: Properties
15+
1316 /// The context used by the repository
1417 public let context : NSManagedObjectContext
1518 var cancellables = [ AnyCancellable] ( )
1619 var subscriptions = [ SubscriptionProvider] ( )
1720
1821 // MARK: Init
22+
1923 /// Initializes a repository
2024 /// - Parameters:
2125 /// - context: NSManagedObjectContext
@@ -25,6 +29,7 @@ public final class AggregateRepository {
2529 }
2630
2731 // MARK: Types
32+
2833 /// The aggregate function to be calculated
2934 public enum Function : String {
3035 case count
@@ -56,7 +61,13 @@ public final class AggregateRepository {
5661 }
5762 }
5863
59- private func request( function: Function , predicate: NSPredicate , entityDesc: NSEntityDescription , attributeDesc: NSAttributeDescription , groupBy: NSAttributeDescription ? = nil ) -> NSFetchRequest < NSDictionary > {
64+ private func request(
65+ function: Function ,
66+ predicate _: NSPredicate ,
67+ entityDesc: NSEntityDescription ,
68+ attributeDesc: NSAttributeDescription ,
69+ groupBy: NSAttributeDescription ? = nil
70+ ) -> NSFetchRequest < NSDictionary > {
6071 let expDesc = NSExpressionDescription . aggregate ( function: function, attributeDesc: attributeDesc)
6172 let request = NSFetchRequest < NSDictionary > ( entityName: entityDesc. managedObjectClassName)
6273 request. entity = entityDesc
@@ -67,7 +78,7 @@ public final class AggregateRepository {
6778 } else {
6879 request. propertiesToFetch = [ expDesc]
6980 }
70-
81+
7182 if let groupBy = groupBy {
7283 request. propertiesToGroupBy = [ groupBy. name]
7384 }
@@ -76,6 +87,7 @@ public final class AggregateRepository {
7687 }
7788
7889 // MARK: Private Functions
90+
7991 /// Calculates aggregate values
8092 /// - Parameters
8193 /// - function: Function
@@ -87,31 +99,41 @@ public final class AggregateRepository {
8799 /// - `[[String: Value]]`
88100 ///
89101 private func aggregate< Value: Numeric > ( request: NSFetchRequest < NSDictionary > ) throws -> [ [ String : Value ] ] {
90- let result = try self . context. fetch ( request)
102+ let result = try context. fetch ( request)
91103 return result as? [ [ String : Value ] ] ?? [ ]
92104 }
93105
94106 // MARK: Public Functions
107+
95108 /// Calculate the count for a fetchRequest
96109 /// - Parameters:
97110 /// - predicate: NSPredicate
98111 /// - entityDesc: NSEntityDescription
99112 /// - Returns
100113 /// - AnyPublisher<Success<Int>, Failure<Int>>
101114 ///
102- public func count< Value: Numeric > ( predicate: NSPredicate , entityDesc: NSEntityDescription ) -> AnyPublisher < Success < Value > , Failure > {
103- return Deferred { Future { [ weak self] callback in
115+ public func count< Value: Numeric > ( predicate: NSPredicate ,
116+ entityDesc: NSEntityDescription ) -> AnyPublisher < Success < Value > , Failure >
117+ {
118+ Deferred { Future { [ weak self] callback in
104119 let request = NSFetchRequest < NSDictionary > ( entityName: entityDesc. name ?? " " )
105120 request. predicate = predicate
106- request. sortDescriptors = [ NSSortDescriptor ( key: entityDesc. attributesByName. values. first!. name, ascending: true ) ]
107- guard let self = self else { return callback ( . failure( Failure ( function: . count, request: request, error: . unknown) ) ) }
121+ request
122+ . sortDescriptors =
123+ [ NSSortDescriptor ( key: entityDesc. attributesByName. values. first!. name, ascending: true ) ]
124+ guard let self = self
125+ else { return callback ( . failure( Failure ( function: . count, request: request, error: . unknown) ) ) }
108126 do {
109127 let count = try self . context. count ( for: request)
110- callback ( . success( Success ( function: . count, result: [ [ " countOf \( entityDesc. name ?? " " ) " : Value ( exactly: count) ?? Value . zero] ] , request: request) ) )
128+ callback ( . success( Success (
129+ function: . count,
130+ result: [ [ " countOf \( entityDesc. name ?? " " ) " : Value ( exactly: count) ?? Value . zero] ] ,
131+ request: request
132+ ) ) )
111133 } catch {
112134 callback ( . failure( Failure ( function: . count, request: request, error: . cocoa( error as NSError ) ) ) )
113135 }
114-
136+
115137 } } . eraseToAnyPublisher ( )
116138 }
117139
@@ -124,13 +146,26 @@ public final class AggregateRepository {
124146 /// - Returns
125147 /// - AnyPublisher<Success<Value>, Failure<Value>>
126148 ///
127- public func sum< Value: Numeric > ( predicate: NSPredicate , entityDesc: NSEntityDescription , attributeDesc: NSAttributeDescription , groupBy: NSAttributeDescription ? = nil ) -> AnyPublisher < Success < Value > , Failure > {
128- let request = self . request ( function: . sum, predicate: predicate, entityDesc: entityDesc, attributeDesc: attributeDesc, groupBy: groupBy)
149+ public func sum< Value: Numeric > (
150+ predicate: NSPredicate ,
151+ entityDesc: NSEntityDescription ,
152+ attributeDesc: NSAttributeDescription ,
153+ groupBy: NSAttributeDescription ? = nil
154+ ) -> AnyPublisher < Success < Value > , Failure > {
155+ let request = request (
156+ function: . sum,
157+ predicate: predicate,
158+ entityDesc: entityDesc,
159+ attributeDesc: attributeDesc,
160+ groupBy: groupBy
161+ )
129162 guard entityDesc == attributeDesc. entity else {
130- return Fail ( error: Failure ( function: . sum, request: request, error: . propertyDoesNotMatchEntity) ) . eraseToAnyPublisher ( )
163+ return Fail ( error: Failure ( function: . sum, request: request, error: . propertyDoesNotMatchEntity) )
164+ . eraseToAnyPublisher ( )
131165 }
132166 return Deferred { Future { [ weak self] callback in
133- guard let self = self else { return callback ( . failure( Failure ( function: . sum, request: request, error: . unknown) ) ) }
167+ guard let self = self
168+ else { return callback ( . failure( Failure ( function: . sum, request: request, error: . unknown) ) ) }
134169 do {
135170 let result : [ [ String : Value ] ] = try self . aggregate ( request: request)
136171 callback ( . success( Success ( function: . sum, result: result, request: request) ) )
@@ -149,13 +184,26 @@ public final class AggregateRepository {
149184 /// - Returns
150185 /// - AnyPublisher<Success<Value>, Failure<Value>>
151186 ///
152- public func average< Value: Numeric > ( predicate: NSPredicate , entityDesc: NSEntityDescription , attributeDesc: NSAttributeDescription , groupBy: NSAttributeDescription ? = nil ) -> AnyPublisher < Success < Value > , Failure > {
153- let request = self . request ( function: . average, predicate: predicate, entityDesc: entityDesc, attributeDesc: attributeDesc, groupBy: groupBy)
187+ public func average< Value: Numeric > (
188+ predicate: NSPredicate ,
189+ entityDesc: NSEntityDescription ,
190+ attributeDesc: NSAttributeDescription ,
191+ groupBy: NSAttributeDescription ? = nil
192+ ) -> AnyPublisher < Success < Value > , Failure > {
193+ let request = request (
194+ function: . average,
195+ predicate: predicate,
196+ entityDesc: entityDesc,
197+ attributeDesc: attributeDesc,
198+ groupBy: groupBy
199+ )
154200 guard entityDesc == attributeDesc. entity else {
155- return Fail ( error: Failure ( function: . average, request: request, error: . propertyDoesNotMatchEntity) ) . eraseToAnyPublisher ( )
201+ return Fail ( error: Failure ( function: . average, request: request, error: . propertyDoesNotMatchEntity) )
202+ . eraseToAnyPublisher ( )
156203 }
157204 return Deferred { Future { [ weak self] callback in
158- guard let self = self else { return callback ( . failure( Failure ( function: . average, request: request, error: . unknown) ) ) }
205+ guard let self = self
206+ else { return callback ( . failure( Failure ( function: . average, request: request, error: . unknown) ) ) }
159207 do {
160208 let result : [ [ String : Value ] ] = try self . aggregate ( request: request)
161209 callback ( . success( Success ( function: . average, result: result, request: request) ) )
@@ -174,13 +222,26 @@ public final class AggregateRepository {
174222 /// - Returns
175223 /// - AnyPublisher<Success<Value>, Failure<Value>>
176224 ///
177- public func min< Value: Numeric > ( predicate: NSPredicate , entityDesc: NSEntityDescription , attributeDesc: NSAttributeDescription , groupBy: NSAttributeDescription ? = nil ) -> AnyPublisher < Success < Value > , Failure > {
178- let request = self . request ( function: . min, predicate: predicate, entityDesc: entityDesc, attributeDesc: attributeDesc, groupBy: groupBy)
225+ public func min< Value: Numeric > (
226+ predicate: NSPredicate ,
227+ entityDesc: NSEntityDescription ,
228+ attributeDesc: NSAttributeDescription ,
229+ groupBy: NSAttributeDescription ? = nil
230+ ) -> AnyPublisher < Success < Value > , Failure > {
231+ let request = request (
232+ function: . min,
233+ predicate: predicate,
234+ entityDesc: entityDesc,
235+ attributeDesc: attributeDesc,
236+ groupBy: groupBy
237+ )
179238 guard entityDesc == attributeDesc. entity else {
180- return Fail ( error: Failure ( function: . min, request: request, error: . propertyDoesNotMatchEntity) ) . eraseToAnyPublisher ( )
239+ return Fail ( error: Failure ( function: . min, request: request, error: . propertyDoesNotMatchEntity) )
240+ . eraseToAnyPublisher ( )
181241 }
182242 return Deferred { Future { [ weak self] callback in
183- guard let self = self else { return callback ( . failure( Failure ( function: . min, request: request, error: . unknown) ) ) }
243+ guard let self = self
244+ else { return callback ( . failure( Failure ( function: . min, request: request, error: . unknown) ) ) }
184245 do {
185246 let result : [ [ String : Value ] ] = try self . aggregate ( request: request)
186247 callback ( . success( Success ( function: . min, result: result, request: request) ) )
@@ -199,13 +260,26 @@ public final class AggregateRepository {
199260 /// - Returns
200261 /// - AnyPublisher<Success<Value>, Failure<Value>>
201262 ///
202- public func max< Value: Numeric > ( predicate: NSPredicate , entityDesc: NSEntityDescription , attributeDesc: NSAttributeDescription , groupBy: NSAttributeDescription ? = nil ) -> AnyPublisher < Success < Value > , Failure > {
203- let request = self . request ( function: . max, predicate: predicate, entityDesc: entityDesc, attributeDesc: attributeDesc, groupBy: groupBy)
263+ public func max< Value: Numeric > (
264+ predicate: NSPredicate ,
265+ entityDesc: NSEntityDescription ,
266+ attributeDesc: NSAttributeDescription ,
267+ groupBy: NSAttributeDescription ? = nil
268+ ) -> AnyPublisher < Success < Value > , Failure > {
269+ let request = request (
270+ function: . max,
271+ predicate: predicate,
272+ entityDesc: entityDesc,
273+ attributeDesc: attributeDesc,
274+ groupBy: groupBy
275+ )
204276 guard entityDesc == attributeDesc. entity else {
205- return Fail ( error: Failure ( function: . max, request: request, error: . propertyDoesNotMatchEntity) ) . eraseToAnyPublisher ( )
277+ return Fail ( error: Failure ( function: . max, request: request, error: . propertyDoesNotMatchEntity) )
278+ . eraseToAnyPublisher ( )
206279 }
207280 return Deferred { Future { [ weak self] callback in
208- guard let self = self else { return callback ( . failure( Failure ( function: . max, request: request, error: . unknown) ) ) }
281+ guard let self = self
282+ else { return callback ( . failure( Failure ( function: . max, request: request, error: . unknown) ) ) }
209283 do {
210284 let result : [ [ String : Value ] ] = try self . aggregate ( request: request)
211285 callback ( . success( Success ( function: . max, result: result, request: request) ) )
@@ -217,6 +291,7 @@ public final class AggregateRepository {
217291}
218292
219293// MARK: Extensions
294+
220295extension NSExpression {
221296 /// Convenience initializer for NSExpression that represent an aggregate function on a keypath
222297 fileprivate convenience init ( function: AggregateRepository . Function , attributeDesc: NSAttributeDescription ) {
@@ -227,7 +302,9 @@ extension NSExpression {
227302
228303extension NSExpressionDescription {
229304 /// Convenience initializer for NSExpressionDescription that represent the properties to fetch in NSFetchRequest
230- fileprivate static func aggregate( function: AggregateRepository . Function , attributeDesc: NSAttributeDescription ) -> NSExpressionDescription {
305+ fileprivate static func aggregate( function: AggregateRepository . Function ,
306+ attributeDesc: NSAttributeDescription ) -> NSExpressionDescription
307+ {
231308 let expression = NSExpression ( function: function, attributeDesc: attributeDesc)
232309 let expDesc = NSExpressionDescription ( )
233310 expDesc. expression = expression
0 commit comments