SlideShare a Scribd company logo
1 of 131
Download to read offline
The underestimated power of
KeyPaths
Vincent Pradeilles (@v_pradeilles) – Worldline 🇫🇷
KeyPaths?
KeyPaths
KeyPaths were introduced with Swift 4

They are a way to defer a call to the getter/setter of a property

let countKeyPath: KeyPath<String, Int> = String.count // or .count
let string = "Hello, Swift Heroes!"
string[keyPath: countKeyPath] // 20
They perform the same job than a closure, but with less $0 hanging around
Underestimated?
Let’s look at some examples!
Data Manipulation
people.sorted(by: { $0.lastName < $1.lastName })
.filter({ $0.isOverEighteen })
.map({ $0.lastName })
people.sorted(by: .lastName)
.filter(.isOverEighteen)
.map(.lastName)
How to implement it?
How to implement it?
extension Sequence {
func map<T>(_ attribute: KeyPath<Element, T>) -> [T] {
return map { $0[keyPath: attribute] }
}
}
How to implement it?
extension Sequence {
func map<T>(_ attribute: KeyPath<Element, T>) -> [T] {
return map { $0[keyPath: attribute] }
}
}
How to implement it?
extension Sequence {
func map<T>(_ attribute: KeyPath<Element, T>) -> [T] {
return map { $0[keyPath: attribute] }
}
}
How to implement it?
extension Sequence {
func map<T>(_ attribute: KeyPath<Element, T>) -> [T] {
return map { $0[keyPath: attribute] }
}
}
How to implement it?
extension Sequence {
func map<T>(_ attribute: KeyPath<Element, T>) -> [T] {
return map { $0[keyPath: attribute] }
}
}
people.map(.lastName)
How to implement it?
extension Sequence {
func sorted<T: Comparable>(by attribute: KeyPath<Element, T>) -> [Element] {
return sorted(by: { (elm1, elm2) -> Bool in
return elm1[keyPath: attribute] < elm2[keyPath: attribute]
})
}
}
How to implement it?
extension Sequence {
func sorted<T: Comparable>(by attribute: KeyPath<Element, T>) -> [Element] {
return sorted(by: { (elm1, elm2) -> Bool in
return elm1[keyPath: attribute] < elm2[keyPath: attribute]
})
}
}
How to implement it?
extension Sequence {
func sorted<T: Comparable>(by attribute: KeyPath<Element, T>) -> [Element] {
return sorted(by: { (elm1, elm2) -> Bool in
return elm1[keyPath: attribute] < elm2[keyPath: attribute]
})
}
}
How to implement it?
extension Sequence {
func sorted<T: Comparable>(by attribute: KeyPath<Element, T>) -> [Element] {
return sorted(by: { (elm1, elm2) -> Bool in
return elm1[keyPath: attribute] < elm2[keyPath: attribute]
})
}
}
How to implement it?
extension Sequence {
func sorted<T: Comparable>(by attribute: KeyPath<Element, T>) -> [Element] {
return sorted(by: { (elm1, elm2) -> Bool in
return elm1[keyPath: attribute] < elm2[keyPath: attribute]
})
}
}
How to implement it?
extension Sequence {
func sorted<T: Comparable>(by attribute: KeyPath<Element, T>) -> [Element] {
return sorted(by: { (elm1, elm2) -> Bool in
return elm1[keyPath: attribute] < elm2[keyPath: attribute]
})
}
}
How to implement it?
extension Sequence {
func sorted<T: Comparable>(by attribute: KeyPath<Element, T>) -> [Element] {
return sorted(by: { (elm1, elm2) -> Bool in
return elm1[keyPath: attribute] < elm2[keyPath: attribute]
})
}
}
people.sorted(by: .age)
Same idea goes for all classic
functions, like min, max, sum,
average, etc.
And if you don’t want to write
those wrappers…
You don’t even have to!
From KeyPath to getter function
func get<Type, Property>(_ keyPath: KeyPath<Type, Property>)
-> (Type) -> Property {
return { obj in obj[keyPath: keyPath] }
}
From KeyPath to getter function
func get<Type, Property>(_ keyPath: KeyPath<Type, Property>)
-> (Type) -> Property {
return { obj in obj[keyPath: keyPath] }
}
From KeyPath to getter function
func get<Type, Property>(_ keyPath: KeyPath<Type, Property>)
-> (Type) -> Property {
return { obj in obj[keyPath: keyPath] }
}
From KeyPath to getter function
func get<Type, Property>(_ keyPath: KeyPath<Type, Property>)
-> (Type) -> Property {
return { obj in obj[keyPath: keyPath] }
}
From KeyPath to getter function
func get<Type, Property>(_ keyPath: KeyPath<Type, Property>)
-> (Type) -> Property {
return { obj in obj[keyPath: keyPath] }
}
people.filter(get(.isOverEighteen))
An operator can even make the
syntax shorter!
Defining an operator
prefix operator ^
people.map(^.lastName)
prefix func ^ <Element, Attribute>(_ keyPath: KeyPath<Element, Attribute>)
-> (Element) -> Attribute {
return get(keyPath)
}
It’s not always this simple…
people.sorted(by: { $0.lastName < $1.lastName })
Just build another helper
func their<Type, T: Comparable>(_ keyPath: KeyPath<Type, T>,
comparedWith comparator: @escaping (T, T) -> Bool = (<))
-> (Type, Type) -> Bool {
return { obj1, obj2 in
return comparator(obj1[keyPath: keyPath], obj2[keyPath: keyPath])
}
}
Just build another helper
func their<Type, T: Comparable>(_ keyPath: KeyPath<Type, T>,
comparedWith comparator: @escaping (T, T) -> Bool = (<))
-> (Type, Type) -> Bool {
return { obj1, obj2 in
return comparator(obj1[keyPath: keyPath], obj2[keyPath: keyPath])
}
}
Just build another helper
func their<Type, T: Comparable>(_ keyPath: KeyPath<Type, T>,
comparedWith comparator: @escaping (T, T) -> Bool = (<))
-> (Type, Type) -> Bool {
return { obj1, obj2 in
return comparator(obj1[keyPath: keyPath], obj2[keyPath: keyPath])
}
}
Just build another helper
func their<Type, T: Comparable>(_ keyPath: KeyPath<Type, T>,
comparedWith comparator: @escaping (T, T) -> Bool = (<))
-> (Type, Type) -> Bool {
return { obj1, obj2 in
return comparator(obj1[keyPath: keyPath], obj2[keyPath: keyPath])
}
}
Just build another helper
func their<Type, T: Comparable>(_ keyPath: KeyPath<Type, T>,
comparedWith comparator: @escaping (T, T) -> Bool = (<))
-> (Type, Type) -> Bool {
return { obj1, obj2 in
return comparator(obj1[keyPath: keyPath], obj2[keyPath: keyPath])
}
}
Just build another helper
func their<Type, T: Comparable>(_ keyPath: KeyPath<Type, T>,
comparedWith comparator: @escaping (T, T) -> Bool = (<))
-> (Type, Type) -> Bool {
return { obj1, obj2 in
return comparator(obj1[keyPath: keyPath], obj2[keyPath: keyPath])
}
}
Just build another helper
func their<Type, T: Comparable>(_ keyPath: KeyPath<Type, T>,
comparedWith comparator: @escaping (T, T) -> Bool = (<))
-> (Type, Type) -> Bool {
return { obj1, obj2 in
return comparator(obj1[keyPath: keyPath], obj2[keyPath: keyPath])
}
}
Just build another helper
func their<Type, T: Comparable>(_ keyPath: KeyPath<Type, T>,
comparedWith comparator: @escaping (T, T) -> Bool = (<))
-> (Type, Type) -> Bool {
return { obj1, obj2 in
return comparator(obj1[keyPath: keyPath], obj2[keyPath: keyPath])
}
}
people.sorted(by: { $0.lastName < $1.lastName })
people.sorted(by: their(.lastName))
people.sorted(by: their(.lastName, comparedWith: >))
🎉
Let’s take it one step further:
DSLs
Predicates
NSPredicate is great, but it’s a stringly-typed API.

NSPredicate(format: "firstName = 'Bob'")
NSPredicate(format: "lastName = %@", userLastName)
And it only works with code that is exposed to Objective-C 😩
Wouldn’t it be cool to have a
type-safe predicate system?
Predicates
We want to be able to write complex predicates, like this one:

people.select(where: .age <= 22 && .lastName.count > 10)
To do so, we need ways to:

• Define what predicates are

• Construct them

• Combine them

