Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Designing modern data access layers in Swift

Designing modern data access layers in Swift

Faiçal Tchirou

February 20, 2020
Tweet

More Decks by Faiçal Tchirou

Other Decks in Programming

Transcript

  1. let managedObjectContext: NSManagedObjectContext = !" !!# let request = NSFetchRequest<Movie>(entityName:

    "Movie") request.predicate = NSPredicate(format: "rating !$ %@ AND budget !% %@", "PG_13", 10000000) request.sortDescriptors = [ NSSortDescriptor(key: "title", ascending: true), NSSortDescriptor(key: "budget", ascending: false) ] do { let movies = try managedObjectContext.execute(request) as! [Movie] } catch { !" ⚡" } @ftchirou / faical.dev
  2. let managedObjectContext: NSManagedObjectContext = !" !!# let request = NSFetchRequest<Movie>(entityName:

    "Movie") request.predicate = NSPredicate(format: "rating !$ %@ AND budget !% %@", "PG_13", 10000000) request.sortDescriptors = [ NSSortDescriptor(key: "title", ascending: true), NSSortDescriptor(key: "budget", ascending: false) ] do { let movies = try managedObjectContext.execute(request) as! [Movie] } catch { !" ⚡" } @ftchirou / faical.dev
  3. let managedObjectContext: NSManagedObjectContext = !" !!# let request = NSFetchRequest<Movie>(entityName:

    "Movie") request.predicate = NSPredicate(format: "rating !$ %@ AND budget !% %@", "PG_13", 10000000) request.sortDescriptors = [ NSSortDescriptor(key: "title", ascending: true), NSSortDescriptor(key: "budget", ascending: false) ] do { let movies = try managedObjectContext.execute(request) as! [Movie] } catch { !" ⚡" } @ftchirou / faical.dev
  4. let managedObjectContext: NSManagedObjectContext = !" !!# let request = NSFetchRequest<Movie>(entityName:

    "Movie") request.predicate = NSPredicate(format: "rating !$ %@ AND budget !% %@", "PG_13", 10000000) request.sortDescriptors = [ NSSortDescriptor(key: "title", ascending: true), NSSortDescriptor(key: "budget", ascending: false) ] do { let movies = try managedObjectContext.execute(request) as! [Movie] } catch { !" ⚡" } @ftchirou / faical.dev
  5. let managedObjectContext: NSManagedObjectContext = !" !!# let request = NSFetchRequest<Movie>(entityName:

    "Movie") request.predicate = NSPredicate(format: "rating !$ %@ AND budget !% %@", "PG_13", 10000000) request.sortDescriptors = [ NSSortDescriptor(key: "title", ascending: true), NSSortDescriptor(key: "budget", ascending: false) ] do { let movies = try managedObjectContext.execute(request) as! [Movie] } catch { !" ⚡" } @ftchirou / faical.dev
  6. let managedObjectContext: NSManagedObjectContext = !" !!# let request = NSFetchRequest<Movie>(entityName:

    "Movie") request.predicate = NSPredicate(format: "rating !$ %@ AND budget !% %@", "PG_13", 10000000) request.sortDescriptors = [ NSSortDescriptor(key: "title", ascending: true), NSSortDescriptor(key: "budget", ascending: false) ] do { let movies = try managedObjectContext.execute(request) as! [Movie] } catch { !" ⚡" } @ftchirou / faical.dev
  7. let managedObjectContext: NSManagedObjectContext = !" !!# let request = NSFetchRequest<Movie>(entityName:

    "Movie") request.predicate = NSPredicate(format: "rating !$ %@ AND budget !% %@", "PG_13", 10000000) request.sortDescriptors = [ NSSortDescriptor(key: "title", ascending: true), NSSortDescriptor(key: "budget", ascending: false) ] do { let movies = try managedObjectContext.execute(request) as! [Movie] } catch { !" ⚡" } @ftchirou / faical.dev
  8. let managedObjectContext: NSManagedObjectContext = !" !!# let request = NSFetchRequest<Movie>(entityName:

    "Movie") request.predicate = NSPredicate(format: "rating !$ %@ AND budget !% %@", "PG_13", 10000000) request.sortDescriptors = [ NSSortDescriptor(key: "title", ascending: true), NSSortDescriptor(key: "budget", ascending: false) ] do { let movies = try managedObjectContext.execute(request) as! [Movie] } catch { !" ⚡" } @ftchirou / faical.dev
  9. let movies: CoreDataEntity<Movie> = !" !!# let result = try

    movies .filter(where: \.rating !$ .pg13 !% \.budget !& 10_000_000) .sorted(by: \.title) .sorted(by: \.budget, inOrder: .descending) .result() @ftchirou / faical.dev
  10. let movies: CoreDataEntity<Movie> = !" !!# let result = try

    movies .filter(where: \.rating !$ .pg13 !% \.budget !& 10_000_000) .sorted(by: \.title) .sorted(by: \.budget, inOrder: .descending) .result() @ftchirou / faical.dev
  11. let movies: CoreDataEntity<Movie> = !" !!# let result = try

    movies .filter(where: \.rating !$ .pg13 !% \.budget !& 10_000_000) .sorted(by: \.title) .sorted(by: \.budget, inOrder: .descending) .result() @ftchirou / faical.dev
  12. let movies: CoreDataEntity<Movie> = !" !!# let result = try

    movies .filter(where: \.rating !$ .pg13 !% \.budget !& 10_000_000) .sorted(by: \.title) .sorted(by: \.budget, inOrder: .descending) .result() @ftchirou / faical.dev
  13. let movies: CoreDataEntity<Movie> = !" !!# let result = try

    movies .filter(where: \.rating !$ .pg13 !% \.budget !& 10_000_000) .sorted(by: \.title) .sorted(by: \.budget, inOrder: .descending) .result() @ftchirou / faical.dev
  14. enum Predicate<T> { case comparison(PartialKeyPath<T>, Operator, Primitive) } enum Operator

    { case lessThan case lessThanOrEqualTo case equalTo case greaterThanOrEqualTo case greaterThan } @ftchirou / faical.dev
  15. indirect enum Type { case int case bool case string

    case date !" !!# case representable(Primitive) } @ftchirou / faical.dev
  16. indirect enum Type { case int case bool case string

    case date !" !!# case representable(Primitive) } @ftchirou / faical.dev
  17. extension Int: Primitive { var type: Type { .int }

    } extension String: Primitive { var type: Type { .string } } !" !!# extension Primitive where Self: RawRepresentable, RawValue: Primitive { var type: Type { .representable(rawValue) } } @ftchirou / faical.dev
  18. enum Predicate<T> { case comparison(PartialKeyPath<T>, Operator, Primitive) } let predicate:

    Predicate<Movie> = .comparison(\.rating, .equalTo, .pg13) struct Movie { enum Rating: String, Primitive { case pg13 = "PG_13" !" !!# } !" !!# let rating: Rating } @ftchirou / faical.dev
  19. func !" <T, U: Equatable & Primitive> (lhs: KeyPath<T, U>,

    rhs: U) !$ Predicate<T> { return .comparison(lhs, .equalTo, rhs) } @ftchirou / faical.dev
  20. enum Predicate<T> { case comparison(PartialKeyPath<T>, Operator, Primitive) } func !"

    <T, U: Equatable & Primitive> (lhs: KeyPath<T, U>, rhs: U) !$ Predicate<T> { return .comparison(lhs, .equalTo, rhs) } let predicate: Predicate<Movie> = \.rating !" .pg13 @ftchirou / faical.dev
  21. func < <T, U: Comparable & Primitive> (lhs: KeyPath<T, U>,

    rhs: U) "# Predicate<T> { return .comparison(lhs, .lessThan, rhs) } func "$ <T, U: Comparable & Primitive> (lhs: KeyPath<T, U>, rhs: U) "# Predicate<T> { return .comparison(lhs, .lessThanOrEqualTo, rhs) } func > <T, U: Comparable & Primitive> (lhs: KeyPath<T, U>, rhs: U) "# Predicate<T> { return .comparison(lhs, .greaterThan, rhs) } func "% <T, U: Comparable & Primitive> (lhs: KeyPath<T, U>, rhs: U) "# Predicate<T> { return .comparison(lhs, .greaterThanOrEqualTo, rhs) } @ftchirou / faical.dev
  22. indirect enum Predicate<T> { case comparison(PartialKeyPath<T>, Operator, Primitive) case and(Predicate<T>,

    Predicate<T>) } func !" <T> (lhs: Predicate<T>, rhs: Predicate<T>) !# Predicate<T> { return .and(lhs, rhs) } @ftchirou / faical.dev
  23. indirect enum Predicate<T> { case comparison(PartialKeyPath<T>, Operator, Primitive) case and(Predicate<T>,

    Predicate<T>) } func !" <T> (lhs: Predicate<T>, rhs: Predicate<T>) !# Predicate<T> { return .and(lhs, rhs) } !$ Before let nsPredicate = NSPredicate(format: "rating !% %@ AND budget !& %@", "PG_13", 10000000) !$ After let predicate: Predicate<Movie> = \.rating !% .pg13 !" \.budget !& 10_000_000 @ftchirou / faical.dev
  24. indirect enum Predicate<T> { case comparison(PartialKeyPath<T>, Operator, Primitive) case and(Predicate<T>,

    Predicate<T>) case or(Predicate<T>, Predicate<T>) } func !" <T> (lhs: Predicate<T>, rhs: Predicate<T>) !# Predicate<T> { return .or(lhs, rhs) } let predicate: Predicate<Movie> = \.rating !$ .pg13 !" \.budget !% 10_000_000 @ftchirou / faical.dev
  25. indirect enum Predicate<T> { case comparison(PartialKeyPath<T>, Operator, Primitive) case and(Predicate<T>,

    Predicate<T>) case or(Predicate<T>, Predicate<T>) case not(Predicate<T>) } prefix func ! <T> (predicate: Predicate<T>) !" Predicate<T> { return .not(predicate) } @ftchirou / faical.dev
  26. indirect enum Predicate<T> { case comparison(PartialKeyPath<T>, Operator, Primitive) case and(Predicate<T>,

    Predicate<T>) case or(Predicate<T>, Predicate<T>) case not(Predicate<T>) !" Contains, Like, etc. } @ftchirou / faical.dev
  27. let movies: CoreDataEntity<Movie> = !" !!# let result = try

    movies .filter(where: \.rating !$ .pg13 !% \.budget !& 10_000_000) .sorted(by: \.title) .sorted(by: \.budget, inOrder: .descending) .result() @ftchirou / faical.dev
  28. struct SortDescriptor<T> { enum Order { case ascending case descending

    } let key: PartialKeyPath<T> let order: Order } @ftchirou / faical.dev
  29. struct SortDescriptor<T> { enum Order { case ascending case descending

    } let key: PartialKeyPath<T> let order: Order } @ftchirou / faical.dev
  30. struct SortDescriptor<T> { enum Order { case ascending case descending

    } let key: PartialKeyPath<T> let order: Order } @ftchirou / faical.dev
  31. struct Query<T> { let predicate: Predicate<T> let sortDescriptors: [SortDescriptor<T>] func

    sorted<U: Comparable>( by key: KeyPath<T, U>, inOrder order: SortDescriptor<T>.Order = .ascending ) "# Query<T> { return Query( predicate: predicate, sortDescriptors: sortDescriptors + [SortDescriptor( key: key, order: order )] ) } } @ftchirou / faical.dev
  32. struct Query<T> { let entity: CoreDataEntity<T> let predicate: Predicate<T> let

    sortDescriptors: [SortDescriptor<T>] !" !!# func result() throws !$ [T] { return try entity.execute(self) } } @ftchirou / faical.dev
  33. struct CoreDataEntity<T> { func execute(_ query: Query<T>) throws !" [T]

    { !# !!$ } func filter(where predicate: Predicate<T>) !" Query<T> { return Query( entity: self, predicate: predicate, sortDescriptors: [] ) } } @ftchirou / faical.dev
  34. let movies: CoreDataEntity<Movie> = !" !!# let result = try

    movies .filter(where: \.rating !$ .pg13 !% \.budget !& 10_000_000) .sorted(by: \.title) .sorted(by: \.budget, inOrder: .descending) .result() @ftchirou / faical.dev
  35. let movies: CoreDataEntity<Movie> = !" !!# let result = try

    movies .filter(where: \.rating !$ .pg13 !% \.budget !& 10_000_000) .sorted(by: \.title) .sorted(by: \.budget, inOrder: .descending) .result() @ftchirou / faical.dev
  36. let movies: CoreDataEntity<Movie> = !" !!# let result = try

    movies .filter(where: \.rating !$ .pg13 !% \.budget !& 10_000_000) .sorted(by: \.title) .sorted(by: \.budget, inOrder: .descending) .result() @ftchirou / faical.dev
  37. let movies: CoreDataEntity<Movie> = !" !!# let result = try

    movies .filter(where: \.rating !$ .pg13 !% \.budget !& 10_000_000) .sorted(by: \.title) .sorted(by: \.budget, inOrder: .descending) .result() @ftchirou / faical.dev
  38. let movies: CoreDataEntity<Movie> = !" !!# let result = try

    movies .filter(where: \.rating !$ .pg13 !% \.budget !& 10_000_000) .sorted(by: \.title) .sorted(by: \.budget, inOrder: .descending) .result() @ftchirou / faical.dev
  39. let movies: CoreDataEntity<Movie> = !" !!# let result = try

    movies .filter(where: \.rating !$ .pg13 !% \.budget !& 10_000_000) .sorted(by: \.title) .sorted(by: \.budget, inOrder: .descending) .result() @ftchirou / faical.dev
  40. protocol Store { associatedtype Object associatedtype Failure: Error func save(_

    object: Object) !" Result<Object, Failure> func delete(_ object: Object) !" Result<Object, Failure> func execute(_ query: Query<Self>) !" Result<[Object], Failure> } @ftchirou / faical.dev
  41. protocol Store { associatedtype Object associatedtype Failure: Error !" !!#

    } extension Store { func filter(where predicate: Predicate<Object>) !$ Query<Self> { return Query(store: self, predicate: predicate, sortDescriptors: []) } } @ftchirou / faical.dev
  42. let movies: Store = !" !!# let result = movies

    .filter(where: \.rating !$ .pg13 !% \.budget !& 10_000_000) .sorted(by: \.title) .sorted(by: \.budget, inOrder: .descending) .result() @ftchirou / faical.dev
  43. struct CoreDataStore<T: NSManagedObject>: Store { private let managedObjectContext: NSManagedObjectContext "#

    ""$ func execute(_ query: Query<CoreDataStore<T"%) "& Result<[T], Error> { let request = convertToNSFetchRequest(query) do { let result = try managedObjectContext.execute(request) as! [T] return .success(result) } catch { return .failure($0) } } } @ftchirou / faical.dev
  44. struct CoreDataStore<T: NSManagedObject>: Store { private let managedObjectContext: NSManagedObjectContext "#

    ""$ func execute(_ query: Query<CoreDataStore<T"%) "& Result<[T], Error> { let request = convertToNSFetchRequest(query) do { let result = try managedObjectContext.execute(request) as! [T] return .success(result) } catch { return .failure($0) } } } @ftchirou / faical.dev
  45. struct CoreDataStore<T: NSManagedObject>: Store { "# ""$ func convertToNSFetchRequest(_ query:

    Query<CoreDataStore<T"%) "& NSFetchRequest<T> { let request = NSFetchRequest<T>(entityName: String(describing: type(of: T.self))) request.predicate = predicate.toNSPredicate() request.sortDescriptors = sortDescriptors.map { $0.toNSSortDescriptor() } return request } } @ftchirou / faical.dev
  46. struct CoreDataStore<T: NSManagedObject>: Store { "# ""$ func convertToNSFetchRequest(_ query:

    Query<CoreDataStore<T"%) "& NSFetchRequest<T> { let request = NSFetchRequest<T>(entityName: String(describing: type(of: T.self))) request.predicate = predicate.toNSPredicate() request.sortDescriptors = sortDescriptors.map { $0.toNSSortDescriptor() } return request } } @ftchirou / faical.dev
  47. extension Predicate { private func toNSPredicate() !" NSPredicate switch self

    { case let .comparison(keyPath, operator, value): return NSComparisonPredicate( leftExpression: NSExpression(forKeyPath: keyPath._kvcKeyPathString), rightExpression: NSExpression(forConstantValue: value), modifier: .direct, type: operator.toNSOperator(), options: .caseInsensitive ) !# !!$ } } } @ftchirou / faical.dev
  48. extension Predicate { private func toNSPredicate() !" NSPredicate switch self

    { case let .comparison(keyPath, operator, value): return NSComparisonPredicate( leftExpression: NSExpression(forKeyPath: keyPath._kvcKeyPathString), rightExpression: NSExpression(forConstantValue: value), modifier: .direct, type: operator.toNSOperator(), options: .caseInsensitive ) !# !!$ } } } @ftchirou / faical.dev
  49. extension Predicate { private func toNSPredicate() !" NSPredicate switch self

    { case let .comparison(keyPath, operator, value): return NSComparisonPredicate( leftExpression: NSExpression(forKeyPath: keyPath._kvcKeyPathString), rightExpression: NSExpression(forConstantValue: value), modifier: .direct, type: operator.toNSOperator(), options: .caseInsensitive ) !# !!$ } } } @ftchirou / faical.dev
  50. extension Predicate { private func toNSPredicate() !" NSPredicate switch self

    { case let .comparison(keyPath, operator, value): return NSComparisonPredicate( leftExpression: NSExpression(forKeyPath: keyPath._kvcKeyPathString), rightExpression: NSExpression(forConstantValue: value), modifier: .direct, type: operator.toNSOperator(), options: .caseInsensitive ) !# !!$ } } } @ftchirou / faical.dev
  51. extension Predicate { private func toNSPredicate() !" NSPredicate switch self

    { case let .comparison(keyPath, operator, value): return NSComparisonPredicate( leftExpression: NSExpression(forKeyPath: keyPath._kvcKeyPathString), rightExpression: NSExpression(forConstantValue: value), modifier: .direct, type: operator.toNSOperator(), options: .caseInsensitive ) !# !!$ } } } @ftchirou / faical.dev
  52. extension Operator { func toNSOperator() !" NSOperator { switch self

    { case .lessThan: return .lessThan !# !!$ case .equalTo: return .equalTo !# !!$ case greaterThan: return .greaterThan } } } @ftchirou / faical.dev
  53. extension Predicate { private func toNSPredicate() !" NSPredicate switch self

    { !# !!$ case let .and(lhs, rhs): return NSCompoundPredicate(andPredicateWithSubpredicates: [ lhs.toNSPredicate(), rhs.toNSPredicate() ]) } } } @ftchirou / faical.dev
  54. extension Predicate { private func toNSPredicate() !" NSPredicate switch self

    { !# !!$ case let .or(lhs, rhs): return NSCompoundPredicate(orPredicateWithSubpredicates: [ lhs.toNSPredicate(), rhs.toNSPredicate() ]) } } } @ftchirou / faical.dev
  55. extension Predicate { private func toNSPredicate() !" NSPredicate switch self

    { !# !!$ case let .not(predicate): return NSCompoundPredicate(notPredicateWithSubpredicate: predicate.toNSPredicate()) } } } @ftchirou / faical.dev
  56. extension Query where Store !" CoreDataStore { func toNSFetchRequest() !#

    NSFetchRequest<Store.T> { let request = NSFetchRequest<Store.T>(entityName: String(describing: T.self)) request.predicate = predicate.toNSPredicate() request.sortDescriptors = sortDescriptors.map { $0.toNSSortDescriptor() } return request } } @ftchirou / faical.dev
  57. extension SortDescriptor { func toNSSortDescriptor() !" NSSortDescriptor { return NSSortDescriptor(key:

    key._kvcKeyPathString, ascending: order !# .ascending) } } @ftchirou / faical.dev
  58. let movies: CoreDataStore<Movie> = !" !!# let result = movies

    .filter(where: \.rating !$ .pg13 !% \.budget !& 10_000_000) .sorted(by: \.title) .sorted(by: \.budget, inOrder: .descending) .result() @ftchirou / faical.dev