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
210
Other Decks in Programming
See All in Programming
AI が書く Go コードの品質を劇的に向上させる Linter: “declscope”
mpyw
0
370
UnityでSystem.Net.WebSocketsなWebSocketサーバが動かないのでUnity Monoのコードを覗いてみた / about implementing websocket server with unity mono
drumath2237
1
150
一人だけ、Kiroが静止する日
hideg
0
120
The Good Stuff, Not the Slop: Engineering High-Quality Android Apps with Modern AI Tooling
danybony
1
260
スマート反転とウェブアクセシビリティ
camiha
0
210
LoopHub - ローカルで動く GitHub で、AI と共同開発
jugyo
1
560
Intent as Code
shoppingjaws
6
1k
Starting & Sustaining Code-Based E2E Testing for Non-Coding QA Teams( #jasstniigata )
teyamagu
PRO
1
410
GKE で Pod の見方を変えたら、スケールアウト時の挙動を真に捉えられた話
stkk
0
130
AHC070解法紹介
eijirou
0
120
モジュールの視点からSwiftを読み解く #iosdc
s_shimotori
0
180
そのリトライ、死んだコネクションを使い回していませんか ── GoのHTTPクライアントとHTTP/2を実プロダクト障害から学び直す
myus4a
0
150
Featured
See All Featured
The browser strikes back
jonoalderson
0
1.7k
Building a Scalable Design System with Sketch
lauravandoore
464
34k
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.8k
Mind Mapping
helmedeiros
1
360
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.9k
Documentation Writing (for coders)
carmenintech
77
5.5k
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
200
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
68
57k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
The Curse of the Amulet
leimatthew05
3
15k
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
1
1.6k
A better future with KSS
kneath
240
18k
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