• Evaluate them
Defining Predicates
struct Predicate<Element> {
private let condition: (Element) -> Bool
func evaluate(for element: Element) -> Bool {
return condition(element)
}
}
Constructing Predicates
func <= <Element, T: Comparable>(_ attribute: KeyPath<Element, T>,
_ constant: T)
-> Predicate<Element> {
return Predicate(condition: { value in value[keyPath: attribute] <= constant })
}
contacts.select(where: .age <= 22 && .lastName.count > 10)
Constructing Predicates
func <= <Element, T: Comparable>(_ attribute: KeyPath<Element, T>,
_ constant: T)
-> Predicate<Element> {
return Predicate(condition: { value in value[keyPath: attribute] <= constant })
}
contacts.select(where: .age <= 22 && .lastName.count > 10)
Constructing Predicates
func <= <Element, T: Comparable>(_ attribute: KeyPath<Element, T>,
_ constant: T)
-> Predicate<Element> {
return Predicate(condition: { value in value[keyPath: attribute] <= constant })
}
contacts.select(where: .age <= 22 && .lastName.count > 10)
Constructing Predicates
func <= <Element, T: Comparable>(_ attribute: KeyPath<Element, T>,
_ constant: T)
-> Predicate<Element> {
return Predicate(condition: { value in value[keyPath: attribute] <= constant })
}
contacts.select(where: .age <= 22 && .lastName.count > 10)
Constructing Predicates
func <= <Element, T: Comparable>(_ attribute: KeyPath<Element, T>,
_ constant: T)
-> Predicate<Element> {
return Predicate(condition: { value in value[keyPath: attribute] <= constant })
}
contacts.select(where: .age <= 22 && .lastName.count > 10)
Constructing Predicates
func <= <Element, T: Comparable>(_ attribute: KeyPath<Element, T>,
_ constant: T)
-> Predicate<Element> {
return Predicate(condition: { value in value[keyPath: attribute] <= constant })
}
contacts.select(where: .age <= 22 && .lastName.count > 10)
Constructing Predicates
func <= <Element, T: Comparable>(_ attribute: KeyPath<Element, T>,
_ constant: T)
-> Predicate<Element> {
return Predicate(condition: { value in value[keyPath: attribute] <= constant })
}
contacts.select(where: .age <= 22 && .lastName.count > 10)
func > <Element, T: Comparable>(_ attribute: KeyPath<Element, T>,
_ constant: T)
-> Predicate<Element> {
return Predicate(condition: { value in value[keyPath: attribute] > constant })
}
Constructing Predicates
func <= <Element, T: Comparable>(_ attribute: KeyPath<Element, T>,
_ constant: T)
-> Predicate<Element> {
return Predicate(condition: { value in value[keyPath: attribute] <= constant })
}
contacts.select(where: .age <= 22 && .lastName.count > 10)
func > <Element, T: Comparable>(_ attribute: KeyPath<Element, T>,
_ constant: T)
-> Predicate<Element> {
return Predicate(condition: { value in value[keyPath: attribute] > constant })
}
Combining Predicates
func && <Element> (_ leftPredicate: Predicate<Element>,
_ rightPredicate: Predicate<Element>)
-> Predicate<Element> {
return Predicate(condition: { value in
leftPredicate.evaluate(for: value) && rightPredicate.evaluate(for: value)
})
}
contacts.select(where: .age <= 22 && .lastName.count > 10)
Evaluating Predicates
extension Sequence {
func select(where predicate: Predicate<Element>) -> [Element] {
return filter { element in predicate.evaluate(for: element) }
}
}
That’s all you need!
people.select(where: .age <= 22 && .lastName.count > 10)
people.select(where: 4...18 ~= .age)
people.first(where: .age < 18)
people.contains(where: .lastName.count > 10)
You can even go for more
fancy stuff…
Switching with Predicates
func ~= <Element>(_ lhs: KeyPathPredicate<Element>, rhs: Element) -> Bool {
return lhs.evaluate(for: rhs)
}
Switching with Predicates
func ~= <Element>(_ lhs: KeyPathPredicate<Element>, rhs: Element) -> Bool {
return lhs.evaluate(for: rhs)
}
switch person {
case .firstName == "Charlie":
print("I'm Charlie!")
fallthrough
case .age < 18:
print("I'm not an adult...")
fallthrough
default:
break
}
💪
Now, let’s look at other
possible applications
Builder Pattern
Builder Pattern
Provides separation between building and using an object
Builder Pattern
let label = UILabel()
label.textColor = .red
label.text = "Hello Swift Heroes!"
label.textAlignment = .center
label.layer.cornerRadius = 5
// do something with label
view.addSubview(label)
Builder Pattern
protocol Buildable {}
Builder Pattern
protocol Buildable {}
extension Buildable where Self: AnyObject {
func with<T>(_ property: ReferenceWritableKeyPath<Self, T>,
setTo value: T) -> Self {
self[keyPath: property] = value
return self
}
}
Builder Pattern
protocol Buildable {}
extension Buildable where Self: AnyObject {
func with<T>(_ property: ReferenceWritableKeyPath<Self, T>,
setTo value: T) -> Self {
self[keyPath: property] = value
return self
}
}
Builder Pattern
protocol Buildable {}
extension Buildable where Self: AnyObject {
func with<T>(_ property: ReferenceWritableKeyPath<Self, T>,
setTo value: T) -> Self {
self[keyPath: property] = value
return self
}
}
Builder Pattern
protocol Buildable {}
extension Buildable where Self: AnyObject {
func with<T>(_ property: ReferenceWritableKeyPath<Self, T>,
setTo value: T) -> Self {
self[keyPath: property] = value
return self
}
}
Builder Pattern
protocol Buildable {}
extension Buildable where Self: AnyObject {
func with<T>(_ property: ReferenceWritableKeyPath<Self, T>,
setTo value: T) -> Self {
self[keyPath: property] = value
return self
}
}
Builder Pattern
protocol Buildable {}
extension Buildable where Self: AnyObject {
func with<T>(_ property: ReferenceWritableKeyPath<Self, T>,
setTo value: T) -> Self {
self[keyPath: property] = value
return self
}
}
Builder Pattern
protocol Buildable {}
extension Buildable where Self: AnyObject {
func with<T>(_ property: ReferenceWritableKeyPath<Self, T>,
setTo value: T) -> Self {
self[keyPath: property] = value
return self
}
}
Builder Pattern
protocol Buildable {}
extension Buildable where Self: AnyObject {
func with<T>(_ property: ReferenceWritableKeyPath<Self, T>,
setTo value: T) -> Self {
self[keyPath: property] = value
return self
}
}
extension NSObject: Buildable { }
Builder Pattern
let label = UILabel()
.with(.textColor, setTo: .red)
.with(.text, setTo: "Hello Swift Heroes!")
.with(.textAlignment, setTo: .center)
.with(.layer.cornerRadius, setTo: 5)
// do something with label
view.addSubview(label)
Identifiable Pattern
Identifiable Pattern
Enables equality based on an ID
Identifiable Pattern
protocol Identifiable: Equatable {
associatedtype ID: Equatable
static var idKeyPath: KeyPath<Self, ID> { get }
}
Identifiable Pattern
protocol Identifiable: Equatable {
associatedtype ID: Equatable
static var idKeyPath: KeyPath<Self, ID> { get }
}
extension Identifiable {
static func == (lhs: Self, rhs: Self) -> Bool {
return lhs[keyPath: Self.idKeyPath] == rhs[keyPath: Self.idKeyPath]
}
}
Identifiable Pattern
struct User: Identifiable {
static let idKeyPath = User.id
let id: String
let firstName: String
let lastName: String
}
Identifiable Pattern
let user1 = User(id: "1", firstName: "John", lastName: "Lennon")
let user2 = User(id: "2", firstName: "Ringo", lastName: "Starr")
user1 == user2 // false
let user3 = User(id: "2", firstName: "Paul", lastName: "McCartney")
user2 == user3 // true
Validable Pattern
Validable Pattern
Performs property-wise checks
Validable Pattern
struct Validator<T> {
let validee: T
let validator: (T) -> Bool
func validate() -> Bool {
return validator(validee)
}
}
Validable Pattern
protocol Validable { }
Validable Pattern
protocol Validable { }
extension Validable {
func add<Property>(validation: @escaping (Property) -> Bool,
for property: KeyPath<Self, Property>)
-> Validator<Self> {
return Validator<Self>(validee: self,
validator: { validation($0[keyPath: property]) })
}
}
Validable Pattern
protocol Validable { }
extension Validable {
func add<Property>(validation: @escaping (Property) -> Bool,
for property: KeyPath<Self, Property>)
-> Validator<Self> {
return Validator<Self>(validee: self,
validator: { validation($0[keyPath: property]) })
}
}
Validable Pattern
protocol Validable { }
extension Validable {
func add<Property>(validation: @escaping (Property) -> Bool,
for property: KeyPath<Self, Property>)
-> Validator<Self> {
return Validator<Self>(validee: self,
validator: { validation($0[keyPath: property]) })
}
}
Validable Pattern
protocol Validable { }
extension Validable {
func add<Property>(validation: @escaping (Property) -> Bool,
for property: KeyPath<Self, Property>)
-> Validator<Self> {
return Validator<Self>(validee: self,
validator: { validation($0[keyPath: property]) })
}
}
Validable Pattern
protocol Validable { }
extension Validable {
func add<Property>(validation: @escaping (Property) -> Bool,
for property: KeyPath<Self, Property>)
-> Validator<Self> {
return Validator<Self>(validee: self,
validator: { validation($0[keyPath: property]) })
}
}
Validable Pattern
protocol Validable { }
extension Validable {
func add<Property>(validation: @escaping (Property) -> Bool,
for property: KeyPath<Self, Property>)
-> Validator<Self> {
return Validator<Self>(validee: self,
validator: { validation($0[keyPath: property]) })
}
}
Validable Pattern
protocol Validable { }
extension Validable {
func add<Property>(validation: @escaping (Property) -> Bool,
for property: KeyPath<Self, Property>)
-> Validator<Self> {
return Validator<Self>(validee: self,
validator: { validation($0[keyPath: property]) })
}
}
Validable Pattern
extension Validator {
func add<Property>(validation: @escaping (Property) -> Bool,
for property: KeyPath<T, Property>)
-> Validator<T> {
return Validator<T>(validee: self.validee,
validator: { self.validate() &&
validation($0[keyPath: property]) })
}
}
Validable Pattern
extension Validator {
func add<Property>(validation: @escaping (Property) -> Bool,
for property: KeyPath<T, Property>)
-> Validator<T> {
return Validator<T>(validee: self.validee,
validator: { self.validate() &&
validation($0[keyPath: property]) })
}
}
Validable Pattern
extension Validator {
func add<Property>(validation: @escaping (Property) -> Bool,
for property: KeyPath<T, Property>)
-> Validator<T> {
return Validator<T>(validee: self.validee,
validator: { self.validate() &&
validation($0[keyPath: property]) })
}
}
Validable Pattern
extension Validator {
func add<Property>(validation: @escaping (Property) -> Bool,
for property: KeyPath<T, Property>)
-> Validator<T> {
return Validator<T>(validee: self.validee,
validator: { self.validate() &&
validation($0[keyPath: property]) })
}
}
Validable Pattern
let user1 = User(id: "1", firstName: "John", lastName: "Lennon")
let user2 = User(id: "2", firstName: "Ringo", lastName: "Starr")
Validable Pattern
let user1 = User(id: "1", firstName: "John", lastName: "Lennon")
let user2 = User(id: "2", firstName: "Ringo", lastName: "Starr")
extension User: Validable { }
Validable Pattern
let user1 = User(id: "1", firstName: "John", lastName: "Lennon")
let user2 = User(id: "2", firstName: "Ringo", lastName: "Starr")
extension User: Validable { }
user1.add(validation: { !$0.isEmpty }, for: .id)
.add(validation: { $0 == "Lennon" }, for: .lastName)
.validate() // true
Validable Pattern
let user1 = User(id: "1", firstName: "John", lastName: "Lennon")
let user2 = User(id: "2", firstName: "Ringo", lastName: "Starr")
extension User: Validable { }
user1.add(validation: { !$0.isEmpty }, for: .id)
.add(validation: { $0 == "Lennon" }, for: .lastName)
.validate() // true
user2.add(validation: { !$0.isEmpty }, for: .id)
.add(validation: { $0 == "Lennon" }, for: .lastName)
.validate() // false
(Of course, you could also use predicates instead of closures 😉)
Binding Pattern
Binding Pattern
Sets up a data-flow
Binding Pattern
class Binding<Destination: AnyObject>: NSObject {
weak var source: UITextField?
weak var destination: Destination?
var property: WritableKeyPath<Destination, String?>
}
Binding Pattern
class Binding<Destination: AnyObject>: NSObject {
weak var source: UITextField?
weak var destination: Destination?
var property: WritableKeyPath<Destination, String?>
}
Binding Pattern
class Binding<Destination: AnyObject>: NSObject {
weak var source: UITextField?
weak var destination: Destination?
var property: WritableKeyPath<Destination, String?>
}
Binding Pattern
class Binding<Destination: AnyObject>: NSObject {
weak var source: UITextField?
weak var destination: Destination?
var property: WritableKeyPath<Destination, String?>
}
Binding Pattern
class Binding<Destination: AnyObject>: NSObject {
weak var source: UITextField?
weak var destination: Destination?
var property: WritableKeyPath<Destination, String?>
init(source: UITextField,
destination: Destination,
property: WritableKeyPath<Destination, String?>) {
self.source = source
self.destination = destination
self.property = property
super.init()
self.source?.addTarget(self,
action: #selector(textFieldDidChange),
for: .editingChanged)
}
}
Binding Pattern
class Binding<Destination: AnyObject>: NSObject {
weak var source: UITextField?
weak var destination: Destination?
var property: WritableKeyPath<Destination, String?>
init(source: UITextField,
destination: Destination,
property: WritableKeyPath<Destination, String?>) {
self.source = source
self.destination = destination
self.property = property
super.init()
self.source?.addTarget(self,
action: #selector(textFieldDidChange),
for: .editingChanged)
}
}
Binding Pattern
class Binding<Destination: AnyObject>: NSObject {
weak var source: UITextField?
weak var destination: Destination?
var property: WritableKeyPath<Destination, String?>
init(source: UITextField,
destination: Destination,
property: WritableKeyPath<Destination, String?>) {
self.source = source
self.destination = destination
self.property = property
super.init()
self.source?.addTarget(self,
action: #selector(textFieldDidChange),
for: .editingChanged)
}
}
Binding Pattern
class Binding<Destination: AnyObject>: NSObject {
weak var source: UITextField?
weak var destination: Destination?
var property: WritableKeyPath<Destination, String?>
init(source: UITextField,
destination: Destination,
property: WritableKeyPath<Destination, String?>) {
self.source = source
self.destination = destination
self.property = property
super.init()
self.source?.addTarget(self,
action: #selector(textFieldDidChange),
for: .editingChanged)
}
}
Binding Pattern
class Binding<Destination: AnyObject>: NSObject {
weak var source: UITextField?
weak var destination: Destination?
var property: WritableKeyPath<Destination, String?>
init(source: UITextField,
destination: Destination,
property: WritableKeyPath<Destination, String?>) {
self.source = source
self.destination = destination
self.property = property
super.init()
self.source?.addTarget(self,
action: #selector(textFieldDidChange),
for: .editingChanged)
}
}
Binding Pattern
class Binding<Destination: AnyObject>: NSObject {
weak var source: UITextField?
weak var destination: Destination?
var property: WritableKeyPath<Destination, String?>
init(source: UITextField,
destination: Destination,
property: WritableKeyPath<Destination, String?>) {
self.source = source
self.destination = destination
self.property = property
super.init()
self.source?.addTarget(self,
action: #selector(textFieldDidChange),
for: .editingChanged)
}
@objc func textFieldDidChange() {
destination?[keyPath: property] = source?.text
}
}
Binding Pattern
class Binding<Destination: AnyObject>: NSObject {
weak var source: UITextField?
weak var destination: Destination?
var property: WritableKeyPath<Destination, String?>
init(source: UITextField,
destination: Destination,
property: WritableKeyPath<Destination, String?>) {
self.source = source
self.destination = destination
self.property = property
super.init()
self.source?.addTarget(self,
action: #selector(textFieldDidChange),
for: .editingChanged)
}
@objc func textFieldDidChange() {
destination?[keyPath: property] = source?.text
}
}
Binding Pattern
class Binding<Destination: AnyObject>: NSObject {
weak var source: UITextField?
weak var destination: Destination?
var property: WritableKeyPath<Destination, String?>
init(source: UITextField,
destination: Destination,
property: WritableKeyPath<Destination, String?>) {
self.source = source
self.destination = destination
self.property = property
super.init()
self.source?.addTarget(self,
action: #selector(textFieldDidChange),
for: .editingChanged)
}
@objc func textFieldDidChange() {
destination?[keyPath: property] = source?.text
}
}
Binding Pattern
extension UITextField {
func bindText<Destination>(to destination: Destination,
on property: WritableKeyPath<Destination, String?>)
-> Binding<Destination> {
return Binding(source: self, destination: destination, property: property)
}
}
Binding Pattern
class ViewController: UIViewController {
lazy var viewModel: ViewModel = { ViewModel() }()
lazy var nameTextField: UITextField = { UITextField() }()
var bindings: [Binding<ViewModel>] = []
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
let binding = nameTextField.bindText(to: viewModel, on: .name)
bindings.append(binding)
}
}
Binding Pattern
class ViewController: UIViewController {
lazy var viewModel: ViewModel = { ViewModel() }()
lazy var nameTextField: UITextField = { UITextField() }()
var bindings: [Binding<ViewModel>] = []
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
let binding = nameTextField.bindText(to: viewModel, on: .name)
bindings.append(binding)
}
}
Binding Pattern
class ViewController: UIViewController {
lazy var viewModel: ViewModel = { ViewModel() }()
lazy var nameTextField: UITextField = { UITextField() }()
var bindings: [Binding<ViewModel>] = []
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
let binding = nameTextField.bindText(to: viewModel, on: .name)
bindings.append(binding)
}
}
Binding Pattern
class ViewController: UIViewController {
lazy var viewModel: ViewModel = { ViewModel() }()
lazy var nameTextField: UITextField = { UITextField() }()
var bindings: [Binding<ViewModel>] = []
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
let binding = nameTextField.bindText(to: viewModel, on: .name)
bindings.append(binding)
}
}
Binding Pattern
class ViewController: UIViewController {
lazy var viewModel: ViewModel = { ViewModel() }()
lazy var nameTextField: UITextField = { UITextField() }()
var bindings: [Binding<ViewModel>] = []
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
let binding = nameTextField.bindText(to: viewModel, on: .name)
bindings.append(binding)
}
}
😎
Recap
Recap
KeyPaths offer deferred reading/writing to a property

