Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Designing modern data access layers in Swift
Search
Faiçal Tchirou
February 20, 2020
Programming
150
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Designing modern data access layers in Swift
Faiçal Tchirou
February 20, 2020
More Decks by Faiçal Tchirou
See All by Faiçal Tchirou
Interfaces and abstract classes in Java
ftchirou
0
140
Reactive Programming on Android with RxJava
ftchirou
1
220
Other Decks in Programming
See All in Programming
Herb in Rails 8.2: Your ERB views, now HTML-aware @ Kaigi on Rails 2026, Tokyo, Japan
marcoroth
2
220
ChromeでローカルLLM
ryu1013
0
140
ソニーのクラウド共通基盤の変遷とAI時代の開発スタイルに合わせた進化 / The Journey of Sony’s Common Cloud Platform and Its Evolution for AI-Native Development
kenjiyoneyama
0
270
HTML-in-Canvasで作る未来のWeb UI表現
clockmaker
5
5.1k
IBM Bob Dojo #1 仕様駆動開発入門
oniak3ibm
PRO
0
390
ActionController meets Protocol Buffers
sorah
0
530
Railsのためのパスキー実践入門 〜マルチテナントSaaSでの意思決定〜
utakaha
2
560
パズルゲームの作り方 / how to make puzzle games
kaityo256
PRO
2
290
AmebaブログはKMPから卒業します
kmars0605
0
190
GemmaをJevのように使ってみる / Use Gemma like Jev
kishida
5
790
動作中のプログラムの中身をリアルタイムに覗く / Realtime Debugger for CSharp with Roslyn
prota
1
1.9k
IBM Bob でAI駆動開発『QC-Playbook』
ichirokano2
0
110
Featured
See All Featured
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
2
330
The World Runs on Bad Software
bkeepers
PRO
72
12k
Making Projects Easy
brettharned
120
6.8k
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
360
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
3
1.3k
Mind Mapping
helmedeiros
1
400
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
520
Building an army of robots
kneath
307
47k
Leading Effective Engineering Teams in the AI Era
addyosmani
9
2.7k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
AI: The stuff that nobody shows you
jnunemaker
PRO
10
1.2k
Self-Hosted WebAssembly Runtime for Runtime-Neutral Checkpoint/Restore in Edge–Cloud Continuum
chikuwait
0
890
Transcript
Designing modern data access layers in Swift @ftchirou / faical.dev
Let's redesign CoreData @ftchirou / faical.dev
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
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
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
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
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
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
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
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
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
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
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
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
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
Type-Safe Predicates @ftchirou / faical.dev
enum Predicate<T> { } @ftchirou / faical.dev
enum Predicate<T> { case comparison(PartialKeyPath<T>, Operator, Primitive) } @ftchirou /
faical.dev
enum Predicate<T> { case comparison(PartialKeyPath<T>, Operator, Primitive) } enum Operator
{ case lessThan case lessThanOrEqualTo case equalTo case greaterThanOrEqualTo case greaterThan } @ftchirou / faical.dev
enum Predicate<T> { case comparison(PartialKeyPath<T>, Operator, Primitive) } protocol Primitive
{ var type: Type { get } } @ftchirou / faical.dev
indirect enum Type { case int case bool case string
case date !" !!# case representable(Primitive) } @ftchirou / faical.dev
indirect enum Type { case int case bool case string
case date !" !!# case representable(Primitive) } @ftchirou / faical.dev
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
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
func !" <T, U: Equatable & Primitive> (lhs: KeyPath<T, U>,
rhs: U) !$ Predicate<T> { return .comparison(lhs, .equalTo, rhs) } @ftchirou / faical.dev
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
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
indirect enum Predicate<T> { case comparison(PartialKeyPath<T>, Operator, Primitive) case and(Predicate<T>,
Predicate<T>) } @ftchirou / faical.dev
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
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
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
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
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
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
Type-Safe Sort Descriptors @ftchirou / faical.dev
struct SortDescriptor<T> { enum Order { case ascending case descending
} let key: PartialKeyPath<T> let order: Order } @ftchirou / faical.dev
struct SortDescriptor<T> { enum Order { case ascending case descending
} let key: PartialKeyPath<T> let order: Order } @ftchirou / faical.dev
struct SortDescriptor<T> { enum Order { case ascending case descending
} let key: PartialKeyPath<T> let order: Order } @ftchirou / faical.dev
let descriptor: SortDescriptor<Movie> = .init(key: \.title, order: .ascending) @ftchirou /
faical.dev
Tying it all together @ftchirou / faical.dev
struct Query<T> { let predicate: Predicate<T> let sortDescriptors: [SortDescriptor<T>] }
@ftchirou / faical.dev
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
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
Final Touches @ftchirou / faical.dev
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
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
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
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
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
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
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
Back to the real world @ftchirou / faical.dev
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
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
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
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
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
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
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
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
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
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
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
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
extension Operator { func toNSOperator() !" NSOperator { switch self
{ case .lessThan: return .lessThan !# !!$ case .equalTo: return .equalTo !# !!$ case greaterThan: return .greaterThan } } } @ftchirou / faical.dev
extension Predicate { private func toNSPredicate() !" NSPredicate switch self
{ !# !!$ case let .and(lhs, rhs): return NSCompoundPredicate(andPredicateWithSubpredicates: [ lhs.toNSPredicate(), rhs.toNSPredicate() ]) } } } @ftchirou / faical.dev
extension Predicate { private func toNSPredicate() !" NSPredicate switch self
{ !# !!$ case let .or(lhs, rhs): return NSCompoundPredicate(orPredicateWithSubpredicates: [ lhs.toNSPredicate(), rhs.toNSPredicate() ]) } } } @ftchirou / faical.dev
extension Predicate { private func toNSPredicate() !" NSPredicate switch self
{ !# !!$ case let .not(predicate): return NSCompoundPredicate(notPredicateWithSubpredicate: predicate.toNSPredicate()) } } } @ftchirou / faical.dev
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
extension SortDescriptor { func toNSSortDescriptor() !" NSSortDescriptor { return NSSortDescriptor(key:
key._kvcKeyPathString, ascending: order !# .ascending) } } @ftchirou / faical.dev
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
Thank you @ftchirou / faical.dev
Questions? @ftchirou / faical.dev