They work very well with generic algorithms

They allow for a very clean, declarative and expressive syntax

They are a great tool to build DSLs

They bring some nice sugar syntax to many patterns
Conclusion
Conclusion
KeyPaths are great, use them!

Big thank you to Marion Curtil and Jérôme Alves for their valuable inputs.

You liked what you saw, and you want some of it in your app?

https://github.com/vincent-pradeilles/KeyPathKit
😊
The underestimated power of
KeyPaths
Vincent Pradeilles (@v_pradeilles) – Worldline 🇫🇷

More Related Content

What's hot

Refactoring to Java 8 (Devoxx BE)
Refactoring to Java 8 (Devoxx BE)Refactoring to Java 8 (Devoxx BE)
Refactoring to Java 8 (Devoxx BE)Trisha Gee
 
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STM
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STMConcurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STM
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STMMario Fusco
 
Intro to Functional Programming
Intro to Functional ProgrammingIntro to Functional Programming
Intro to Functional ProgrammingHugo Firth
 
Modern JavaScript - Modern ES6+ Features
Modern JavaScript - Modern ES6+ FeaturesModern JavaScript - Modern ES6+ Features
Modern JavaScript - Modern ES6+ FeaturesNikki Dingding
 
Talk - Query monad
Talk - Query monad Talk - Query monad
Talk - Query monad Fabernovel
 
Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...Mario Fusco
 
Let's make a contract: the art of designing a Java API
Let's make a contract: the art of designing a Java APILet's make a contract: the art of designing a Java API
Let's make a contract: the art of designing a Java APIMario Fusco
 
Side by Side - Scala and Java Adaptations of Martin Fowler’s Javascript Refac...
Side by Side - Scala and Java Adaptations of Martin Fowler’s Javascript Refac...Side by Side - Scala and Java Adaptations of Martin Fowler’s Javascript Refac...
Side by Side - Scala and Java Adaptations of Martin Fowler’s Javascript Refac...Philip Schwarz
 
DRYing to Monad in Java8
DRYing to Monad in Java8DRYing to Monad in Java8
DRYing to Monad in Java8Dhaval Dalal
 
Learning Functional Programming Without Growing a Neckbeard
Learning Functional Programming Without Growing a NeckbeardLearning Functional Programming Without Growing a Neckbeard
Learning Functional Programming Without Growing a NeckbeardKelsey Gilmore-Innis
 
Introduction to Functional Programming in JavaScript
Introduction to Functional Programming in JavaScriptIntroduction to Functional Programming in JavaScript
Introduction to Functional Programming in JavaScripttmont
 
6. Generics. Collections. Streams
6. Generics. Collections. Streams6. Generics. Collections. Streams
6. Generics. Collections. StreamsDEVTYPE
 
Functional Programming in Javascript - IL Tech Talks week
Functional Programming in Javascript - IL Tech Talks weekFunctional Programming in Javascript - IL Tech Talks week
Functional Programming in Javascript - IL Tech Talks weekyoavrubin
 
Pragmatic Real-World Scala (short version)
Pragmatic Real-World Scala (short version)Pragmatic Real-World Scala (short version)
Pragmatic Real-World Scala (short version)Jonas Bonér
 
Functional programming in JavaScript
Functional programming in JavaScriptFunctional programming in JavaScript
Functional programming in JavaScriptJoseph Smith
 
Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosDivante
 
Chapter 8- Advanced Views and URLconfs
Chapter 8- Advanced Views and URLconfsChapter 8- Advanced Views and URLconfs
Chapter 8- Advanced Views and URLconfsVincent Chien
 
Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Map(), flatmap() and reduce() are your new best friends: simpler collections,...Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Map(), flatmap() and reduce() are your new best friends: simpler collections,...Chris Richardson
 
Category theory, Monads, and Duality in the world of (BIG) Data
Category theory, Monads, and Duality in the world of (BIG) DataCategory theory, Monads, and Duality in the world of (BIG) Data
Category theory, Monads, and Duality in the world of (BIG) Datagreenwop
 

What's hot (20)

Refactoring to Java 8 (Devoxx BE)
Refactoring to Java 8 (Devoxx BE)Refactoring to Java 8 (Devoxx BE)
Refactoring to Java 8 (Devoxx BE)
 
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STM
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STMConcurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STM
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STM
 
Intro to Functional Programming
Intro to Functional ProgrammingIntro to Functional Programming
Intro to Functional Programming
 
Modern JavaScript - Modern ES6+ Features
Modern JavaScript - Modern ES6+ FeaturesModern JavaScript - Modern ES6+ Features
Modern JavaScript - Modern ES6+ Features
 
Talk - Query monad
Talk - Query monad Talk - Query monad
Talk - Query monad
 
Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...
 
Let's make a contract: the art of designing a Java API
Let's make a contract: the art of designing a Java APILet's make a contract: the art of designing a Java API
Let's make a contract: the art of designing a Java API
 
Side by Side - Scala and Java Adaptations of Martin Fowler’s Javascript Refac...
Side by Side - Scala and Java Adaptations of Martin Fowler’s Javascript Refac...Side by Side - Scala and Java Adaptations of Martin Fowler’s Javascript Refac...
Side by Side - Scala and Java Adaptations of Martin Fowler’s Javascript Refac...
 
DRYing to Monad in Java8
DRYing to Monad in Java8DRYing to Monad in Java8
DRYing to Monad in Java8
 
Learning Functional Programming Without Growing a Neckbeard
Learning Functional Programming Without Growing a NeckbeardLearning Functional Programming Without Growing a Neckbeard
Learning Functional Programming Without Growing a Neckbeard
 
Introduction to Functional Programming in JavaScript
Introduction to Functional Programming in JavaScriptIntroduction to Functional Programming in JavaScript
Introduction to Functional Programming in JavaScript
 
Hammurabi
HammurabiHammurabi
Hammurabi
 
6. Generics. Collections. Streams
6. Generics. Collections. Streams6. Generics. Collections. Streams
6. Generics. Collections. Streams
 
Functional Programming in Javascript - IL Tech Talks week
Functional Programming in Javascript - IL Tech Talks weekFunctional Programming in Javascript - IL Tech Talks week
Functional Programming in Javascript - IL Tech Talks week
 
Pragmatic Real-World Scala (short version)
Pragmatic Real-World Scala (short version)Pragmatic Real-World Scala (short version)
Pragmatic Real-World Scala (short version)
 
Functional programming in JavaScript
Functional programming in JavaScriptFunctional programming in JavaScript
Functional programming in JavaScript
 
Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenarios
 
Chapter 8- Advanced Views and URLconfs
Chapter 8- Advanced Views and URLconfsChapter 8- Advanced Views and URLconfs
Chapter 8- Advanced Views and URLconfs
 
Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Map(), flatmap() and reduce() are your new best friends: simpler collections,...Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Map(), flatmap() and reduce() are your new best friends: simpler collections,...
 
Category theory, Monads, and Duality in the world of (BIG) Data
Category theory, Monads, and Duality in the world of (BIG) DataCategory theory, Monads, and Duality in the world of (BIG) Data
Category theory, Monads, and Duality in the world of (BIG) Data
 

Similar to The underestimated power of KeyPaths

Kotlin Advanced - Apalon Kotlin Sprint Part 3
Kotlin Advanced - Apalon Kotlin Sprint Part 3Kotlin Advanced - Apalon Kotlin Sprint Part 3
Kotlin Advanced - Apalon Kotlin Sprint Part 3Kirill Rozov
 
Cocoa Design Patterns in Swift
Cocoa Design Patterns in SwiftCocoa Design Patterns in Swift
Cocoa Design Patterns in SwiftMichele Titolo
 
Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...Codemotion
 
Round PEG, Round Hole - Parsing Functionally
Round PEG, Round Hole - Parsing FunctionallyRound PEG, Round Hole - Parsing Functionally
Round PEG, Round Hole - Parsing FunctionallySean Cribbs
 
Functions In Scala
Functions In Scala Functions In Scala
Functions In Scala Knoldus Inc.
 
A brief introduction to apply functions
A brief introduction to apply functionsA brief introduction to apply functions
A brief introduction to apply functionsNIKET CHAURASIA
 
Revision Tour 1 and 2 complete.doc
Revision Tour 1 and 2 complete.docRevision Tour 1 and 2 complete.doc
Revision Tour 1 and 2 complete.docSrikrishnaVundavalli
 
Swift 함수 커링 사용하기
Swift 함수 커링 사용하기Swift 함수 커링 사용하기
Swift 함수 커링 사용하기진성 오
 
CoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love AffairCoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love AffairMark
 
Working With JQuery Part1
Working With JQuery Part1Working With JQuery Part1
Working With JQuery Part1saydin_soft
 
Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with GroovyArturo Herrero
 
Php my sql - functions - arrays - tutorial - programmerblog.net
Php my sql - functions - arrays - tutorial - programmerblog.netPhp my sql - functions - arrays - tutorial - programmerblog.net
Php my sql - functions - arrays - tutorial - programmerblog.netProgrammer Blog
 
Rewriting Java In Scala
Rewriting Java In ScalaRewriting Java In Scala
Rewriting Java In ScalaSkills Matter
 
Generics and Inference
Generics and InferenceGenerics and Inference
Generics and InferenceRichard Fox
 
Functional Programming for OO Programmers (part 2)
Functional Programming for OO Programmers (part 2)Functional Programming for OO Programmers (part 2)
Functional Programming for OO Programmers (part 2)Calvin Cheng
 
Erlang for data ops
Erlang for data opsErlang for data ops
Erlang for data opsmnacos
 
Improving Correctness with Types Kats Conf
Improving Correctness with Types Kats ConfImproving Correctness with Types Kats Conf
Improving Correctness with Types Kats ConfIain Hull
 

Similar to The underestimated power of KeyPaths (20)

Kotlin Advanced - Apalon Kotlin Sprint Part 3
Kotlin Advanced - Apalon Kotlin Sprint Part 3Kotlin Advanced - Apalon Kotlin Sprint Part 3
Kotlin Advanced - Apalon Kotlin Sprint Part 3
 
Cocoa Design Patterns in Swift
Cocoa Design Patterns in SwiftCocoa Design Patterns in Swift
Cocoa Design Patterns in Swift
 
Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...
 
Round PEG, Round Hole - Parsing Functionally
Round PEG, Round Hole - Parsing FunctionallyRound PEG, Round Hole - Parsing Functionally
Round PEG, Round Hole - Parsing Functionally
 
Functions In Scala
Functions In Scala Functions In Scala
Functions In Scala
 
Python : Functions
Python : FunctionsPython : Functions
Python : Functions
 
A brief introduction to apply functions
A brief introduction to apply functionsA brief introduction to apply functions
A brief introduction to apply functions
 
Revision Tour 1 and 2 complete.doc
Revision Tour 1 and 2 complete.docRevision Tour 1 and 2 complete.doc
Revision Tour 1 and 2 complete.doc
 
How te bring common UI patterns to ADF
How te bring common UI patterns to ADFHow te bring common UI patterns to ADF
How te bring common UI patterns to ADF
 
Java gets a closure
Java gets a closureJava gets a closure
Java gets a closure
 
Swift 함수 커링 사용하기
Swift 함수 커링 사용하기Swift 함수 커링 사용하기
Swift 함수 커링 사용하기
 
CoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love AffairCoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love Affair
 
Working With JQuery Part1
Working With JQuery Part1Working With JQuery Part1
Working With JQuery Part1
 
Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with Groovy
 
Php my sql - functions - arrays - tutorial - programmerblog.net
Php my sql - functions - arrays - tutorial - programmerblog.netPhp my sql - functions - arrays - tutorial - programmerblog.net
Php my sql - functions - arrays - tutorial - programmerblog.net
 
Rewriting Java In Scala
Rewriting Java In ScalaRewriting Java In Scala
Rewriting Java In Scala
 
Generics and Inference
Generics and InferenceGenerics and Inference
Generics and Inference
 
Functional Programming for OO Programmers (part 2)
Functional Programming for OO Programmers (part 2)Functional Programming for OO Programmers (part 2)
Functional Programming for OO Programmers (part 2)
 
Erlang for data ops
Erlang for data opsErlang for data ops
Erlang for data ops
 
Improving Correctness with Types Kats Conf
Improving Correctness with Types Kats ConfImproving Correctness with Types Kats Conf
Improving Correctness with Types Kats Conf
 

More from Vincent Pradeilles

Solving callback hell with good old function composition
Solving callback hell with good old function compositionSolving callback hell with good old function composition
Solving callback hell with good old function compositionVincent Pradeilles
 
Taking the boilerplate out of your tests with Sourcery
Taking the boilerplate out of your tests with SourceryTaking the boilerplate out of your tests with Sourcery
Taking the boilerplate out of your tests with SourceryVincent Pradeilles
 
How to build a debug view almost for free
How to build a debug view almost for freeHow to build a debug view almost for free
How to build a debug view almost for freeVincent Pradeilles
 
An introduction to property-based testing
An introduction to property-based testingAn introduction to property-based testing
An introduction to property-based testingVincent Pradeilles
 
Implementing pseudo-keywords through Functional Programing
Implementing pseudo-keywords through Functional ProgramingImplementing pseudo-keywords through Functional Programing
Implementing pseudo-keywords through Functional ProgramingVincent Pradeilles
 
Property Wrappers or how Swift decided to become Java
Property Wrappers or how Swift decided to become JavaProperty Wrappers or how Swift decided to become Java
Property Wrappers or how Swift decided to become JavaVincent Pradeilles
 
Advanced functional programing in Swift
Advanced functional programing in SwiftAdvanced functional programing in Swift
Advanced functional programing in SwiftVincent Pradeilles
 

More from Vincent Pradeilles (10)

On-Boarding New Engineers
On-Boarding New EngineersOn-Boarding New Engineers
On-Boarding New Engineers
 
Solving callback hell with good old function composition
Solving callback hell with good old function compositionSolving callback hell with good old function composition
Solving callback hell with good old function composition
 
Taking the boilerplate out of your tests with Sourcery
Taking the boilerplate out of your tests with SourceryTaking the boilerplate out of your tests with Sourcery
Taking the boilerplate out of your tests with Sourcery
 
How to build a debug view almost for free
How to build a debug view almost for freeHow to build a debug view almost for free
How to build a debug view almost for free
 
An introduction to property-based testing
An introduction to property-based testingAn introduction to property-based testing
An introduction to property-based testing
 
Implementing pseudo-keywords through Functional Programing
Implementing pseudo-keywords through Functional ProgramingImplementing pseudo-keywords through Functional Programing
Implementing pseudo-keywords through Functional Programing
 
Property Wrappers or how Swift decided to become Java
Property Wrappers or how Swift decided to become JavaProperty Wrappers or how Swift decided to become Java
Property Wrappers or how Swift decided to become Java
 
Cocoa heads 09112017
Cocoa heads 09112017Cocoa heads 09112017
Cocoa heads 09112017
 
Advanced functional programing in Swift
Advanced functional programing in SwiftAdvanced functional programing in Swift
Advanced functional programing in Swift
 
Monads in Swift
Monads in SwiftMonads in Swift
Monads in Swift
 

Recently uploaded

React Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaReact Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaHanief Utama
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio, Inc.
 
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdfGOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdfAlina Yurenko
 
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...gurkirankumar98700
 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataBradBedford3
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...OnePlan Solutions
 
Implementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureImplementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureDinusha Kumarasiri
 
Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesPhilip Schwarz
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityNeo4j
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...stazi3110
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...soniya singh
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmSujith Sukumaran
 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWave PLM
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEEVICTOR MAESTRE RAMIREZ
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...OnePlan Solutions
 
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024StefanoLambiase
 
The Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdfThe Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdfPower Karaoke
 
Unveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsUnveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsAhmed Mohamed
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideChristina Lin
 

Recently uploaded (20)

React Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaReact Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief Utama
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
 
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdfGOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
 
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
 
Implementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureImplementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with Azure
 
Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a series
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered Sustainability
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalm
 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need It
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEE
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...
 
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
 
The Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdfThe Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdf
 
Unveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsUnveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML Diagrams
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
 

The underestimated power of KeyPaths

  • 1. The underestimated power of KeyPaths Vincent Pradeilles (@v_pradeilles) – Worldline 🇫🇷
  • 3. KeyPaths KeyPaths were introduced with Swift 4 They are a way to defer a call to the getter/setter of a property let countKeyPath: KeyPath<String, Int> = String.count // or .count let string = "Hello, Swift Heroes!" string[keyPath: countKeyPath] // 20 They perform the same job than a closure, but with less $0 hanging around
  • 5. Let’s look at some examples!
  • 7. people.sorted(by: { $0.lastName < $1.lastName }) .filter({ $0.isOverEighteen }) .map({ $0.lastName })
  • 10. How to implement it? extension Sequence { func map<T>(_ attribute: KeyPath<Element, T>) -> [T] { return map { $0[keyPath: attribute] } } }
  • 11. How to implement it? extension Sequence { func map<T>(_ attribute: KeyPath<Element, T>) -> [T] { return map { $0[keyPath: attribute] } } }
  • 12. How to implement it? extension Sequence { func map<T>(_ attribute: KeyPath<Element, T>) -> [T] { return map { $0[keyPath: attribute] } } }
  • 13. How to implement it? extension Sequence { func map<T>(_ attribute: KeyPath<Element, T>) -> [T] { return map { $0[keyPath: attribute] } } }
  • 14. How to implement it? extension Sequence { func map<T>(_ attribute: KeyPath<Element, T>) -> [T] { return map { $0[keyPath: attribute] } } } people.map(.lastName)
  • 15. How to implement it? extension Sequence { func sorted<T: Comparable>(by attribute: KeyPath<Element, T>) -> [Element] { return sorted(by: { (elm1, elm2) -> Bool in return elm1[keyPath: attribute] < elm2[keyPath: attribute] }) } }
  • 16. How to implement it? extension Sequence { func sorted<T: Comparable>(by attribute: KeyPath<Element, T>) -> [Element] { return sorted(by: { (elm1, elm2) -> Bool in return elm1[keyPath: attribute] < elm2[keyPath: attribute] }) } }
  • 17. How to implement it? extension Sequence { func sorted<T: Comparable>(by attribute: KeyPath<Element, T>) -> [Element] { return sorted(by: { (elm1, elm2) -> Bool in return elm1[keyPath: attribute] < elm2[keyPath: attribute] }) } }
  • 18. How to implement it? extension Sequence { func sorted<T: Comparable>(by attribute: KeyPath<Element, T>) -> [Element] { return sorted(by: { (elm1, elm2) -> Bool in return elm1[keyPath: attribute] < elm2[keyPath: attribute] }) } }
  • 19. How to implement it? extension Sequence { func sorted<T: Comparable>(by attribute: KeyPath<Element, T>) -> [Element] { return sorted(by: { (elm1, elm2) -> Bool in return elm1[keyPath: attribute] < elm2[keyPath: attribute] }) } }
  • 20. How to implement it? extension Sequence { func sorted<T: Comparable>(by attribute: KeyPath<Element, T>) -> [Element] { return sorted(by: { (elm1, elm2) -> Bool in return elm1[keyPath: attribute] < elm2[keyPath: attribute] }) } }
  • 21. How to implement it? extension Sequence { func sorted<T: Comparable>(by attribute: KeyPath<Element, T>) -> [Element] { return sorted(by: { (elm1, elm2) -> Bool in return elm1[keyPath: attribute] < elm2[keyPath: attribute] }) } } people.sorted(by: .age)
  • 22. Same idea goes for all classic functions, like min, max, sum, average, etc.
  • 23. And if you don’t want to write those wrappers…
  • 24. You don’t even have to!
  • 25. From KeyPath to getter function func get<Type, Property>(_ keyPath: KeyPath<Type, Property>) -> (Type) -> Property { return { obj in obj[keyPath: keyPath] } }
  • 26. From KeyPath to getter function func get<Type, Property>(_ keyPath: KeyPath<Type, Property>) -> (Type) -> Property { return { obj in obj[keyPath: keyPath] } }
  • 27. From KeyPath to getter function func get<Type, Property>(_ keyPath: KeyPath<Type, Property>) -> (Type) -> Property { return { obj in obj[keyPath: keyPath] } }
  • 28. From KeyPath to getter function func get<Type, Property>(_ keyPath: KeyPath<Type, Property>) -> (Type) -> Property { return { obj in obj[keyPath: keyPath] } }
  • 29. From KeyPath to getter function func get<Type, Property>(_ keyPath: KeyPath<Type, Property>) -> (Type) -> Property { return { obj in obj[keyPath: keyPath] } } people.filter(get(.isOverEighteen))
  • 30. An operator can even make the syntax shorter!
  • 31. Defining an operator prefix operator ^ people.map(^.lastName) prefix func ^ <Element, Attribute>(_ keyPath: KeyPath<Element, Attribute>) -> (Element) -> Attribute { return get(keyPath) }
  • 32. It’s not always this simple…
  • 34. Just build another helper func their<Type, T: Comparable>(_ keyPath: KeyPath<Type, T>, comparedWith comparator: @escaping (T, T) -> Bool = (<)) -> (Type, Type) -> Bool { return { obj1, obj2 in return comparator(obj1[keyPath: keyPath], obj2[keyPath: keyPath]) } }
  • 35. Just build another helper func their<Type, T: Comparable>(_ keyPath: KeyPath<Type, T>, comparedWith comparator: @escaping (T, T) -> Bool = (<)) -> (Type, Type) -> Bool { return { obj1, obj2 in return comparator(obj1[keyPath: keyPath], obj2[keyPath: keyPath]) } }
  • 36. Just build another helper func their<Type, T: Comparable>(_ keyPath: KeyPath<Type, T>, comparedWith comparator: @escaping (T, T) -> Bool = (<)) -> (Type, Type) -> Bool { return { obj1, obj2 in return comparator(obj1[keyPath: keyPath], obj2[keyPath: keyPath]) } }
  • 37. Just build another helper func their<Type, T: Comparable>(_ keyPath: KeyPath<Type, T>, comparedWith comparator: @escaping (T, T) -> Bool = (<)) -> (Type, Type) -> Bool { return { obj1, obj2 in return comparator(obj1[keyPath: keyPath], obj2[keyPath: keyPath]) } }
  • 38. Just build another helper func their<Type, T: Comparable>(_ keyPath: KeyPath<Type, T>, comparedWith comparator: @escaping (T, T) -> Bool = (<)) -> (Type, Type) -> Bool { return { obj1, obj2 in return comparator(obj1[keyPath: keyPath], obj2[keyPath: keyPath]) } }
  • 39. Just build another helper func their<Type, T: Comparable>(_ keyPath: KeyPath<Type, T>, comparedWith comparator: @escaping (T, T) -> Bool = (<)) -> (Type, Type) -> Bool { return { obj1, obj2 in return comparator(obj1[keyPath: keyPath], obj2[keyPath: keyPath]) } }
  • 40. Just build another helper func their<Type, T: Comparable>(_ keyPath: KeyPath<Type, T>, comparedWith comparator: @escaping (T, T) -> Bool = (<)) -> (Type, Type) -> Bool { return { obj1, obj2 in return comparator(obj1[keyPath: keyPath], obj2[keyPath: keyPath]) } }
  • 41. Just build another helper func their<Type, T: Comparable>(_ keyPath: KeyPath<Type, T>, comparedWith comparator: @escaping (T, T) -> Bool = (<)) -> (Type, Type) -> Bool { return { obj1, obj2 in return comparator(obj1[keyPath: keyPath], obj2[keyPath: keyPath]) } }
  • 45. 🎉
  • 46. Let’s take it one step further: DSLs
  • 47. Predicates NSPredicate is great, but it’s a stringly-typed API. NSPredicate(format: "firstName = 'Bob'") NSPredicate(format: "lastName = %@", userLastName) And it only works with code that is exposed to Objective-C 😩
  • 48. Wouldn’t it be cool to have a type-safe predicate system?
  • 49. Predicates We want to be able to write complex predicates, like this one: people.select(where: .age <= 22 && .lastName.count > 10) To do so, we need ways to: • Define what predicates are • Construct them • Combine them • Evaluate them
  • 50. Defining Predicates struct Predicate<Element> { private let condition: (Element) -> Bool func evaluate(for element: Element) -> Bool { return condition(element) } }
  • 51. Constructing Predicates func <= <Element, T: Comparable>(_ attribute: KeyPath<Element, T>, _ constant: T) -> Predicate<Element> { return Predicate(condition: { value in value[keyPath: attribute] <= constant }) } contacts.select(where: .age <= 22 && .lastName.count > 10)
  • 52. Constructing Predicates func <= <Element, T: Comparable>(_ attribute: KeyPath<Element, T>, _ constant: T) -> Predicate<Element> { return Predicate(condition: { value in value[keyPath: attribute] <= constant }) } contacts.select(where: .age <= 22 && .lastName.count > 10)
  • 53. Constructing Predicates func <= <Element, T: Comparable>(_ attribute: KeyPath<Element, T>, _ constant: T) -> Predicate<Element> { return Predicate(condition: { value in value[keyPath: attribute] <= constant }) } contacts.select(where: .age <= 22 && .lastName.count > 10)
  • 54. Constructing Predicates func <= <Element, T: Comparable>(_ attribute: KeyPath<Element, T>, _ constant: T) -> Predicate<Element> { return Predicate(condition: { value in value[keyPath: attribute] <= constant }) } contacts.select(where: .age <= 22 && .lastName.count > 10)
  • 55. Constructing Predicates func <= <Element, T: Comparable>(_ attribute: KeyPath<Element, T>, _ constant: T) -> Predicate<Element> { return Predicate(condition: { value in value[keyPath: attribute] <= constant }) } contacts.select(where: .age <= 22 && .lastName.count > 10)
  • 56. Constructing Predicates func <= <Element, T: Comparable>(_ attribute: KeyPath<Element, T>, _ constant: T) -> Predicate<Element> { return Predicate(condition: { value in value[keyPath: attribute] <= constant }) } contacts.select(where: .age <= 22 && .lastName.count > 10)
  • 57. Constructing Predicates func <= <Element, T: Comparable>(_ attribute: KeyPath<Element, T>, _ constant: T) -> Predicate<Element> { return Predicate(condition: { value in value[keyPath: attribute] <= constant }) } contacts.select(where: .age <= 22 && .lastName.count > 10) func > <Element, T: Comparable>(_ attribute: KeyPath<Element, T>, _ constant: T) -> Predicate<Element> { return Predicate(condition: { value in value[keyPath: attribute] > constant }) }
  • 58. Constructing Predicates func <= <Element, T: Comparable>(_ attribute: KeyPath<Element, T>, _ constant: T) -> Predicate<Element> { return Predicate(condition: { value in value[keyPath: attribute] <= constant }) } contacts.select(where: .age <= 22 && .lastName.count > 10) func > <Element, T: Comparable>(_ attribute: KeyPath<Element, T>, _ constant: T) -> Predicate<Element> { return Predicate(condition: { value in value[keyPath: attribute] > constant }) }
  • 59. Combining Predicates func && <Element> (_ leftPredicate: Predicate<Element>, _ rightPredicate: Predicate<Element>) -> Predicate<Element> { return Predicate(condition: { value in leftPredicate.evaluate(for: value) && rightPredicate.evaluate(for: value) }) } contacts.select(where: .age <= 22 && .lastName.count > 10)
  • 60. Evaluating Predicates extension Sequence { func select(where predicate: Predicate<Element>) -> [Element] { return filter { element in predicate.evaluate(for: element) } } }
  • 61. That’s all you need! people.select(where: .age <= 22 && .lastName.count > 10) people.select(where: 4...18 ~= .age) people.first(where: .age < 18) people.contains(where: .lastName.count > 10)
  • 62. You can even go for more fancy stuff…
  • 63. Switching with Predicates func ~= <Element>(_ lhs: KeyPathPredicate<Element>, rhs: Element) -> Bool { return lhs.evaluate(for: rhs) }
  • 64. Switching with Predicates func ~= <Element>(_ lhs: KeyPathPredicate<Element>, rhs: Element) -> Bool { return lhs.evaluate(for: rhs) } switch person { case .firstName == "Charlie": print("I'm Charlie!") fallthrough case .age < 18: print("I'm not an adult...") fallthrough default: break }
  • 65. 💪
  • 66. Now, let’s look at other possible applications
  • 68. Builder Pattern Provides separation between building and using an object
  • 69. Builder Pattern let label = UILabel() label.textColor = .red label.text = "Hello Swift Heroes!" label.textAlignment = .center label.layer.cornerRadius = 5 // do something with label view.addSubview(label)
  • 71. Builder Pattern protocol Buildable {} extension Buildable where Self: AnyObject { func with<T>(_ property: ReferenceWritableKeyPath<Self, T>, setTo value: T) -> Self { self[keyPath: property] = value return self } }
  • 72. Builder Pattern protocol Buildable {} extension Buildable where Self: AnyObject { func with<T>(_ property: ReferenceWritableKeyPath<Self, T>, setTo value: T) -> Self { self[keyPath: property] = value return self } }
  • 73. Builder Pattern protocol Buildable {} extension Buildable where Self: AnyObject { func with<T>(_ property: ReferenceWritableKeyPath<Self, T>, setTo value: T) -> Self { self[keyPath: property] = value return self } }
  • 74. Builder Pattern protocol Buildable {} extension Buildable where Self: AnyObject { func with<T>(_ property: ReferenceWritableKeyPath<Self, T>, setTo value: T) -> Self { self[keyPath: property] = value return self } }
  • 75. Builder Pattern protocol Buildable {} extension Buildable where Self: AnyObject { func with<T>(_ property: ReferenceWritableKeyPath<Self, T>, setTo value: T) -> Self { self[keyPath: property] = value return self } }
  • 76. Builder Pattern protocol Buildable {} extension Buildable where Self: AnyObject { func with<T>(_ property: ReferenceWritableKeyPath<Self, T>, setTo value: T) -> Self { self[keyPath: property] = value return self } }
  • 77. Builder Pattern protocol Buildable {} extension Buildable where Self: AnyObject { func with<T>(_ property: ReferenceWritableKeyPath<Self, T>, setTo value: T) -> Self { self[keyPath: property] = value return self } }
  • 78. Builder Pattern protocol Buildable {} extension Buildable where Self: AnyObject { func with<T>(_ property: ReferenceWritableKeyPath<Self, T>, setTo value: T) -> Self { self[keyPath: property] = value return self } } extension NSObject: Buildable { }
  • 79. Builder Pattern let label = UILabel() .with(.textColor, setTo: .red) .with(.text, setTo: "Hello Swift Heroes!") .with(.textAlignment, setTo: .center) .with(.layer.cornerRadius, setTo: 5) // do something with label view.addSubview(label)
  • 82. Identifiable Pattern protocol Identifiable: Equatable { associatedtype ID: Equatable static var idKeyPath: KeyPath<Self, ID> { get } }
  • 83. Identifiable Pattern protocol Identifiable: Equatable { associatedtype ID: Equatable static var idKeyPath: KeyPath<Self, ID> { get } } extension Identifiable { static func == (lhs: Self, rhs: Self) -> Bool { return lhs[keyPath: Self.idKeyPath] == rhs[keyPath: Self.idKeyPath] } }
  • 84. Identifiable Pattern struct User: Identifiable { static let idKeyPath = User.id let id: String let firstName: String let lastName: String }
  • 85. Identifiable Pattern let user1 = User(id: "1", firstName: "John", lastName: "Lennon") let user2 = User(id: "2", firstName: "Ringo", lastName: "Starr") user1 == user2 // false let user3 = User(id: "2", firstName: "Paul", lastName: "McCartney") user2 == user3 // true
  • 88. Validable Pattern struct Validator<T> { let validee: T let validator: (T) -> Bool func validate() -> Bool { return validator(validee) } }
  • 90. Validable Pattern protocol Validable { } extension Validable { func add<Property>(validation: @escaping (Property) -> Bool, for property: KeyPath<Self, Property>) -> Validator<Self> { return Validator<Self>(validee: self, validator: { validation($0[keyPath: property]) }) } }
  • 91. Validable Pattern protocol Validable { } extension Validable { func add<Property>(validation: @escaping (Property) -> Bool, for property: KeyPath<Self, Property>) -> Validator<Self> { return Validator<Self>(validee: self, validator: { validation($0[keyPath: property]) }) } }
  • 92. Validable Pattern protocol Validable { } extension Validable { func add<Property>(validation: @escaping (Property) -> Bool, for property: KeyPath<Self, Property>) -> Validator<Self> { return Validator<Self>(validee: self, validator: { validation($0[keyPath: property]) }) } }
  • 93. Validable Pattern protocol Validable { } extension Validable { func add<Property>(validation: @escaping (Property) -> Bool, for property: KeyPath<Self, Property>) -> Validator<Self> { return Validator<Self>(validee: self, validator: { validation($0[keyPath: property]) }) } }
  • 94. Validable Pattern protocol Validable { } extension Validable { func add<Property>(validation: @escaping (Property) -> Bool, for property: KeyPath<Self, Property>) -> Validator<Self> { return Validator<Self>(validee: self, validator: { validation($0[keyPath: property]) }) } }
  • 95. Validable Pattern protocol Validable { } extension Validable { func add<Property>(validation: @escaping (Property) -> Bool, for property: KeyPath<Self, Property>) -> Validator<Self> { return Validator<Self>(validee: self, validator: { validation($0[keyPath: property]) }) } }
  • 96. Validable Pattern protocol Validable { } extension Validable { func add<Property>(validation: @escaping (Property) -> Bool, for property: KeyPath<Self, Property>) -> Validator<Self> { return Validator<Self>(validee: self, validator: { validation($0[keyPath: property]) }) } }
  • 97. Validable Pattern extension Validator { func add<Property>(validation: @escaping (Property) -> Bool, for property: KeyPath<T, Property>) -> Validator<T> { return Validator<T>(validee: self.validee, validator: { self.validate() && validation($0[keyPath: property]) }) } }
  • 98. Validable Pattern extension Validator { func add<Property>(validation: @escaping (Property) -> Bool, for property: KeyPath<T, Property>) -> Validator<T> { return Validator<T>(validee: self.validee, validator: { self.validate() && validation($0[keyPath: property]) }) } }
  • 99. Validable Pattern extension Validator { func add<Property>(validation: @escaping (Property) -> Bool, for property: KeyPath<T, Property>) -> Validator<T> { return Validator<T>(validee: self.validee, validator: { self.validate() && validation($0[keyPath: property]) }) } }
  • 100. Validable Pattern extension Validator { func add<Property>(validation: @escaping (Property) -> Bool, for property: KeyPath<T, Property>) -> Validator<T> { return Validator<T>(validee: self.validee, validator: { self.validate() && validation($0[keyPath: property]) }) } }
  • 101. Validable Pattern let user1 = User(id: "1", firstName: "John", lastName: "Lennon") let user2 = User(id: "2", firstName: "Ringo", lastName: "Starr")
  • 102. Validable Pattern let user1 = User(id: "1", firstName: "John", lastName: "Lennon") let user2 = User(id: "2", firstName: "Ringo", lastName: "Starr") extension User: Validable { }
  • 103. Validable Pattern let user1 = User(id: "1", firstName: "John", lastName: "Lennon") let user2 = User(id: "2", firstName: "Ringo", lastName: "Starr") extension User: Validable { } user1.add(validation: { !$0.isEmpty }, for: .id) .add(validation: { $0 == "Lennon" }, for: .lastName) .validate() // true
  • 104. Validable Pattern let user1 = User(id: "1", firstName: "John", lastName: "Lennon") let user2 = User(id: "2", firstName: "Ringo", lastName: "Starr") extension User: Validable { } user1.add(validation: { !$0.isEmpty }, for: .id) .add(validation: { $0 == "Lennon" }, for: .lastName) .validate() // true user2.add(validation: { !$0.isEmpty }, for: .id) .add(validation: { $0 == "Lennon" }, for: .lastName) .validate() // false (Of course, you could also use predicates instead of closures 😉)
  • 106. Binding Pattern Sets up a data-flow
  • 107. Binding Pattern class Binding<Destination: AnyObject>: NSObject { weak var source: UITextField? weak var destination: Destination? var property: WritableKeyPath<Destination, String?> }
  • 108. Binding Pattern class Binding<Destination: AnyObject>: NSObject { weak var source: UITextField? weak var destination: Destination? var property: WritableKeyPath<Destination, String?> }
  • 109. Binding Pattern class Binding<Destination: AnyObject>: NSObject { weak var source: UITextField? weak var destination: Destination? var property: WritableKeyPath<Destination, String?> }
  • 110. Binding Pattern class Binding<Destination: AnyObject>: NSObject { weak var source: UITextField? weak var destination: Destination? var property: WritableKeyPath<Destination, String?> }
  • 111. Binding Pattern class Binding<Destination: AnyObject>: NSObject { weak var source: UITextField? weak var destination: Destination? var property: WritableKeyPath<Destination, String?> init(source: UITextField, destination: Destination, property: WritableKeyPath<Destination, String?>) { self.source = source self.destination = destination self.property = property super.init() self.source?.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged) } }
  • 112. Binding Pattern class Binding<Destination: AnyObject>: NSObject { weak var source: UITextField? weak var destination: Destination? var property: WritableKeyPath<Destination, String?> init(source: UITextField, destination: Destination, property: WritableKeyPath<Destination, String?>) { self.source = source self.destination = destination self.property = property super.init() self.source?.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged) } }
  • 113. Binding Pattern class Binding<Destination: AnyObject>: NSObject { weak var source: UITextField? weak var destination: Destination? var property: WritableKeyPath<Destination, String?> init(source: UITextField, destination: Destination, property: WritableKeyPath<Destination, String?>) { self.source = source self.destination = destination self.property = property super.init() self.source?.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged) } }
  • 114. Binding Pattern class Binding<Destination: AnyObject>: NSObject { weak var source: UITextField? weak var destination: Destination? var property: WritableKeyPath<Destination, String?> init(source: UITextField, destination: Destination, property: WritableKeyPath<Destination, String?>) { self.source = source self.destination = destination self.property = property super.init() self.source?.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged) } }
  • 115. Binding Pattern class Binding<Destination: AnyObject>: NSObject { weak var source: UITextField? weak var destination: Destination? var property: WritableKeyPath<Destination, String?> init(source: UITextField, destination: Destination, property: WritableKeyPath<Destination, String?>) { self.source = source self.destination = destination self.property = property super.init() self.source?.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged) } }
  • 116. Binding Pattern class Binding<Destination: AnyObject>: NSObject { weak var source: UITextField? weak var destination: Destination? var property: WritableKeyPath<Destination, String?> init(source: UITextField, destination: Destination, property: WritableKeyPath<Destination, String?>) { self.source = source self.destination = destination self.property = property super.init() self.source?.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged) } @objc func textFieldDidChange() { destination?[keyPath: property] = source?.text } }
  • 117. Binding Pattern class Binding<Destination: AnyObject>: NSObject { weak var source: UITextField? weak var destination: Destination? var property: WritableKeyPath<Destination, String?> init(source: UITextField, destination: Destination, property: WritableKeyPath<Destination, String?>) { self.source = source self.destination = destination self.property = property super.init() self.source?.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged) } @objc func textFieldDidChange() { destination?[keyPath: property] = source?.text } }
  • 118. Binding Pattern class Binding<Destination: AnyObject>: NSObject { weak var source: UITextField? weak var destination: Destination? var property: WritableKeyPath<Destination, String?> init(source: UITextField, destination: Destination, property: WritableKeyPath<Destination, String?>) { self.source = source self.destination = destination self.property = property super.init() self.source?.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged) } @objc func textFieldDidChange() { destination?[keyPath: property] = source?.text } }
  • 119. Binding Pattern extension UITextField { func bindText<Destination>(to destination: Destination, on property: WritableKeyPath<Destination, String?>) -> Binding<Destination> { return Binding(source: self, destination: destination, property: property) } }
  • 120. Binding Pattern class ViewController: UIViewController { lazy var viewModel: ViewModel = { ViewModel() }() lazy var nameTextField: UITextField = { UITextField() }() var bindings: [Binding<ViewModel>] = [] override func viewDidLoad() { super.viewDidLoad() setupUI() let binding = nameTextField.bindText(to: viewModel, on: .name) bindings.append(binding) } }
  • 121. Binding Pattern class ViewController: UIViewController { lazy var viewModel: ViewModel = { ViewModel() }() lazy var nameTextField: UITextField = { UITextField() }() var bindings: [Binding<ViewModel>] = [] override func viewDidLoad() { super.viewDidLoad() setupUI() let binding = nameTextField.bindText(to: viewModel, on: .name) bindings.append(binding) } }
  • 122. Binding Pattern class ViewController: UIViewController { lazy var viewModel: ViewModel = { ViewModel() }() lazy var nameTextField: UITextField = { UITextField() }() var bindings: [Binding<ViewModel>] = [] override func viewDidLoad() { super.viewDidLoad() setupUI() let binding = nameTextField.bindText(to: viewModel, on: .name) bindings.append(binding) } }
  • 123. Binding Pattern class ViewController: UIViewController { lazy var viewModel: ViewModel = { ViewModel() }() lazy var nameTextField: UITextField = { UITextField() }() var bindings: [Binding<ViewModel>] = [] override func viewDidLoad() { super.viewDidLoad() setupUI() let binding = nameTextField.bindText(to: viewModel, on: .name) bindings.append(binding) } }
  • 124. Binding Pattern class ViewController: UIViewController { lazy var viewModel: ViewModel = { ViewModel() }() lazy var nameTextField: UITextField = { UITextField() }() var bindings: [Binding<ViewModel>] = [] override func viewDidLoad() { super.viewDidLoad() setupUI() let binding = nameTextField.bindText(to: viewModel, on: .name) bindings.append(binding) } }
  • 125. 😎
  • 126. Recap
  • 127. Recap KeyPaths offer deferred reading/writing to a property They work very well with generic algorithms They allow for a very clean, declarative and expressive syntax They are a great tool to build DSLs They bring some nice sugar syntax to many patterns
  • 129. Conclusion KeyPaths are great, use them! Big thank you to Marion Curtil and Jérôme Alves for their valuable inputs. You liked what you saw, and you want some of it in your app? https://github.com/vincent-pradeilles/KeyPathKit
  • 130. 😊
  • 131. The underestimated power of KeyPaths Vincent Pradeilles (@v_pradeilles) – Worldline 🇫🇷