SlideShare a Scribd company logo
1 of 192
Download to read offline
Property Wrappers
or how Swift decided to become Java
Vincent Pradeilles (@v_pradeilles) – Worldline
!
I'm Vincent
2
Property
Wrappers!
3
But first...
4
// Kotlin
data class MyServiceResponse(/* ... */)
interface MyService {
@FormUrlEncoded
@POST("/myservice/endpoint")
fun call(@Header("Authorization") authorizationHeader: String,
@Field("first_argument") firstArgument: String,
@Field("second_argument") secondArgument: Int
): Observable<MyServiceResponse>
}
5
// Kotlin
data class MyServiceResponse(/* ... */)
interface MyService {
@FormUrlEncoded
@POST("/myservice/endpoint")
fun call(@Header("Authorization") authorizationHeader: String,
@Field("first_argument") firstArgument: String,
@Field("second_argument") secondArgument: Int
): Observable<MyServiceResponse>
}
5
// Kotlin
data class MyServiceResponse(/* ... */)
interface MyService {
@FormUrlEncoded
@POST("/myservice/endpoint")
fun call(@Header("Authorization") authorizationHeader: String,
@Field("first_argument") firstArgument: String,
@Field("second_argument") secondArgument: Int
): Observable<MyServiceResponse>
}
5
// Kotlin
data class MyServiceResponse(/* ... */)
interface MyService {
@FormUrlEncoded
@POST("/myservice/endpoint")
fun call(@Header("Authorization") authorizationHeader: String,
@Field("first_argument") firstArgument: String,
@Field("second_argument") secondArgument: Int
): Observable<MyServiceResponse>
}
5
// Kotlin
data class MyServiceResponse(/* ... */)
interface MyService {
@FormUrlEncoded
@POST("/myservice/endpoint")
fun call(@Header("Authorization") authorizationHeader: String,
@Field("first_argument") firstArgument: String,
@Field("second_argument") secondArgument: Int
): Observable<MyServiceResponse>
}
5
// Kotlin
data class MyServiceResponse(/* ... */)
interface MyService {
@FormUrlEncoded
@POST("/myservice/endpoint")
fun call(@Header("Authorization") authorizationHeader: String,
@Field("first_argument") firstArgument: String,
@Field("second_argument") secondArgument: Int
): Observable<MyServiceResponse>
}
5
// Kotlin
data class MyServiceResponse(/* ... */)
interface MyService {
@FormUrlEncoded
@POST("/myservice/endpoint")
fun call(@Header("Authorization") authorizationHeader: String,
@Field("first_argument") firstArgument: String,
@Field("second_argument") secondArgument: Int
): Observable<MyServiceResponse>
}
5
Kotlin & Java Annotations
Through these annotations, Kotlin developers can
decorate their code.
6
Kotlin & Java Annotations
Through these annotations, Kotlin developers can
decorate their code.
These annotations carry a meaning that will enrich
the behavior of the code.
7
Kotlin & Java Annotations
Through these annotations, Kotlin developers can
decorate their code.
These annotations carry a meaning that will enrich
the behavior of the code.
They let developers extend their code in a very
declarative way.
8
Swift Developers
9
WWDC 2019!
10
11
12
13
Swift 5.1
14
Property
Wrappers!
15
Property Wrappers
import SwiftUI
struct MyView: View {
@State var name = "John"
var body: some View {
Text(name)
}
}
16
Property Wrappers
import SwiftUI
struct MyView: View {
@State var name = "John"
var body: some View {
Text(name)
}
}
16
Property Wrappers
@State is a Property Wrapper.
A property wrapper decorates a property with a
custom behavior.
(This sounds very similar to annotations in Kotlin/
Java)
17
Property Wrappers
To understand how they work under the hood, let's
take a look at their implementation!
The best place to start is the Swift Evolution
proposal (SE-0258) where they have been initially
pitched.
18
SE-0258
By reading the introduction, we can are already get
some insight in what motivated their addition to
the language.
19
SE-0258
There are property implementation patterns that
come up repeatedly.
Rather than hardcode a fixed set of patterns into the
compiler (as we have done for lazy and @NSCopying),
we should provide a general "property wrapper"
mechanism to allow these patterns to be defined as
libraries.
20
Let's implement some Property Wrappers
(Courtesy of https://nshipster.com/propertywrapper/)
21
Clamping
Numerical
Values
22
Clamping Numerical Values
struct Solution {
@Clamping(0...14) var pH: Double = 7.0
}
23
Clamping Numerical Values
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
}
24
Clamping Numerical Values
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
}
24
Clamping Numerical Values
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
}
24
Clamping Numerical Values
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
}
24
Clamping Numerical Values
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
precondition(range.contains(wrappedValue))
self.value = wrappedValue
self.range = range
}
}
25
Clamping Numerical Values
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
precondition(range.contains(wrappedValue))
self.value = wrappedValue
self.range = range
}
}
25
Clamping Numerical Values
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
precondition(range.contains(wrappedValue))
self.value = wrappedValue
self.range = range
}
}
25
Clamping Numerical Values
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
precondition(range.contains(wrappedValue))
self.value = wrappedValue
self.range = range
}
}
25
Clamping Numerical Values
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
precondition(range.contains(wrappedValue))
self.value = wrappedValue
self.range = range
}
}
25
Clamping Numerical Values
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
precondition(range.contains(wrappedValue))
self.value = wrappedValue
self.range = range
}
}
25
Clamping Numerical Values
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
precondition(range.contains(wrappedValue))
self.value = wrappedValue
self.range = range
}
}
25
Clamping Numerical Values
@propertyWrapper
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
precondition(range.contains(wrappedValue))
self.value = wrappedValue
self.range = range
}
}
26
Clamping Numerical Values
@propertyWrapper
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
precondition(range.contains(wrappedValue))
self.value = wrappedValue
self.range = range
}
}
26
Clamping Numerical Values
@propertyWrapper
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
precondition(range.contains(wrappedValue))
self.value = wrappedValue
self.range = range
}
}
26
Clamping Numerical Values
@propertyWrapper
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
precondition(range.contains(wrappedValue))
self.value = wrappedValue
self.range = range
}
var wrappedValue: Value {
get { value }
set { value = min(max(range.lowerBound, newValue), range.upperBound) }
}
}
27
Clamping Numerical Values
@propertyWrapper
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
precondition(range.contains(wrappedValue))
self.value = wrappedValue
self.range = range
}
var wrappedValue: Value {
get { value }
set { value = min(max(range.lowerBound, newValue), range.upperBound) }
}
}
27
Clamping Numerical Values
@propertyWrapper
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
precondition(range.contains(wrappedValue))
self.value = wrappedValue
self.range = range
}
var wrappedValue: Value {
get { value }
set { value = min(max(range.lowerBound, newValue), range.upperBound) }
}
}
27
Clamping Numerical Values
@propertyWrapper
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
precondition(range.contains(wrappedValue))
self.value = wrappedValue
self.range = range
}
var wrappedValue: Value {
get { value }
set { value = min(max(range.lowerBound, newValue), range.upperBound) }
}
}
27
Clamping Numerical Values
@propertyWrapper
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
precondition(range.contains(wrappedValue))
self.value = wrappedValue
self.range = range
}
var wrappedValue: Value {
get { value }
set { value = min(max(range.lowerBound, newValue), range.upperBound) }
}
}
27
Clamping Numerical Values
@propertyWrapper
struct Clamping<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
precondition(range.contains(wrappedValue))
self.value = wrappedValue
self.range = range
}
var wrappedValue: Value {
get { value }
set { value = min(max(range.lowerBound, newValue), range.upperBound) }
}
}
27
That's it
28
Clamping Numerical Values
struct Solution {
@Clamping(0...14) var pH: Double = 7.0
}
var solution = Solution(pH: 7.0)
solution.pH = -1
solution.pH // 0
29
Clamping Numerical Values
struct Solution {
@Clamping(0...14) var pH: Double = 7.0
}
var solution = Solution(pH: 7.0)
solution.pH = -1
solution.pH // 0
29
Clamping Numerical Values
struct Solution {
@Clamping(0...14) var pH: Double = 7.0
}
var solution = Solution(pH: 7.0)
solution.pH = -1
solution.pH // 0
29
Clamping Numerical Values
struct Solution {
@Clamping(0...14) var pH: Double = 7.0
}
var solution = Solution(pH: 7.0)
solution.pH = -1
solution.pH // 0
29
Magic?
30
Compiler Magic!
31
Compiler Magic
Whenever we write solution.pH, the compiler actually
replaces it by solution.pH.wrappedValue.
This is what makes Property Wrapper incredibly
seamless to use!
And because this happens at compile time, they
have no minimum OS requirement
32
Compiler Magic
The initializer also gets some compiler magic:
init(wrappedValue: Value, _ range: ClosedRange<Value>)
@Clamping(0...14) var pH: Double = 7.0
By convention, the argument wrappedValue will be set
to the initial value of the property (here 7.0).
33
Compiler Magic
If you need to access the Wrapper itself, you can do
so by implementing the property projectedValue.
var projectedValue: Clamping<Value> {
get { return self }
}
Which you would then call like this:
solution.$pH // is of type Clamping<Double>
34
Numerical Values
Now that we've implemented @Clamping, we can easily think
of other useful wrappers to deal with numerical values:
35
Numerical Values
Now that we've implemented @Clamping, we can easily think
of other useful wrappers to deal with numerical values:
→ @Normalized
35
Numerical Values
Now that we've implemented @Clamping, we can easily think
of other useful wrappers to deal with numerical values:
→ @Normalized
→ @Rounded
35
Numerical Values
Now that we've implemented @Clamping, we can easily think
of other useful wrappers to deal with numerical values:
→ @Normalized
→ @Rounded
→ @Truncated
35
Numerical Values
Now that we've implemented @Clamping, we can easily think
of other useful wrappers to deal with numerical values:
→ @Normalized
→ @Rounded
→ @Truncated
→ @Quantized
35
Numerical Values
Now that we've implemented @Clamping, we can easily think
of other useful wrappers to deal with numerical values:
→ @Normalized
→ @Rounded
→ @Truncated
→ @Quantized
→ etc.
35
Trimming
Characters
36
Trimming Characters
When we deal with standardized formats, lingering
whitespaces can be very tricky:
37
Trimming Characters
When we deal with standardized formats, lingering
whitespaces can be very tricky:
URL(string: " https://nshipster.com") // nil (!)
ISO8601DateFormatter().date(from: " 2019-06-24") // nil (!)
38
Trimming Characters
When we deal with standardized formats, lingering
whitespaces can be very tricky:
URL(string: " https://nshipster.com") // nil (!)
ISO8601DateFormatter().date(from: " 2019-06-24") // nil (!)
So let's introduce a Property Wrapper that will trim
such characters!
39
Trimming Characters
@propertyWrapper
struct Trimmed {
private(set) var value: String = ""
var wrappedValue: String {
get { value }
set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }
}
init(wrappedValue: String) {
self.wrappedValue = wrappedValue
}
}
40
Trimming Characters
@propertyWrapper
struct Trimmed {
private(set) var value: String = ""
var wrappedValue: String {
get { value }
set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }
}
init(wrappedValue: String) {
self.wrappedValue = wrappedValue
}
}
40
Trimming Characters
@propertyWrapper
struct Trimmed {
private(set) var value: String = ""
var wrappedValue: String {
get { value }
set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }
}
init(wrappedValue: String) {
self.wrappedValue = wrappedValue
}
}
40
Trimming Characters
@propertyWrapper
struct Trimmed {
private(set) var value: String = ""
var wrappedValue: String {
get { value }
set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }
}
init(wrappedValue: String) {
self.wrappedValue = wrappedValue
}
}
40
Trimming Characters
@propertyWrapper
struct Trimmed {
private(set) var value: String = ""
var wrappedValue: String {
get { value }
set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }
}
init(wrappedValue: String) {
self.wrappedValue = wrappedValue
}
}
40
Trimming Characters
@propertyWrapper
struct Trimmed {
private(set) var value: String = ""
var wrappedValue: String {
get { value }
set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }
}
init(wrappedValue: String) {
self.wrappedValue = wrappedValue
}
}
40
Trimming Characters
@propertyWrapper
struct Trimmed {
private(set) var value: String = ""
var wrappedValue: String {
get { value }
set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }
}
init(wrappedValue: String) {
self.wrappedValue = wrappedValue
}
}
40
Trimming Characters
@propertyWrapper
struct Trimmed {
private(set) var value: String = ""
var wrappedValue: String {
get { value }
set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }
}
init(wrappedValue: String) {
self.wrappedValue = wrappedValue
}
}
40
Trimming Characters
@propertyWrapper
struct Trimmed {
private(set) var value: String = ""
var wrappedValue: String {
get { value }
set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }
}
init(wrappedValue: String) {
self.wrappedValue = wrappedValue
}
}
40
Trimming Characters
struct Post {
@Trimmed var title: String
@Trimmed var body: String
}
let quine = Post(title: " Swift Property Wrappers ", body: "...")
quine.title // "Swift Property Wrappers" (no leading or trailing spaces!)
quine.title = " @propertyWrapper "
quine.title // "@propertyWrapper" (still no leading or trailing spaces!)
41
Trimming Characters
struct Post {
@Trimmed var title: String
@Trimmed var body: String
}
let quine = Post(title: " Swift Property Wrappers ", body: "...")
quine.title // "Swift Property Wrappers" (no leading or trailing spaces!)
quine.title = " @propertyWrapper "
quine.title // "@propertyWrapper" (still no leading or trailing spaces!)
41
Trimming Characters
struct Post {
@Trimmed var title: String
@Trimmed var body: String
}
let quine = Post(title: " Swift Property Wrappers ", body: "...")
quine.title // "Swift Property Wrappers" (no leading or trailing spaces!)
quine.title = " @propertyWrapper "
quine.title // "@propertyWrapper" (still no leading or trailing spaces!)
41
Trimming Characters
struct Post {
@Trimmed var title: String
@Trimmed var body: String
}
let quine = Post(title: " Swift Property Wrappers ", body: "...")
quine.title // "Swift Property Wrappers" (no leading or trailing spaces!)
quine.title = " @propertyWrapper "
quine.title // "@propertyWrapper" (still no leading or trailing spaces!)
41
Trimming Characters
struct Post {
@Trimmed var title: String
@Trimmed var body: String
}
let quine = Post(title: " Swift Property Wrappers ", body: "...")
quine.title // "Swift Property Wrappers" (no leading or trailing spaces!)
quine.title = " @propertyWrapper "
quine.title // "@propertyWrapper" (still no leading or trailing spaces!)
41
Trimming Characters
struct Post {
@Trimmed var title: String
@Trimmed var body: String
}
let quine = Post(title: " Swift Property Wrappers ", body: "...")
quine.title // "Swift Property Wrappers" (no leading or trailing spaces!)
quine.title = " @propertyWrapper "
quine.title // "@propertyWrapper" (still no leading or trailing spaces!)
41
Trimming Characters
struct Post {
@Trimmed var title: String
@Trimmed var body: String
}
let quine = Post(title: " Swift Property Wrappers ", body: "...")
quine.title // "Swift Property Wrappers" (no leading or trailing spaces!)
quine.title = " @propertyWrapper "
quine.title // "@propertyWrapper" (still no leading or trailing spaces!)
41
Data
Versioning
42
Data Versioning
We can even go further, and implement an entire
business requirement through a Property Wrapper.
Let's implement a Property Wrapper that will manage
an history of the values assigned to a given
variable.
(Developers working on server-side might find it
particularly useful!)
43
Data Versioning
@propertyWrapper
struct Versioned<Value> {
private var value: Value
private(set) var timestampedValues: [(Date, Value)] = []
var wrappedValue: Value {
get { value }
set {
value = newValue
timestampedValues.append((Date(), value))
}
}
init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
}
44
Data Versioning
@propertyWrapper
struct Versioned<Value> {
private var value: Value
private(set) var timestampedValues: [(Date, Value)] = []
var wrappedValue: Value {
get { value }
set {
value = newValue
timestampedValues.append((Date(), value))
}
}
init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
}
44
Data Versioning
@propertyWrapper
struct Versioned<Value> {
private var value: Value
private(set) var timestampedValues: [(Date, Value)] = []
var wrappedValue: Value {
get { value }
set {
value = newValue
timestampedValues.append((Date(), value))
}
}
init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
}
44
Data Versioning
@propertyWrapper
struct Versioned<Value> {
private var value: Value
private(set) var timestampedValues: [(Date, Value)] = []
var wrappedValue: Value {
get { value }
set {
value = newValue
timestampedValues.append((Date(), value))
}
}
init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
}
44
Data Versioning
@propertyWrapper
struct Versioned<Value> {
private var value: Value
private(set) var timestampedValues: [(Date, Value)] = []
var wrappedValue: Value {
get { value }
set {
value = newValue
timestampedValues.append((Date(), value))
}
}
init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
}
44
Data Versioning
@propertyWrapper
struct Versioned<Value> {
private var value: Value
private(set) var timestampedValues: [(Date, Value)] = []
var wrappedValue: Value {
get { value }
set {
value = newValue
timestampedValues.append((Date(), value))
}
}
init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
}
44
Data Versioning
@propertyWrapper
struct Versioned<Value> {
private var value: Value
private(set) var timestampedValues: [(Date, Value)] = []
var wrappedValue: Value {
get { value }
set {
value = newValue
timestampedValues.append((Date(), value))
}
}
init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
}
44
Data Versioning
@propertyWrapper
struct Versioned<Value> {
private var value: Value
private(set) var timestampedValues: [(Date, Value)] = []
var wrappedValue: Value {
get { value }
set {
value = newValue
timestampedValues.append((Date(), value))
}
}
init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
}
44
Data Versioning
@propertyWrapper
struct Versioned<Value> {
private var value: Value
private(set) var timestampedValues: [(Date, Value)] = []
var wrappedValue: Value {
get { value }
set {
value = newValue
timestampedValues.append((Date(), value))
}
}
init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
}
44
Data Versioning
@propertyWrapper
struct Versioned<Value> {
private var value: Value
private(set) var timestampedValues: [(Date, Value)] = []
var wrappedValue: Value {
get { value }
set {
value = newValue
timestampedValues.append((Date(), value))
}
}
init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
}
44
Data Versioning
@propertyWrapper
struct Versioned<Value> {
private var value: Value
private(set) var timestampedValues: [(Date, Value)] = []
var wrappedValue: Value {
get { value }
set {
value = newValue
timestampedValues.append((Date(), value))
}
}
init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
}
44
Data Versioning
class ExpenseReport {
enum State { case submitted, received, approved, denied }
@Versioned var state: State = .submitted
}
45
Data Versioning
A real world back-end application could go even
further.
We could implement another wrapper @Audited, that
would track which user read or wrote a given
variable.
46
Data Versioning
However there are still some limitations: Property
Wrapper cannot throw errors, which makes some
interesting use cases out of reach.
47
Data Versioning
However there are still some limitations: Property
Wrapper cannot throw errors, which makes some
interesting use cases out of reach.
If it were possible, we could write some pretty cool
things, like extending @Versioned to implement a
workflow mechanism
!
48
We've used Property Wrappers
to implement standalone features...
49
...Now, let's try to
interact with existing APIs
50
Let's look at UserDefaults
(Example from https://www.avanderlee.com/swift/property-wrappers/)
51
UserDefaults
extension UserDefaults {
public enum Keys {
static let hasSeenAppIntroduction = "has_seen_app_introduction"
}
/// Indicates whether or not the user has seen the on-boarding.
var hasSeenAppIntroduction: Bool {
set {
set(newValue, forKey: Keys.hasSeenAppIntroduction)
}
get {
return bool(forKey: Keys.hasSeenAppIntroduction)
}
}
}
52
UserDefaults
UserDefaults.standard.hasSeenAppIntroduction = true
guard !UserDefaults.standard.hasSeenAppIntroduction else { return }
showAppIntroduction()
53
UserDefaults
Very clean call sites
!
Requires boilerplate
!
54
Perfect Use Case
for a Property Wrapper
55
@propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
56
@propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
56
@propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
56
@propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
56
@propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
56
UserDefaults
struct UserDefaultsConfig {
@UserDefault("has_seen_app_introduction", defaultValue: false)
static var hasSeenAppIntroduction: Bool
}
UserDefaultsConfig.hasSeenAppIntroduction = false
print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: false
UserDefaultsConfig.hasSeenAppIntroduction = true
print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: true
57
UserDefaults
struct UserDefaultsConfig {
@UserDefault("has_seen_app_introduction", defaultValue: false)
static var hasSeenAppIntroduction: Bool
}
UserDefaultsConfig.hasSeenAppIntroduction = false
print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: false
UserDefaultsConfig.hasSeenAppIntroduction = true
print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: true
57
UserDefaults
struct UserDefaultsConfig {
@UserDefault("has_seen_app_introduction", defaultValue: false)
static var hasSeenAppIntroduction: Bool
}
UserDefaultsConfig.hasSeenAppIntroduction = false
print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: false
UserDefaultsConfig.hasSeenAppIntroduction = true
print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: true
57
UserDefaults
struct UserDefaultsConfig {
@UserDefault("has_seen_app_introduction", defaultValue: false)
static var hasSeenAppIntroduction: Bool
}
UserDefaultsConfig.hasSeenAppIntroduction = false
print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: false
UserDefaultsConfig.hasSeenAppIntroduction = true
print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: true
57
UserDefaults
struct UserDefaultsConfig {
@UserDefault("has_seen_app_introduction", defaultValue: false)
static var hasSeenAppIntroduction: Bool
}
UserDefaultsConfig.hasSeenAppIntroduction = false
print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: false
UserDefaultsConfig.hasSeenAppIntroduction = true
print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: true
57
UserDefaults & More
This example focused on smoothing out interactions
with UserDefaults.
58
UserDefaults & More
This example focused on smoothing out interactions
with UserDefaults.
But it's an approach that could be applied to any
data store.
59
Property Wrappers
operate on properties
60
Thank you,
Captain Obvious
61
Wouldn't it be cool to have
the same mechanism for functions?
62
Swift
Functional
63
Functional Programing in Swift
Functions are first-class citizens
64
Functional Programing in Swift
Functions are first-class citizens
var increment: (Int) -> Int = { $0 + 1 }
65
Functional Programing in Swift
Functions are first-class citizens
var increment: (Int) -> Int = { $0 + 1 }
[1, 2, 3].map({ $0 * $0 })
66
Functional Programing in Swift
Functions are first-class citizens
var increment: (Int) -> Int = { $0 + 1 }
[1, 2, 3].map({ $0 * $0 })
func buildIncrementor() -> (Int) -> Int {
return { $0 + 1 }
}
67
!
68
We can store a function in a property...
69
...so Property Wrappers can work with functions
70
Let's start with a simple use case
71
Caching Pure
Functions
72
Caching Pure Functions
@propertyWrapper
struct Cached<Input: Hashable, Output> {
private var functionWithCache: (Input) -> Output
init(wrappedValue: @escaping (Input) -> Output) {
self.functionWithCache = Cached.addCachingLogic(to: wrappedValue)
}
var wrappedValue: (Input) -> Output {
get { return self.functionWithCache }
set { self.functionWithCache = Cached.addCachingLogic(to: newValue) }
}
private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output {
var cache: [Input: Output] = [:]
return { input in
if let cachedOutput = cache[input] {
return cachedOutput
} else {
let output = function(input)
cache[input] = output
return output
}
}
}
}
73
Caching Pure Functions
@propertyWrapper
struct Cached<Input: Hashable, Output> {
private var functionWithCache: (Input) -> Output
init(wrappedValue: @escaping (Input) -> Output) {
self.functionWithCache = Cached.addCachingLogic(to: wrappedValue)
}
var wrappedValue: (Input) -> Output {
/* ... */
}
private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output {
/* ... */
}
}
74
Caching Pure Functions
@propertyWrapper
struct Cached<Input: Hashable, Output> {
private var functionWithCache: (Input) -> Output
init(wrappedValue: @escaping (Input) -> Output) {
self.functionWithCache = Cached.addCachingLogic(to: wrappedValue)
}
var wrappedValue: (Input) -> Output {
/* ... */
}
private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output {
/* ... */
}
}
74
Caching Pure Functions
@propertyWrapper
struct Cached<Input: Hashable, Output> {
private var functionWithCache: (Input) -> Output
init(wrappedValue: @escaping (Input) -> Output) {
self.functionWithCache = Cached.addCachingLogic(to: wrappedValue)
}
var wrappedValue: (Input) -> Output {
/* ... */
}
private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output {
/* ... */
}
}
74
Caching Pure Functions
@propertyWrapper
struct Cached<Input: Hashable, Output> {
private var functionWithCache: (Input) -> Output
init(wrappedValue: @escaping (Input) -> Output) {
self.functionWithCache = Cached.addCachingLogic(to: wrappedValue)
}
var wrappedValue: (Input) -> Output {
/* ... */
}
private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output {
/* ... */
}
}
74
Caching Pure Functions
@propertyWrapper
struct Cached<Input: Hashable, Output> {
private var functionWithCache: (Input) -> Output
init(wrappedValue: @escaping (Input) -> Output) {
self.functionWithCache = Cached.addCachingLogic(to: wrappedValue)
}
var wrappedValue: (Input) -> Output {
/* ... */
}
private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output {
/* ... */
}
}
74
Caching Pure Functions
@propertyWrapper
struct Cached<Input: Hashable, Output> {
/* ... */
var wrappedValue: (Input) -> Output {
get { return self.functionWithCache }
set { self.functionWithCache = Cached.addCachingLogic(to: newValue) }
}
private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output {
/* ... */
}
}
75
Caching Pure Functions
@propertyWrapper
struct Cached<Input: Hashable, Output> {
/* ... */
var wrappedValue: (Input) -> Output {
get { return self.functionWithCache }
set { self.functionWithCache = Cached.addCachingLogic(to: newValue) }
}
private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output {
/* ... */
}
}
75
Caching Pure Functions
@propertyWrapper
struct Cached<Input: Hashable, Output> {
/* ... */
var wrappedValue: (Input) -> Output {
get { return self.functionWithCache }
set { self.functionWithCache = Cached.addCachingLogic(to: newValue) }
}
private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output {
/* ... */
}
}
75
Caching Pure Functions
@propertyWrapper
struct Cached<Input: Hashable, Output> {
/* ... */
var wrappedValue: (Input) -> Output {
get { return self.functionWithCache }
set { self.functionWithCache = Cached.addCachingLogic(to: newValue) }
}
private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output {
/* ... */
}
}
75
Caching Pure Functions
@propertyWrapper
struct Cached<Input: Hashable, Output> {
/* ... */
var wrappedValue: (Input) -> Output {
get { return self.functionWithCache }
set { self.functionWithCache = Cached.addCachingLogic(to: newValue) }
}
private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output {
/* ... */
}
}
75
@propertyWrapper
struct Cached<Input: Hashable, Output> {
/* ... */
var wrappedValue: (Input) -> Output {
/* ... */
}
private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output {
var cache: [Input: Output] = [:]
return { input in
if let cachedOutput = cache[input] {
return cachedOutput
} else {
let output = function(input)
cache[input] = output
return output
}
}
}
}
76
@propertyWrapper
struct Cached<Input: Hashable, Output> {
/* ... */
var wrappedValue: (Input) -> Output {
/* ... */
}
private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output {
var cache: [Input: Output] = [:]
return { input in
if let cachedOutput = cache[input] {
return cachedOutput
} else {
let output = function(input)
cache[input] = output
return output
}
}
}
}
76
@propertyWrapper
struct Cached<Input: Hashable, Output> {
/* ... */
var wrappedValue: (Input) -> Output {
/* ... */
}
private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output {
var cache: [Input: Output] = [:]
return { input in
if let cachedOutput = cache[input] {
return cachedOutput
} else {
let output = function(input)
cache[input] = output
return output
}
}
}
}
76
@propertyWrapper
struct Cached<Input: Hashable, Output> {
/* ... */
var wrappedValue: (Input) -> Output {
/* ... */
}
private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output {
var cache: [Input: Output] = [:]
return { input in
if let cachedOutput = cache[input] {
return cachedOutput
} else {
let output = function(input)
cache[input] = output
return output
}
}
}
}
76
@propertyWrapper
struct Cached<Input: Hashable, Output> {
/* ... */
var wrappedValue: (Input) -> Output {
/* ... */
}
private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output {
var cache: [Input: Output] = [:]
return { input in
if let cachedOutput = cache[input] {
return cachedOutput
} else {
let output = function(input)
cache[input] = output
return output
}
}
}
}
76
!
77
Caching Pure Functions
struct Trigo {
@Cached static var cachedCos = { (x: Double) in cos(x) }
}
Trigo.cachedCos(.pi * 2) // takes 48.85 µs
// value of cos for 2π is now cached
Trigo.cachedCos(.pi * 2) // takes 0.13 µs
78
Caching Pure Functions
struct Trigo {
@Cached static var cachedCos = { (x: Double) in cos(x) }
}
Trigo.cachedCos(.pi * 2) // takes 48.85 µs
// value of cos for 2π is now cached
Trigo.cachedCos(.pi * 2) // takes 0.13 µs
78
Caching Pure Functions
struct Trigo {
@Cached static var cachedCos = { (x: Double) in cos(x) }
}
Trigo.cachedCos(.pi * 2) // takes 48.85 µs
// value of cos for 2π is now cached
Trigo.cachedCos(.pi * 2) // takes 0.13 µs
78
Caching Pure Functions
struct Trigo {
@Cached static var cachedCos = { (x: Double) in cos(x) }
}
Trigo.cachedCos(.pi * 2) // takes 48.85 µs
// value of cos for 2π is now cached
Trigo.cachedCos(.pi * 2) // takes 0.13 µs
78
Property Wrappers & Functions
@Cached is a very good example to grasp how Property
Wrappers and functions can work together.
But we could devise many others:
79
Property Wrappers & Functions
@Cached is a very good example to grasp how Property
Wrappers and functions can work together.
But we could devise many others:
→ @Delayed(delay: 0.3) and @Debounced(delay: 0.3), to deal
with timing
79
Property Wrappers & Functions
@Cached is a very good example to grasp how Property
Wrappers and functions can work together.
But we could devise many others:
→ @Delayed(delay: 0.3) and @Debounced(delay: 0.3), to deal
with timing
→ @ThreadSafe, to wrap a piece of code around a Lock
79
Property Wrappers & Functions
@Cached is a very good example to grasp how Property
Wrappers and functions can work together.
But we could devise many others:
→ @Delayed(delay: 0.3) and @Debounced(delay: 0.3), to deal
with timing
→ @ThreadSafe, to wrap a piece of code around a Lock
→ etc.
79
Last one
80
// Kotlin
data class MyServiceResponse(/* ... */)
interface MyService {
@FormUrlEncoded
@POST("/myservice/endpoint")
fun call(@Header("Authorization") authorizationHeader: String,
@Field("first_argument") firstArgument: String,
@Field("second_argument") secondArgument: Int
): Observable<MyServiceResponse>
}
81
// Kotlin
data class MyServiceResponse(/* ... */)
interface MyService {
@FormUrlEncoded
@POST("/myservice/endpoint")
fun call(@Header("Authorization") authorizationHeader: String,
@Field("first_argument") firstArgument: String,
@Field("second_argument") secondArgument: Int
): Observable<MyServiceResponse>
}
81
Property Wrappers can't be composed (yet)...
82
...so we're going to implement GET instead
83
typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> ()
@propertyWrapper
struct GET {
private var url: URL
init(url: String) {
self.url = URL(string: url)!
}
var wrappedValue: Service<String> {
get {
return { completionHandler in
let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in
guard error == nil else { completionHandler(.failure(error!)); return }
let string = String(data: data!, encoding: .utf8)!
completionHandler(.success(string))
}
task.resume()
}
}
}
}
84
typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> ()
@propertyWrapper
struct GET {
private var url: URL
init(url: String) {
self.url = URL(string: url)!
}
var wrappedValue: Service<String> {
get {
return { completionHandler in
let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in
guard error == nil else { completionHandler(.failure(error!)); return }
let string = String(data: data!, encoding: .utf8)!
completionHandler(.success(string))
}
task.resume()
}
}
}
}
84
typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> ()
@propertyWrapper
struct GET {
private var url: URL
init(url: String) {
self.url = URL(string: url)!
}
var wrappedValue: Service<String> {
get {
return { completionHandler in
let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in
guard error == nil else { completionHandler(.failure(error!)); return }
let string = String(data: data!, encoding: .utf8)!
completionHandler(.success(string))
}
task.resume()
}
}
}
}
84
typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> ()
@propertyWrapper
struct GET {
private var url: URL
init(url: String) {
self.url = URL(string: url)!
}
var wrappedValue: Service<String> {
get {
return { completionHandler in
let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in
guard error == nil else { completionHandler(.failure(error!)); return }
let string = String(data: data!, encoding: .utf8)!
completionHandler(.success(string))
}
task.resume()
}
}
}
}
84
typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> ()
@propertyWrapper
struct GET {
private var url: URL
init(url: String) {
self.url = URL(string: url)!
}
var wrappedValue: Service<String> {
get {
return { completionHandler in
let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in
guard error == nil else { completionHandler(.failure(error!)); return }
let string = String(data: data!, encoding: .utf8)!
completionHandler(.success(string))
}
task.resume()
}
}
}
}
84
typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> ()
@propertyWrapper
struct GET {
private var url: URL
init(url: String) {
self.url = URL(string: url)!
}
var wrappedValue: Service<String> {
get {
return { completionHandler in
let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in
guard error == nil else { completionHandler(.failure(error!)); return }
let string = String(data: data!, encoding: .utf8)!
completionHandler(.success(string))
}
task.resume()
}
}
}
}
84
typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> ()
@propertyWrapper
struct GET {
private var url: URL
init(url: String) {
self.url = URL(string: url)!
}
var wrappedValue: Service<String> {
get {
return { completionHandler in
let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in
guard error == nil else { completionHandler(.failure(error!)); return }
let string = String(data: data!, encoding: .utf8)!
completionHandler(.success(string))
}
task.resume()
}
}
}
}
84
typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> ()
@propertyWrapper
struct GET {
private var url: URL
init(url: String) {
self.url = URL(string: url)!
}
var wrappedValue: Service<String> {
get {
return { completionHandler in
let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in
guard error == nil else { completionHandler(.failure(error!)); return }
let string = String(data: data!, encoding: .utf8)!
completionHandler(.success(string))
}
task.resume()
}
}
}
}
84
typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> ()
@propertyWrapper
struct GET {
private var url: URL
init(url: String) {
self.url = URL(string: url)!
}
var wrappedValue: Service<String> {
get {
return { completionHandler in
let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in
guard error == nil else { completionHandler(.failure(error!)); return }
let string = String(data: data!, encoding: .utf8)!
completionHandler(.success(string))
}
task.resume()
}
}
}
}
84
typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> ()
@propertyWrapper
struct GET {
private var url: URL
init(url: String) {
self.url = URL(string: url)!
}
var wrappedValue: Service<String> {
get {
return { completionHandler in
let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in
guard error == nil else { completionHandler(.failure(error!)); return }
let string = String(data: data!, encoding: .utf8)!
completionHandler(.success(string))
}
task.resume()
}
}
}
}
84
typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> ()
@propertyWrapper
struct GET {
private var url: URL
init(url: String) {
self.url = URL(string: url)!
}
var wrappedValue: Service<String> {
get {
return { completionHandler in
let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in
guard error == nil else { completionHandler(.failure(error!)); return }
let string = String(data: data!, encoding: .utf8)!
completionHandler(.success(string))
}
task.resume()
}
}
}
}
84
typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> ()
@propertyWrapper
struct GET {
private var url: URL
init(url: String) {
self.url = URL(string: url)!
}
var wrappedValue: Service<String> {
get {
return { completionHandler in
let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in
guard error == nil else { completionHandler(.failure(error!)); return }
let string = String(data: data!, encoding: .utf8)!
completionHandler(.success(string))
}
task.resume()
}
}
}
}
84
typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> ()
@propertyWrapper
struct GET {
private var url: URL
init(url: String) {
self.url = URL(string: url)!
}
var wrappedValue: Service<String> {
get {
return { completionHandler in
let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in
guard error == nil else { completionHandler(.failure(error!)); return }
let string = String(data: data!, encoding: .utf8)!
completionHandler(.success(string))
}
task.resume()
}
}
}
}
84
typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> ()
@propertyWrapper
struct GET {
private var url: URL
init(url: String) {
self.url = URL(string: url)!
}
var wrappedValue: Service<String> {
get {
return { completionHandler in
let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in
guard error == nil else { completionHandler(.failure(error!)); return }
let string = String(data: data!, encoding: .utf8)!
completionHandler(.success(string))
}
task.resume()
}
}
}
}
84
struct API {
@GET(url: "https://samples.openweathermap.org/data/2.5/weather?id=2172797&appid=b6907d289e10d714a6e88b30761fae22")
static var getCurrentWeather: Service<String>
}
API.getCurrentWeather { result in
print(result)
}
success("{"coord":{"lon":145.77,"lat":-16.92},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds",
"icon":"03n"}],"base":"stations","main":{"temp":300.15,"pressure":1007,"humidity":74,"temp_min":300.15,"temp_max":
300.15},"visibility":10000,"wind":{"speed":3.6,"deg":160},"clouds":{"all":40},"dt":1485790200,"sys":{"type":1,"id":
8166,"message":0.2064,"country":"AU","sunrise":1485720272,"sunset":1485766550},"id":2172797,"name":"Cairns","cod":200}")
85
struct API {
@GET(url: "https://samples.openweathermap.org/data/2.5/weather?id=2172797&appid=b6907d289e10d714a6e88b30761fae22")
static var getCurrentWeather: Service<String>
}
API.getCurrentWeather { result in
print(result)
}
success("{"coord":{"lon":145.77,"lat":-16.92},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds",
"icon":"03n"}],"base":"stations","main":{"temp":300.15,"pressure":1007,"humidity":74,"temp_min":300.15,"temp_max":
300.15},"visibility":10000,"wind":{"speed":3.6,"deg":160},"clouds":{"all":40},"dt":1485790200,"sys":{"type":1,"id":
8166,"message":0.2064,"country":"AU","sunrise":1485720272,"sunset":1485766550},"id":2172797,"name":"Cairns","cod":200}")
85
struct API {
@GET(url: "https://samples.openweathermap.org/data/2.5/weather?id=2172797&appid=b6907d289e10d714a6e88b30761fae22")
static var getCurrentWeather: Service<String>
}
API.getCurrentWeather { result in
print(result)
}
success("{"coord":{"lon":145.77,"lat":-16.92},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds",
"icon":"03n"}],"base":"stations","main":{"temp":300.15,"pressure":1007,"humidity":74,"temp_min":300.15,"temp_max":
300.15},"visibility":10000,"wind":{"speed":3.6,"deg":160},"clouds":{"all":40},"dt":1485790200,"sys":{"type":1,"id":
8166,"message":0.2064,"country":"AU","sunrise":1485720272,"sunset":1485766550},"id":2172797,"name":"Cairns","cod":200}")
85
Time to Recap
86
Recap
87
Recap
→ Property Wrappers let us wrap properties with custom
behavior
87
Recap
→ Property Wrappers let us wrap properties with custom
behavior
→ They are very good at removing boilerplate
87
Recap
→ Property Wrappers let us wrap properties with custom
behavior
→ They are very good at removing boilerplate
→ They provide great ergonomics for ubiquitous constructs
87
Recap
→ Property Wrappers let us wrap properties with custom
behavior
→ They are very good at removing boilerplate
→ They provide great ergonomics for ubiquitous constructs
→ They can also be applied on functions
87
Recap
→ Property Wrappers let us wrap properties with custom
behavior
→ They are very good at removing boilerplate
→ They provide great ergonomics for ubiquitous constructs
→ They can also be applied on functions
→ They can hurt code readability if abused
87
Recap
→ Property Wrappers let us wrap properties with custom
behavior
→ They are very good at removing boilerplate
→ They provide great ergonomics for ubiquitous constructs
→ They can also be applied on functions
→ They can hurt code readability if abused
→ They will probably lead to new and exciting frameworks
87
!
88
Property Wrappers
or how Swift decided to become Java
Vincent Pradeilles @v_pradeilles – Worldline

More Related Content

What's hot

Leveraging Completable Futures to handle your query results Asynchrhonously
Leveraging Completable Futures to handle your query results AsynchrhonouslyLeveraging Completable Futures to handle your query results Asynchrhonously
Leveraging Completable Futures to handle your query results AsynchrhonouslyDavid Gómez García
 
03 - Qt UI Development
03 - Qt UI Development03 - Qt UI Development
03 - Qt UI DevelopmentAndreas Jakl
 
Design Patterns Reconsidered
Design Patterns ReconsideredDesign Patterns Reconsidered
Design Patterns ReconsideredAlex Miller
 
(Greach 2015) Dsl'ing your Groovy
(Greach 2015) Dsl'ing your Groovy(Greach 2015) Dsl'ing your Groovy
(Greach 2015) Dsl'ing your GroovyAlonso Torres
 
Singletons in PHP - Why they are bad and how you can eliminate them from your...
Singletons in PHP - Why they are bad and how you can eliminate them from your...Singletons in PHP - Why they are bad and how you can eliminate them from your...
Singletons in PHP - Why they are bad and how you can eliminate them from your...go_oh
 
Say Hello To Ecmascript 5
Say Hello To Ecmascript 5Say Hello To Ecmascript 5
Say Hello To Ecmascript 5Juriy Zaytsev
 
Uncommon Design Patterns
Uncommon Design PatternsUncommon Design Patterns
Uncommon Design PatternsStefano Fago
 
LetSwift RxSwift 시작하기
LetSwift RxSwift 시작하기LetSwift RxSwift 시작하기
LetSwift RxSwift 시작하기Wanbok Choi
 
#살아있다 #자프링외길12년차 #코프링2개월생존기
#살아있다 #자프링외길12년차 #코프링2개월생존기#살아있다 #자프링외길12년차 #코프링2개월생존기
#살아있다 #자프링외길12년차 #코프링2개월생존기Arawn Park
 
Getting started with ES6
Getting started with ES6Getting started with ES6
Getting started with ES6Nitay Neeman
 
Java script advance-auroskills (2)
Java script advance-auroskills (2)Java script advance-auroskills (2)
Java script advance-auroskills (2)BoneyGawande
 
Java script – basic auroskills (2)
Java script – basic   auroskills (2)Java script – basic   auroskills (2)
Java script – basic auroskills (2)BoneyGawande
 
Finagle and Java Service Framework at Pinterest
Finagle and Java Service Framework at PinterestFinagle and Java Service Framework at Pinterest
Finagle and Java Service Framework at PinterestPavan Chitumalla
 
Concurrency Concepts in Java
Concurrency Concepts in JavaConcurrency Concepts in Java
Concurrency Concepts in JavaDoug Hawkins
 
The Ring programming language version 1.6 book - Part 77 of 189
The Ring programming language version 1.6 book - Part 77 of 189The Ring programming language version 1.6 book - Part 77 of 189
The Ring programming language version 1.6 book - Part 77 of 189Mahmoud Samir Fayed
 
Clojure and the Web
Clojure and the WebClojure and the Web
Clojure and the Webnickmbailey
 

What's hot (20)

Leveraging Completable Futures to handle your query results Asynchrhonously
Leveraging Completable Futures to handle your query results AsynchrhonouslyLeveraging Completable Futures to handle your query results Asynchrhonously
Leveraging Completable Futures to handle your query results Asynchrhonously
 
03 - Qt UI Development
03 - Qt UI Development03 - Qt UI Development
03 - Qt UI Development
 
Design Patterns Reconsidered
Design Patterns ReconsideredDesign Patterns Reconsidered
Design Patterns Reconsidered
 
Rustlabs Quick Start
Rustlabs Quick StartRustlabs Quick Start
Rustlabs Quick Start
 
(Greach 2015) Dsl'ing your Groovy
(Greach 2015) Dsl'ing your Groovy(Greach 2015) Dsl'ing your Groovy
(Greach 2015) Dsl'ing your Groovy
 
Singletons in PHP - Why they are bad and how you can eliminate them from your...
Singletons in PHP - Why they are bad and how you can eliminate them from your...Singletons in PHP - Why they are bad and how you can eliminate them from your...
Singletons in PHP - Why they are bad and how you can eliminate them from your...
 
Say Hello To Ecmascript 5
Say Hello To Ecmascript 5Say Hello To Ecmascript 5
Say Hello To Ecmascript 5
 
Uncommon Design Patterns
Uncommon Design PatternsUncommon Design Patterns
Uncommon Design Patterns
 
LetSwift RxSwift 시작하기
LetSwift RxSwift 시작하기LetSwift RxSwift 시작하기
LetSwift RxSwift 시작하기
 
#살아있다 #자프링외길12년차 #코프링2개월생존기
#살아있다 #자프링외길12년차 #코프링2개월생존기#살아있다 #자프링외길12년차 #코프링2개월생존기
#살아있다 #자프링외길12년차 #코프링2개월생존기
 
Spring data
Spring dataSpring data
Spring data
 
Getting started with ES6
Getting started with ES6Getting started with ES6
Getting started with ES6
 
Java script advance-auroskills (2)
Java script advance-auroskills (2)Java script advance-auroskills (2)
Java script advance-auroskills (2)
 
Hamcrest
HamcrestHamcrest
Hamcrest
 
Design Patterns
Design PatternsDesign Patterns
Design Patterns
 
Java script – basic auroskills (2)
Java script – basic   auroskills (2)Java script – basic   auroskills (2)
Java script – basic auroskills (2)
 
Finagle and Java Service Framework at Pinterest
Finagle and Java Service Framework at PinterestFinagle and Java Service Framework at Pinterest
Finagle and Java Service Framework at Pinterest
 
Concurrency Concepts in Java
Concurrency Concepts in JavaConcurrency Concepts in Java
Concurrency Concepts in Java
 
The Ring programming language version 1.6 book - Part 77 of 189
The Ring programming language version 1.6 book - Part 77 of 189The Ring programming language version 1.6 book - Part 77 of 189
The Ring programming language version 1.6 book - Part 77 of 189
 
Clojure and the Web
Clojure and the WebClojure and the Web
Clojure and the Web
 

Similar to Property Wrappers or how Swift decided to become Java

Getting the most out of Java [Nordic Coding-2010]
Getting the most out of Java [Nordic Coding-2010]Getting the most out of Java [Nordic Coding-2010]
Getting the most out of Java [Nordic Coding-2010]Sven Efftinge
 
BOF2644 Developing Java EE 7 Scala apps
BOF2644 Developing Java EE 7 Scala appsBOF2644 Developing Java EE 7 Scala apps
BOF2644 Developing Java EE 7 Scala appsPeter Pilgrim
 
Scala uma poderosa linguagem para a jvm
Scala   uma poderosa linguagem para a jvmScala   uma poderosa linguagem para a jvm
Scala uma poderosa linguagem para a jvmIsaias Barroso
 
Use of Apache Commons and Utilities
Use of Apache Commons and UtilitiesUse of Apache Commons and Utilities
Use of Apache Commons and UtilitiesPramod Kumar
 
ASP.Net 5 and C# 6
ASP.Net 5 and C# 6ASP.Net 5 and C# 6
ASP.Net 5 and C# 6Andy Butland
 
Scala Programming for Semantic Web Developers ESWC Semdev2015
Scala Programming for Semantic Web Developers ESWC Semdev2015Scala Programming for Semantic Web Developers ESWC Semdev2015
Scala Programming for Semantic Web Developers ESWC Semdev2015Jean-Paul Calbimonte
 
Vert.x - Reactive & Distributed [Devoxx version]
Vert.x - Reactive & Distributed [Devoxx version]Vert.x - Reactive & Distributed [Devoxx version]
Vert.x - Reactive & Distributed [Devoxx version]Orkhan Gasimov
 
Scala4sling
Scala4slingScala4sling
Scala4slingday
 
Hadoop Integration in Cassandra
Hadoop Integration in CassandraHadoop Integration in Cassandra
Hadoop Integration in CassandraJairam Chandar
 
JavaScript - An Introduction
JavaScript - An IntroductionJavaScript - An Introduction
JavaScript - An IntroductionManvendra Singh
 
Scaladroids: Developing Android Apps with Scala
Scaladroids: Developing Android Apps with ScalaScaladroids: Developing Android Apps with Scala
Scaladroids: Developing Android Apps with ScalaOstap Andrusiv
 
Need help coding MorseCode in JavaCreate Class MorseCodeClient. T.pdf
Need help coding MorseCode in JavaCreate Class MorseCodeClient. T.pdfNeed help coding MorseCode in JavaCreate Class MorseCodeClient. T.pdf
Need help coding MorseCode in JavaCreate Class MorseCodeClient. T.pdffastechsrv
 
Android Automated Testing
Android Automated TestingAndroid Automated Testing
Android Automated Testingroisagiv
 
Real Life Clean Architecture
Real Life Clean ArchitectureReal Life Clean Architecture
Real Life Clean ArchitectureMattia Battiston
 

Similar to Property Wrappers or how Swift decided to become Java (20)

Getting the most out of Java [Nordic Coding-2010]
Getting the most out of Java [Nordic Coding-2010]Getting the most out of Java [Nordic Coding-2010]
Getting the most out of Java [Nordic Coding-2010]
 
Scala in Places API
Scala in Places APIScala in Places API
Scala in Places API
 
BOF2644 Developing Java EE 7 Scala apps
BOF2644 Developing Java EE 7 Scala appsBOF2644 Developing Java EE 7 Scala apps
BOF2644 Developing Java EE 7 Scala apps
 
Scala uma poderosa linguagem para a jvm
Scala   uma poderosa linguagem para a jvmScala   uma poderosa linguagem para a jvm
Scala uma poderosa linguagem para a jvm
 
Use of Apache Commons and Utilities
Use of Apache Commons and UtilitiesUse of Apache Commons and Utilities
Use of Apache Commons and Utilities
 
ASP.Net 5 and C# 6
ASP.Net 5 and C# 6ASP.Net 5 and C# 6
ASP.Net 5 and C# 6
 
Scala Programming for Semantic Web Developers ESWC Semdev2015
Scala Programming for Semantic Web Developers ESWC Semdev2015Scala Programming for Semantic Web Developers ESWC Semdev2015
Scala Programming for Semantic Web Developers ESWC Semdev2015
 
Kitura Todolist tutorial
Kitura Todolist tutorialKitura Todolist tutorial
Kitura Todolist tutorial
 
Vert.x - Reactive & Distributed [Devoxx version]
Vert.x - Reactive & Distributed [Devoxx version]Vert.x - Reactive & Distributed [Devoxx version]
Vert.x - Reactive & Distributed [Devoxx version]
 
Lambdas puzzler - Peter Lawrey
Lambdas puzzler - Peter LawreyLambdas puzzler - Peter Lawrey
Lambdas puzzler - Peter Lawrey
 
Scala4sling
Scala4slingScala4sling
Scala4sling
 
Hadoop Integration in Cassandra
Hadoop Integration in CassandraHadoop Integration in Cassandra
Hadoop Integration in Cassandra
 
JavaScript - An Introduction
JavaScript - An IntroductionJavaScript - An Introduction
JavaScript - An Introduction
 
Scaladroids: Developing Android Apps with Scala
Scaladroids: Developing Android Apps with ScalaScaladroids: Developing Android Apps with Scala
Scaladroids: Developing Android Apps with Scala
 
Need help coding MorseCode in JavaCreate Class MorseCodeClient. T.pdf
Need help coding MorseCode in JavaCreate Class MorseCodeClient. T.pdfNeed help coding MorseCode in JavaCreate Class MorseCodeClient. T.pdf
Need help coding MorseCode in JavaCreate Class MorseCodeClient. T.pdf
 
Android Automated Testing
Android Automated TestingAndroid Automated Testing
Android Automated Testing
 
Solr @ Etsy - Apache Lucene Eurocon
Solr @ Etsy - Apache Lucene EuroconSolr @ Etsy - Apache Lucene Eurocon
Solr @ Etsy - Apache Lucene Eurocon
 
Real Life Clean Architecture
Real Life Clean ArchitectureReal Life Clean Architecture
Real Life Clean Architecture
 
Litwin linq
Litwin linqLitwin linq
Litwin linq
 
Scala in a nutshell by venkat
Scala in a nutshell by venkatScala in a nutshell by venkat
Scala in a nutshell by venkat
 

More from Vincent Pradeilles

The underestimated power of KeyPaths
The underestimated power of KeyPathsThe underestimated power of KeyPaths
The underestimated power of KeyPathsVincent 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
 
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
 
The underestimated power of KeyPaths
The underestimated power of KeyPathsThe underestimated power of KeyPaths
The underestimated power of KeyPaths
 
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
 
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

ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...Christina Lin
 
software engineering Chapter 5 System modeling.pptx
software engineering Chapter 5 System modeling.pptxsoftware engineering Chapter 5 System modeling.pptx
software engineering Chapter 5 System modeling.pptxnada99848
 
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
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackVICTOR MAESTRE RAMIREZ
 
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
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
 
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
 
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样umasea
 
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.
 
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
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEOrtus Solutions, Corp
 
MYjobs Presentation Django-based project
MYjobs Presentation Django-based projectMYjobs Presentation Django-based project
MYjobs Presentation Django-based projectAnoyGreter
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...MyIntelliSource, Inc.
 
Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantAxelRicardoTrocheRiq
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxTier1 app
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...MyIntelliSource, Inc.
 
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
 

Recently uploaded (20)

ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
 
software engineering Chapter 5 System modeling.pptx
software engineering Chapter 5 System modeling.pptxsoftware engineering Chapter 5 System modeling.pptx
software engineering Chapter 5 System modeling.pptx
 
Hot Sexy call girls in Patel Nagar🔝 9953056974 🔝 escort Service
Hot Sexy call girls in Patel Nagar🔝 9953056974 🔝 escort ServiceHot Sexy call girls in Patel Nagar🔝 9953056974 🔝 escort Service
Hot Sexy call girls in Patel Nagar🔝 9953056974 🔝 escort Service
 
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
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStack
 
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...
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
 
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
 
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
 
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
 
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
 
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
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
 
MYjobs Presentation Django-based project
MYjobs Presentation Django-based projectMYjobs Presentation Django-based project
MYjobs Presentation Django-based project
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
 
Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service Consultant
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
 
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...
 

Property Wrappers or how Swift decided to become Java

  • 1. Property Wrappers or how Swift decided to become Java Vincent Pradeilles (@v_pradeilles) – Worldline !
  • 5. // Kotlin data class MyServiceResponse(/* ... */) interface MyService { @FormUrlEncoded @POST("/myservice/endpoint") fun call(@Header("Authorization") authorizationHeader: String, @Field("first_argument") firstArgument: String, @Field("second_argument") secondArgument: Int ): Observable<MyServiceResponse> } 5
  • 6. // Kotlin data class MyServiceResponse(/* ... */) interface MyService { @FormUrlEncoded @POST("/myservice/endpoint") fun call(@Header("Authorization") authorizationHeader: String, @Field("first_argument") firstArgument: String, @Field("second_argument") secondArgument: Int ): Observable<MyServiceResponse> } 5
  • 7. // Kotlin data class MyServiceResponse(/* ... */) interface MyService { @FormUrlEncoded @POST("/myservice/endpoint") fun call(@Header("Authorization") authorizationHeader: String, @Field("first_argument") firstArgument: String, @Field("second_argument") secondArgument: Int ): Observable<MyServiceResponse> } 5
  • 8. // Kotlin data class MyServiceResponse(/* ... */) interface MyService { @FormUrlEncoded @POST("/myservice/endpoint") fun call(@Header("Authorization") authorizationHeader: String, @Field("first_argument") firstArgument: String, @Field("second_argument") secondArgument: Int ): Observable<MyServiceResponse> } 5
  • 9. // Kotlin data class MyServiceResponse(/* ... */) interface MyService { @FormUrlEncoded @POST("/myservice/endpoint") fun call(@Header("Authorization") authorizationHeader: String, @Field("first_argument") firstArgument: String, @Field("second_argument") secondArgument: Int ): Observable<MyServiceResponse> } 5
  • 10. // Kotlin data class MyServiceResponse(/* ... */) interface MyService { @FormUrlEncoded @POST("/myservice/endpoint") fun call(@Header("Authorization") authorizationHeader: String, @Field("first_argument") firstArgument: String, @Field("second_argument") secondArgument: Int ): Observable<MyServiceResponse> } 5
  • 11. // Kotlin data class MyServiceResponse(/* ... */) interface MyService { @FormUrlEncoded @POST("/myservice/endpoint") fun call(@Header("Authorization") authorizationHeader: String, @Field("first_argument") firstArgument: String, @Field("second_argument") secondArgument: Int ): Observable<MyServiceResponse> } 5
  • 12. Kotlin & Java Annotations Through these annotations, Kotlin developers can decorate their code. 6
  • 13. Kotlin & Java Annotations Through these annotations, Kotlin developers can decorate their code. These annotations carry a meaning that will enrich the behavior of the code. 7
  • 14. Kotlin & Java Annotations Through these annotations, Kotlin developers can decorate their code. These annotations carry a meaning that will enrich the behavior of the code. They let developers extend their code in a very declarative way. 8
  • 17. 11
  • 18. 12
  • 19. 13
  • 22. Property Wrappers import SwiftUI struct MyView: View { @State var name = "John" var body: some View { Text(name) } } 16
  • 23. Property Wrappers import SwiftUI struct MyView: View { @State var name = "John" var body: some View { Text(name) } } 16
  • 24. Property Wrappers @State is a Property Wrapper. A property wrapper decorates a property with a custom behavior. (This sounds very similar to annotations in Kotlin/ Java) 17
  • 25. Property Wrappers To understand how they work under the hood, let's take a look at their implementation! The best place to start is the Swift Evolution proposal (SE-0258) where they have been initially pitched. 18
  • 26. SE-0258 By reading the introduction, we can are already get some insight in what motivated their addition to the language. 19
  • 27. SE-0258 There are property implementation patterns that come up repeatedly. Rather than hardcode a fixed set of patterns into the compiler (as we have done for lazy and @NSCopying), we should provide a general "property wrapper" mechanism to allow these patterns to be defined as libraries. 20
  • 28. Let's implement some Property Wrappers (Courtesy of https://nshipster.com/propertywrapper/) 21
  • 30. Clamping Numerical Values struct Solution { @Clamping(0...14) var pH: Double = 7.0 } 23
  • 31. Clamping Numerical Values struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> } 24
  • 32. Clamping Numerical Values struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> } 24
  • 33. Clamping Numerical Values struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> } 24
  • 34. Clamping Numerical Values struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> } 24
  • 35. Clamping Numerical Values struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { precondition(range.contains(wrappedValue)) self.value = wrappedValue self.range = range } } 25
  • 36. Clamping Numerical Values struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { precondition(range.contains(wrappedValue)) self.value = wrappedValue self.range = range } } 25
  • 37. Clamping Numerical Values struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { precondition(range.contains(wrappedValue)) self.value = wrappedValue self.range = range } } 25
  • 38. Clamping Numerical Values struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { precondition(range.contains(wrappedValue)) self.value = wrappedValue self.range = range } } 25
  • 39. Clamping Numerical Values struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { precondition(range.contains(wrappedValue)) self.value = wrappedValue self.range = range } } 25
  • 40. Clamping Numerical Values struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { precondition(range.contains(wrappedValue)) self.value = wrappedValue self.range = range } } 25
  • 41. Clamping Numerical Values struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { precondition(range.contains(wrappedValue)) self.value = wrappedValue self.range = range } } 25
  • 42. Clamping Numerical Values @propertyWrapper struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { precondition(range.contains(wrappedValue)) self.value = wrappedValue self.range = range } } 26
  • 43. Clamping Numerical Values @propertyWrapper struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { precondition(range.contains(wrappedValue)) self.value = wrappedValue self.range = range } } 26
  • 44. Clamping Numerical Values @propertyWrapper struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { precondition(range.contains(wrappedValue)) self.value = wrappedValue self.range = range } } 26
  • 45. Clamping Numerical Values @propertyWrapper struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { precondition(range.contains(wrappedValue)) self.value = wrappedValue self.range = range } var wrappedValue: Value { get { value } set { value = min(max(range.lowerBound, newValue), range.upperBound) } } } 27
  • 46. Clamping Numerical Values @propertyWrapper struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { precondition(range.contains(wrappedValue)) self.value = wrappedValue self.range = range } var wrappedValue: Value { get { value } set { value = min(max(range.lowerBound, newValue), range.upperBound) } } } 27
  • 47. Clamping Numerical Values @propertyWrapper struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { precondition(range.contains(wrappedValue)) self.value = wrappedValue self.range = range } var wrappedValue: Value { get { value } set { value = min(max(range.lowerBound, newValue), range.upperBound) } } } 27
  • 48. Clamping Numerical Values @propertyWrapper struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { precondition(range.contains(wrappedValue)) self.value = wrappedValue self.range = range } var wrappedValue: Value { get { value } set { value = min(max(range.lowerBound, newValue), range.upperBound) } } } 27
  • 49. Clamping Numerical Values @propertyWrapper struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { precondition(range.contains(wrappedValue)) self.value = wrappedValue self.range = range } var wrappedValue: Value { get { value } set { value = min(max(range.lowerBound, newValue), range.upperBound) } } } 27
  • 50. Clamping Numerical Values @propertyWrapper struct Clamping<Value: Comparable> { var value: Value let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { precondition(range.contains(wrappedValue)) self.value = wrappedValue self.range = range } var wrappedValue: Value { get { value } set { value = min(max(range.lowerBound, newValue), range.upperBound) } } } 27
  • 52. Clamping Numerical Values struct Solution { @Clamping(0...14) var pH: Double = 7.0 } var solution = Solution(pH: 7.0) solution.pH = -1 solution.pH // 0 29
  • 53. Clamping Numerical Values struct Solution { @Clamping(0...14) var pH: Double = 7.0 } var solution = Solution(pH: 7.0) solution.pH = -1 solution.pH // 0 29
  • 54. Clamping Numerical Values struct Solution { @Clamping(0...14) var pH: Double = 7.0 } var solution = Solution(pH: 7.0) solution.pH = -1 solution.pH // 0 29
  • 55. Clamping Numerical Values struct Solution { @Clamping(0...14) var pH: Double = 7.0 } var solution = Solution(pH: 7.0) solution.pH = -1 solution.pH // 0 29
  • 58. Compiler Magic Whenever we write solution.pH, the compiler actually replaces it by solution.pH.wrappedValue. This is what makes Property Wrapper incredibly seamless to use! And because this happens at compile time, they have no minimum OS requirement 32
  • 59. Compiler Magic The initializer also gets some compiler magic: init(wrappedValue: Value, _ range: ClosedRange<Value>) @Clamping(0...14) var pH: Double = 7.0 By convention, the argument wrappedValue will be set to the initial value of the property (here 7.0). 33
  • 60. Compiler Magic If you need to access the Wrapper itself, you can do so by implementing the property projectedValue. var projectedValue: Clamping<Value> { get { return self } } Which you would then call like this: solution.$pH // is of type Clamping<Double> 34
  • 61. Numerical Values Now that we've implemented @Clamping, we can easily think of other useful wrappers to deal with numerical values: 35
  • 62. Numerical Values Now that we've implemented @Clamping, we can easily think of other useful wrappers to deal with numerical values: → @Normalized 35
  • 63. Numerical Values Now that we've implemented @Clamping, we can easily think of other useful wrappers to deal with numerical values: → @Normalized → @Rounded 35
  • 64. Numerical Values Now that we've implemented @Clamping, we can easily think of other useful wrappers to deal with numerical values: → @Normalized → @Rounded → @Truncated 35
  • 65. Numerical Values Now that we've implemented @Clamping, we can easily think of other useful wrappers to deal with numerical values: → @Normalized → @Rounded → @Truncated → @Quantized 35
  • 66. Numerical Values Now that we've implemented @Clamping, we can easily think of other useful wrappers to deal with numerical values: → @Normalized → @Rounded → @Truncated → @Quantized → etc. 35
  • 68. Trimming Characters When we deal with standardized formats, lingering whitespaces can be very tricky: 37
  • 69. Trimming Characters When we deal with standardized formats, lingering whitespaces can be very tricky: URL(string: " https://nshipster.com") // nil (!) ISO8601DateFormatter().date(from: " 2019-06-24") // nil (!) 38
  • 70. Trimming Characters When we deal with standardized formats, lingering whitespaces can be very tricky: URL(string: " https://nshipster.com") // nil (!) ISO8601DateFormatter().date(from: " 2019-06-24") // nil (!) So let's introduce a Property Wrapper that will trim such characters! 39
  • 71. Trimming Characters @propertyWrapper struct Trimmed { private(set) var value: String = "" var wrappedValue: String { get { value } set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) } } init(wrappedValue: String) { self.wrappedValue = wrappedValue } } 40
  • 72. Trimming Characters @propertyWrapper struct Trimmed { private(set) var value: String = "" var wrappedValue: String { get { value } set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) } } init(wrappedValue: String) { self.wrappedValue = wrappedValue } } 40
  • 73. Trimming Characters @propertyWrapper struct Trimmed { private(set) var value: String = "" var wrappedValue: String { get { value } set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) } } init(wrappedValue: String) { self.wrappedValue = wrappedValue } } 40
  • 74. Trimming Characters @propertyWrapper struct Trimmed { private(set) var value: String = "" var wrappedValue: String { get { value } set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) } } init(wrappedValue: String) { self.wrappedValue = wrappedValue } } 40
  • 75. Trimming Characters @propertyWrapper struct Trimmed { private(set) var value: String = "" var wrappedValue: String { get { value } set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) } } init(wrappedValue: String) { self.wrappedValue = wrappedValue } } 40
  • 76. Trimming Characters @propertyWrapper struct Trimmed { private(set) var value: String = "" var wrappedValue: String { get { value } set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) } } init(wrappedValue: String) { self.wrappedValue = wrappedValue } } 40
  • 77. Trimming Characters @propertyWrapper struct Trimmed { private(set) var value: String = "" var wrappedValue: String { get { value } set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) } } init(wrappedValue: String) { self.wrappedValue = wrappedValue } } 40
  • 78. Trimming Characters @propertyWrapper struct Trimmed { private(set) var value: String = "" var wrappedValue: String { get { value } set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) } } init(wrappedValue: String) { self.wrappedValue = wrappedValue } } 40
  • 79. Trimming Characters @propertyWrapper struct Trimmed { private(set) var value: String = "" var wrappedValue: String { get { value } set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) } } init(wrappedValue: String) { self.wrappedValue = wrappedValue } } 40
  • 80. Trimming Characters struct Post { @Trimmed var title: String @Trimmed var body: String } let quine = Post(title: " Swift Property Wrappers ", body: "...") quine.title // "Swift Property Wrappers" (no leading or trailing spaces!) quine.title = " @propertyWrapper " quine.title // "@propertyWrapper" (still no leading or trailing spaces!) 41
  • 81. Trimming Characters struct Post { @Trimmed var title: String @Trimmed var body: String } let quine = Post(title: " Swift Property Wrappers ", body: "...") quine.title // "Swift Property Wrappers" (no leading or trailing spaces!) quine.title = " @propertyWrapper " quine.title // "@propertyWrapper" (still no leading or trailing spaces!) 41
  • 82. Trimming Characters struct Post { @Trimmed var title: String @Trimmed var body: String } let quine = Post(title: " Swift Property Wrappers ", body: "...") quine.title // "Swift Property Wrappers" (no leading or trailing spaces!) quine.title = " @propertyWrapper " quine.title // "@propertyWrapper" (still no leading or trailing spaces!) 41
  • 83. Trimming Characters struct Post { @Trimmed var title: String @Trimmed var body: String } let quine = Post(title: " Swift Property Wrappers ", body: "...") quine.title // "Swift Property Wrappers" (no leading or trailing spaces!) quine.title = " @propertyWrapper " quine.title // "@propertyWrapper" (still no leading or trailing spaces!) 41
  • 84. Trimming Characters struct Post { @Trimmed var title: String @Trimmed var body: String } let quine = Post(title: " Swift Property Wrappers ", body: "...") quine.title // "Swift Property Wrappers" (no leading or trailing spaces!) quine.title = " @propertyWrapper " quine.title // "@propertyWrapper" (still no leading or trailing spaces!) 41
  • 85. Trimming Characters struct Post { @Trimmed var title: String @Trimmed var body: String } let quine = Post(title: " Swift Property Wrappers ", body: "...") quine.title // "Swift Property Wrappers" (no leading or trailing spaces!) quine.title = " @propertyWrapper " quine.title // "@propertyWrapper" (still no leading or trailing spaces!) 41
  • 86. Trimming Characters struct Post { @Trimmed var title: String @Trimmed var body: String } let quine = Post(title: " Swift Property Wrappers ", body: "...") quine.title // "Swift Property Wrappers" (no leading or trailing spaces!) quine.title = " @propertyWrapper " quine.title // "@propertyWrapper" (still no leading or trailing spaces!) 41
  • 88. Data Versioning We can even go further, and implement an entire business requirement through a Property Wrapper. Let's implement a Property Wrapper that will manage an history of the values assigned to a given variable. (Developers working on server-side might find it particularly useful!) 43
  • 89. Data Versioning @propertyWrapper struct Versioned<Value> { private var value: Value private(set) var timestampedValues: [(Date, Value)] = [] var wrappedValue: Value { get { value } set { value = newValue timestampedValues.append((Date(), value)) } } init(wrappedValue: Value) { self.wrappedValue = wrappedValue } } 44
  • 90. Data Versioning @propertyWrapper struct Versioned<Value> { private var value: Value private(set) var timestampedValues: [(Date, Value)] = [] var wrappedValue: Value { get { value } set { value = newValue timestampedValues.append((Date(), value)) } } init(wrappedValue: Value) { self.wrappedValue = wrappedValue } } 44
  • 91. Data Versioning @propertyWrapper struct Versioned<Value> { private var value: Value private(set) var timestampedValues: [(Date, Value)] = [] var wrappedValue: Value { get { value } set { value = newValue timestampedValues.append((Date(), value)) } } init(wrappedValue: Value) { self.wrappedValue = wrappedValue } } 44
  • 92. Data Versioning @propertyWrapper struct Versioned<Value> { private var value: Value private(set) var timestampedValues: [(Date, Value)] = [] var wrappedValue: Value { get { value } set { value = newValue timestampedValues.append((Date(), value)) } } init(wrappedValue: Value) { self.wrappedValue = wrappedValue } } 44
  • 93. Data Versioning @propertyWrapper struct Versioned<Value> { private var value: Value private(set) var timestampedValues: [(Date, Value)] = [] var wrappedValue: Value { get { value } set { value = newValue timestampedValues.append((Date(), value)) } } init(wrappedValue: Value) { self.wrappedValue = wrappedValue } } 44
  • 94. Data Versioning @propertyWrapper struct Versioned<Value> { private var value: Value private(set) var timestampedValues: [(Date, Value)] = [] var wrappedValue: Value { get { value } set { value = newValue timestampedValues.append((Date(), value)) } } init(wrappedValue: Value) { self.wrappedValue = wrappedValue } } 44
  • 95. Data Versioning @propertyWrapper struct Versioned<Value> { private var value: Value private(set) var timestampedValues: [(Date, Value)] = [] var wrappedValue: Value { get { value } set { value = newValue timestampedValues.append((Date(), value)) } } init(wrappedValue: Value) { self.wrappedValue = wrappedValue } } 44
  • 96. Data Versioning @propertyWrapper struct Versioned<Value> { private var value: Value private(set) var timestampedValues: [(Date, Value)] = [] var wrappedValue: Value { get { value } set { value = newValue timestampedValues.append((Date(), value)) } } init(wrappedValue: Value) { self.wrappedValue = wrappedValue } } 44
  • 97. Data Versioning @propertyWrapper struct Versioned<Value> { private var value: Value private(set) var timestampedValues: [(Date, Value)] = [] var wrappedValue: Value { get { value } set { value = newValue timestampedValues.append((Date(), value)) } } init(wrappedValue: Value) { self.wrappedValue = wrappedValue } } 44
  • 98. Data Versioning @propertyWrapper struct Versioned<Value> { private var value: Value private(set) var timestampedValues: [(Date, Value)] = [] var wrappedValue: Value { get { value } set { value = newValue timestampedValues.append((Date(), value)) } } init(wrappedValue: Value) { self.wrappedValue = wrappedValue } } 44
  • 99. Data Versioning @propertyWrapper struct Versioned<Value> { private var value: Value private(set) var timestampedValues: [(Date, Value)] = [] var wrappedValue: Value { get { value } set { value = newValue timestampedValues.append((Date(), value)) } } init(wrappedValue: Value) { self.wrappedValue = wrappedValue } } 44
  • 100. Data Versioning class ExpenseReport { enum State { case submitted, received, approved, denied } @Versioned var state: State = .submitted } 45
  • 101. Data Versioning A real world back-end application could go even further. We could implement another wrapper @Audited, that would track which user read or wrote a given variable. 46
  • 102. Data Versioning However there are still some limitations: Property Wrapper cannot throw errors, which makes some interesting use cases out of reach. 47
  • 103. Data Versioning However there are still some limitations: Property Wrapper cannot throw errors, which makes some interesting use cases out of reach. If it were possible, we could write some pretty cool things, like extending @Versioned to implement a workflow mechanism ! 48
  • 104. We've used Property Wrappers to implement standalone features... 49
  • 105. ...Now, let's try to interact with existing APIs 50
  • 106. Let's look at UserDefaults (Example from https://www.avanderlee.com/swift/property-wrappers/) 51
  • 107. UserDefaults extension UserDefaults { public enum Keys { static let hasSeenAppIntroduction = "has_seen_app_introduction" } /// Indicates whether or not the user has seen the on-boarding. var hasSeenAppIntroduction: Bool { set { set(newValue, forKey: Keys.hasSeenAppIntroduction) } get { return bool(forKey: Keys.hasSeenAppIntroduction) } } } 52
  • 108. UserDefaults UserDefaults.standard.hasSeenAppIntroduction = true guard !UserDefaults.standard.hasSeenAppIntroduction else { return } showAppIntroduction() 53
  • 109. UserDefaults Very clean call sites ! Requires boilerplate ! 54
  • 110. Perfect Use Case for a Property Wrapper 55
  • 111. @propertyWrapper struct UserDefault<T> { let key: String let defaultValue: T init(_ key: String, defaultValue: T) { self.key = key self.defaultValue = defaultValue } var wrappedValue: T { get { return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue } set { UserDefaults.standard.set(newValue, forKey: key) } } } 56
  • 112. @propertyWrapper struct UserDefault<T> { let key: String let defaultValue: T init(_ key: String, defaultValue: T) { self.key = key self.defaultValue = defaultValue } var wrappedValue: T { get { return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue } set { UserDefaults.standard.set(newValue, forKey: key) } } } 56
  • 113. @propertyWrapper struct UserDefault<T> { let key: String let defaultValue: T init(_ key: String, defaultValue: T) { self.key = key self.defaultValue = defaultValue } var wrappedValue: T { get { return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue } set { UserDefaults.standard.set(newValue, forKey: key) } } } 56
  • 114. @propertyWrapper struct UserDefault<T> { let key: String let defaultValue: T init(_ key: String, defaultValue: T) { self.key = key self.defaultValue = defaultValue } var wrappedValue: T { get { return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue } set { UserDefaults.standard.set(newValue, forKey: key) } } } 56
  • 115. @propertyWrapper struct UserDefault<T> { let key: String let defaultValue: T init(_ key: String, defaultValue: T) { self.key = key self.defaultValue = defaultValue } var wrappedValue: T { get { return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue } set { UserDefaults.standard.set(newValue, forKey: key) } } } 56
  • 116. UserDefaults struct UserDefaultsConfig { @UserDefault("has_seen_app_introduction", defaultValue: false) static var hasSeenAppIntroduction: Bool } UserDefaultsConfig.hasSeenAppIntroduction = false print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: false UserDefaultsConfig.hasSeenAppIntroduction = true print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: true 57
  • 117. UserDefaults struct UserDefaultsConfig { @UserDefault("has_seen_app_introduction", defaultValue: false) static var hasSeenAppIntroduction: Bool } UserDefaultsConfig.hasSeenAppIntroduction = false print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: false UserDefaultsConfig.hasSeenAppIntroduction = true print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: true 57
  • 118. UserDefaults struct UserDefaultsConfig { @UserDefault("has_seen_app_introduction", defaultValue: false) static var hasSeenAppIntroduction: Bool } UserDefaultsConfig.hasSeenAppIntroduction = false print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: false UserDefaultsConfig.hasSeenAppIntroduction = true print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: true 57
  • 119. UserDefaults struct UserDefaultsConfig { @UserDefault("has_seen_app_introduction", defaultValue: false) static var hasSeenAppIntroduction: Bool } UserDefaultsConfig.hasSeenAppIntroduction = false print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: false UserDefaultsConfig.hasSeenAppIntroduction = true print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: true 57
  • 120. UserDefaults struct UserDefaultsConfig { @UserDefault("has_seen_app_introduction", defaultValue: false) static var hasSeenAppIntroduction: Bool } UserDefaultsConfig.hasSeenAppIntroduction = false print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: false UserDefaultsConfig.hasSeenAppIntroduction = true print(UserDefaultsConfig.hasSeenAppIntroduction) // Prints: true 57
  • 121. UserDefaults & More This example focused on smoothing out interactions with UserDefaults. 58
  • 122. UserDefaults & More This example focused on smoothing out interactions with UserDefaults. But it's an approach that could be applied to any data store. 59
  • 125. Wouldn't it be cool to have the same mechanism for functions? 62
  • 127. Functional Programing in Swift Functions are first-class citizens 64
  • 128. Functional Programing in Swift Functions are first-class citizens var increment: (Int) -> Int = { $0 + 1 } 65
  • 129. Functional Programing in Swift Functions are first-class citizens var increment: (Int) -> Int = { $0 + 1 } [1, 2, 3].map({ $0 * $0 }) 66
  • 130. Functional Programing in Swift Functions are first-class citizens var increment: (Int) -> Int = { $0 + 1 } [1, 2, 3].map({ $0 * $0 }) func buildIncrementor() -> (Int) -> Int { return { $0 + 1 } } 67
  • 131. ! 68
  • 132. We can store a function in a property... 69
  • 133. ...so Property Wrappers can work with functions 70
  • 134. Let's start with a simple use case 71
  • 136. Caching Pure Functions @propertyWrapper struct Cached<Input: Hashable, Output> { private var functionWithCache: (Input) -> Output init(wrappedValue: @escaping (Input) -> Output) { self.functionWithCache = Cached.addCachingLogic(to: wrappedValue) } var wrappedValue: (Input) -> Output { get { return self.functionWithCache } set { self.functionWithCache = Cached.addCachingLogic(to: newValue) } } private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output { var cache: [Input: Output] = [:] return { input in if let cachedOutput = cache[input] { return cachedOutput } else { let output = function(input) cache[input] = output return output } } } } 73
  • 137. Caching Pure Functions @propertyWrapper struct Cached<Input: Hashable, Output> { private var functionWithCache: (Input) -> Output init(wrappedValue: @escaping (Input) -> Output) { self.functionWithCache = Cached.addCachingLogic(to: wrappedValue) } var wrappedValue: (Input) -> Output { /* ... */ } private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output { /* ... */ } } 74
  • 138. Caching Pure Functions @propertyWrapper struct Cached<Input: Hashable, Output> { private var functionWithCache: (Input) -> Output init(wrappedValue: @escaping (Input) -> Output) { self.functionWithCache = Cached.addCachingLogic(to: wrappedValue) } var wrappedValue: (Input) -> Output { /* ... */ } private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output { /* ... */ } } 74
  • 139. Caching Pure Functions @propertyWrapper struct Cached<Input: Hashable, Output> { private var functionWithCache: (Input) -> Output init(wrappedValue: @escaping (Input) -> Output) { self.functionWithCache = Cached.addCachingLogic(to: wrappedValue) } var wrappedValue: (Input) -> Output { /* ... */ } private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output { /* ... */ } } 74
  • 140. Caching Pure Functions @propertyWrapper struct Cached<Input: Hashable, Output> { private var functionWithCache: (Input) -> Output init(wrappedValue: @escaping (Input) -> Output) { self.functionWithCache = Cached.addCachingLogic(to: wrappedValue) } var wrappedValue: (Input) -> Output { /* ... */ } private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output { /* ... */ } } 74
  • 141. Caching Pure Functions @propertyWrapper struct Cached<Input: Hashable, Output> { private var functionWithCache: (Input) -> Output init(wrappedValue: @escaping (Input) -> Output) { self.functionWithCache = Cached.addCachingLogic(to: wrappedValue) } var wrappedValue: (Input) -> Output { /* ... */ } private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output { /* ... */ } } 74
  • 142. Caching Pure Functions @propertyWrapper struct Cached<Input: Hashable, Output> { /* ... */ var wrappedValue: (Input) -> Output { get { return self.functionWithCache } set { self.functionWithCache = Cached.addCachingLogic(to: newValue) } } private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output { /* ... */ } } 75
  • 143. Caching Pure Functions @propertyWrapper struct Cached<Input: Hashable, Output> { /* ... */ var wrappedValue: (Input) -> Output { get { return self.functionWithCache } set { self.functionWithCache = Cached.addCachingLogic(to: newValue) } } private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output { /* ... */ } } 75
  • 144. Caching Pure Functions @propertyWrapper struct Cached<Input: Hashable, Output> { /* ... */ var wrappedValue: (Input) -> Output { get { return self.functionWithCache } set { self.functionWithCache = Cached.addCachingLogic(to: newValue) } } private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output { /* ... */ } } 75
  • 145. Caching Pure Functions @propertyWrapper struct Cached<Input: Hashable, Output> { /* ... */ var wrappedValue: (Input) -> Output { get { return self.functionWithCache } set { self.functionWithCache = Cached.addCachingLogic(to: newValue) } } private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output { /* ... */ } } 75
  • 146. Caching Pure Functions @propertyWrapper struct Cached<Input: Hashable, Output> { /* ... */ var wrappedValue: (Input) -> Output { get { return self.functionWithCache } set { self.functionWithCache = Cached.addCachingLogic(to: newValue) } } private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output { /* ... */ } } 75
  • 147. @propertyWrapper struct Cached<Input: Hashable, Output> { /* ... */ var wrappedValue: (Input) -> Output { /* ... */ } private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output { var cache: [Input: Output] = [:] return { input in if let cachedOutput = cache[input] { return cachedOutput } else { let output = function(input) cache[input] = output return output } } } } 76
  • 148. @propertyWrapper struct Cached<Input: Hashable, Output> { /* ... */ var wrappedValue: (Input) -> Output { /* ... */ } private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output { var cache: [Input: Output] = [:] return { input in if let cachedOutput = cache[input] { return cachedOutput } else { let output = function(input) cache[input] = output return output } } } } 76
  • 149. @propertyWrapper struct Cached<Input: Hashable, Output> { /* ... */ var wrappedValue: (Input) -> Output { /* ... */ } private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output { var cache: [Input: Output] = [:] return { input in if let cachedOutput = cache[input] { return cachedOutput } else { let output = function(input) cache[input] = output return output } } } } 76
  • 150. @propertyWrapper struct Cached<Input: Hashable, Output> { /* ... */ var wrappedValue: (Input) -> Output { /* ... */ } private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output { var cache: [Input: Output] = [:] return { input in if let cachedOutput = cache[input] { return cachedOutput } else { let output = function(input) cache[input] = output return output } } } } 76
  • 151. @propertyWrapper struct Cached<Input: Hashable, Output> { /* ... */ var wrappedValue: (Input) -> Output { /* ... */ } private static func addCachingLogic(to function: @escaping (Input) -> Output) -> (Input) -> Output { var cache: [Input: Output] = [:] return { input in if let cachedOutput = cache[input] { return cachedOutput } else { let output = function(input) cache[input] = output return output } } } } 76
  • 152. ! 77
  • 153. Caching Pure Functions struct Trigo { @Cached static var cachedCos = { (x: Double) in cos(x) } } Trigo.cachedCos(.pi * 2) // takes 48.85 µs // value of cos for 2π is now cached Trigo.cachedCos(.pi * 2) // takes 0.13 µs 78
  • 154. Caching Pure Functions struct Trigo { @Cached static var cachedCos = { (x: Double) in cos(x) } } Trigo.cachedCos(.pi * 2) // takes 48.85 µs // value of cos for 2π is now cached Trigo.cachedCos(.pi * 2) // takes 0.13 µs 78
  • 155. Caching Pure Functions struct Trigo { @Cached static var cachedCos = { (x: Double) in cos(x) } } Trigo.cachedCos(.pi * 2) // takes 48.85 µs // value of cos for 2π is now cached Trigo.cachedCos(.pi * 2) // takes 0.13 µs 78
  • 156. Caching Pure Functions struct Trigo { @Cached static var cachedCos = { (x: Double) in cos(x) } } Trigo.cachedCos(.pi * 2) // takes 48.85 µs // value of cos for 2π is now cached Trigo.cachedCos(.pi * 2) // takes 0.13 µs 78
  • 157. Property Wrappers & Functions @Cached is a very good example to grasp how Property Wrappers and functions can work together. But we could devise many others: 79
  • 158. Property Wrappers & Functions @Cached is a very good example to grasp how Property Wrappers and functions can work together. But we could devise many others: → @Delayed(delay: 0.3) and @Debounced(delay: 0.3), to deal with timing 79
  • 159. Property Wrappers & Functions @Cached is a very good example to grasp how Property Wrappers and functions can work together. But we could devise many others: → @Delayed(delay: 0.3) and @Debounced(delay: 0.3), to deal with timing → @ThreadSafe, to wrap a piece of code around a Lock 79
  • 160. Property Wrappers & Functions @Cached is a very good example to grasp how Property Wrappers and functions can work together. But we could devise many others: → @Delayed(delay: 0.3) and @Debounced(delay: 0.3), to deal with timing → @ThreadSafe, to wrap a piece of code around a Lock → etc. 79
  • 162. // Kotlin data class MyServiceResponse(/* ... */) interface MyService { @FormUrlEncoded @POST("/myservice/endpoint") fun call(@Header("Authorization") authorizationHeader: String, @Field("first_argument") firstArgument: String, @Field("second_argument") secondArgument: Int ): Observable<MyServiceResponse> } 81
  • 163. // Kotlin data class MyServiceResponse(/* ... */) interface MyService { @FormUrlEncoded @POST("/myservice/endpoint") fun call(@Header("Authorization") authorizationHeader: String, @Field("first_argument") firstArgument: String, @Field("second_argument") secondArgument: Int ): Observable<MyServiceResponse> } 81
  • 164. Property Wrappers can't be composed (yet)... 82
  • 165. ...so we're going to implement GET instead 83
  • 166. typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> () @propertyWrapper struct GET { private var url: URL init(url: String) { self.url = URL(string: url)! } var wrappedValue: Service<String> { get { return { completionHandler in let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in guard error == nil else { completionHandler(.failure(error!)); return } let string = String(data: data!, encoding: .utf8)! completionHandler(.success(string)) } task.resume() } } } } 84
  • 167. typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> () @propertyWrapper struct GET { private var url: URL init(url: String) { self.url = URL(string: url)! } var wrappedValue: Service<String> { get { return { completionHandler in let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in guard error == nil else { completionHandler(.failure(error!)); return } let string = String(data: data!, encoding: .utf8)! completionHandler(.success(string)) } task.resume() } } } } 84
  • 168. typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> () @propertyWrapper struct GET { private var url: URL init(url: String) { self.url = URL(string: url)! } var wrappedValue: Service<String> { get { return { completionHandler in let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in guard error == nil else { completionHandler(.failure(error!)); return } let string = String(data: data!, encoding: .utf8)! completionHandler(.success(string)) } task.resume() } } } } 84
  • 169. typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> () @propertyWrapper struct GET { private var url: URL init(url: String) { self.url = URL(string: url)! } var wrappedValue: Service<String> { get { return { completionHandler in let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in guard error == nil else { completionHandler(.failure(error!)); return } let string = String(data: data!, encoding: .utf8)! completionHandler(.success(string)) } task.resume() } } } } 84
  • 170. typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> () @propertyWrapper struct GET { private var url: URL init(url: String) { self.url = URL(string: url)! } var wrappedValue: Service<String> { get { return { completionHandler in let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in guard error == nil else { completionHandler(.failure(error!)); return } let string = String(data: data!, encoding: .utf8)! completionHandler(.success(string)) } task.resume() } } } } 84
  • 171. typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> () @propertyWrapper struct GET { private var url: URL init(url: String) { self.url = URL(string: url)! } var wrappedValue: Service<String> { get { return { completionHandler in let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in guard error == nil else { completionHandler(.failure(error!)); return } let string = String(data: data!, encoding: .utf8)! completionHandler(.success(string)) } task.resume() } } } } 84
  • 172. typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> () @propertyWrapper struct GET { private var url: URL init(url: String) { self.url = URL(string: url)! } var wrappedValue: Service<String> { get { return { completionHandler in let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in guard error == nil else { completionHandler(.failure(error!)); return } let string = String(data: data!, encoding: .utf8)! completionHandler(.success(string)) } task.resume() } } } } 84
  • 173. typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> () @propertyWrapper struct GET { private var url: URL init(url: String) { self.url = URL(string: url)! } var wrappedValue: Service<String> { get { return { completionHandler in let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in guard error == nil else { completionHandler(.failure(error!)); return } let string = String(data: data!, encoding: .utf8)! completionHandler(.success(string)) } task.resume() } } } } 84
  • 174. typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> () @propertyWrapper struct GET { private var url: URL init(url: String) { self.url = URL(string: url)! } var wrappedValue: Service<String> { get { return { completionHandler in let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in guard error == nil else { completionHandler(.failure(error!)); return } let string = String(data: data!, encoding: .utf8)! completionHandler(.success(string)) } task.resume() } } } } 84
  • 175. typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> () @propertyWrapper struct GET { private var url: URL init(url: String) { self.url = URL(string: url)! } var wrappedValue: Service<String> { get { return { completionHandler in let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in guard error == nil else { completionHandler(.failure(error!)); return } let string = String(data: data!, encoding: .utf8)! completionHandler(.success(string)) } task.resume() } } } } 84
  • 176. typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> () @propertyWrapper struct GET { private var url: URL init(url: String) { self.url = URL(string: url)! } var wrappedValue: Service<String> { get { return { completionHandler in let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in guard error == nil else { completionHandler(.failure(error!)); return } let string = String(data: data!, encoding: .utf8)! completionHandler(.success(string)) } task.resume() } } } } 84
  • 177. typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> () @propertyWrapper struct GET { private var url: URL init(url: String) { self.url = URL(string: url)! } var wrappedValue: Service<String> { get { return { completionHandler in let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in guard error == nil else { completionHandler(.failure(error!)); return } let string = String(data: data!, encoding: .utf8)! completionHandler(.success(string)) } task.resume() } } } } 84
  • 178. typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> () @propertyWrapper struct GET { private var url: URL init(url: String) { self.url = URL(string: url)! } var wrappedValue: Service<String> { get { return { completionHandler in let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in guard error == nil else { completionHandler(.failure(error!)); return } let string = String(data: data!, encoding: .utf8)! completionHandler(.success(string)) } task.resume() } } } } 84
  • 179. typealias Service<Response> = (_ completionHandler: @escaping (Result<Response, Error>) -> Void) -> () @propertyWrapper struct GET { private var url: URL init(url: String) { self.url = URL(string: url)! } var wrappedValue: Service<String> { get { return { completionHandler in let task = URLSession.shared.dataTask(with: self.url) { (data, response, error) in guard error == nil else { completionHandler(.failure(error!)); return } let string = String(data: data!, encoding: .utf8)! completionHandler(.success(string)) } task.resume() } } } } 84
  • 180. struct API { @GET(url: "https://samples.openweathermap.org/data/2.5/weather?id=2172797&appid=b6907d289e10d714a6e88b30761fae22") static var getCurrentWeather: Service<String> } API.getCurrentWeather { result in print(result) } success("{"coord":{"lon":145.77,"lat":-16.92},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds", "icon":"03n"}],"base":"stations","main":{"temp":300.15,"pressure":1007,"humidity":74,"temp_min":300.15,"temp_max": 300.15},"visibility":10000,"wind":{"speed":3.6,"deg":160},"clouds":{"all":40},"dt":1485790200,"sys":{"type":1,"id": 8166,"message":0.2064,"country":"AU","sunrise":1485720272,"sunset":1485766550},"id":2172797,"name":"Cairns","cod":200}") 85
  • 181. struct API { @GET(url: "https://samples.openweathermap.org/data/2.5/weather?id=2172797&appid=b6907d289e10d714a6e88b30761fae22") static var getCurrentWeather: Service<String> } API.getCurrentWeather { result in print(result) } success("{"coord":{"lon":145.77,"lat":-16.92},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds", "icon":"03n"}],"base":"stations","main":{"temp":300.15,"pressure":1007,"humidity":74,"temp_min":300.15,"temp_max": 300.15},"visibility":10000,"wind":{"speed":3.6,"deg":160},"clouds":{"all":40},"dt":1485790200,"sys":{"type":1,"id": 8166,"message":0.2064,"country":"AU","sunrise":1485720272,"sunset":1485766550},"id":2172797,"name":"Cairns","cod":200}") 85
  • 182. struct API { @GET(url: "https://samples.openweathermap.org/data/2.5/weather?id=2172797&appid=b6907d289e10d714a6e88b30761fae22") static var getCurrentWeather: Service<String> } API.getCurrentWeather { result in print(result) } success("{"coord":{"lon":145.77,"lat":-16.92},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds", "icon":"03n"}],"base":"stations","main":{"temp":300.15,"pressure":1007,"humidity":74,"temp_min":300.15,"temp_max": 300.15},"visibility":10000,"wind":{"speed":3.6,"deg":160},"clouds":{"all":40},"dt":1485790200,"sys":{"type":1,"id": 8166,"message":0.2064,"country":"AU","sunrise":1485720272,"sunset":1485766550},"id":2172797,"name":"Cairns","cod":200}") 85
  • 185. Recap → Property Wrappers let us wrap properties with custom behavior 87
  • 186. Recap → Property Wrappers let us wrap properties with custom behavior → They are very good at removing boilerplate 87
  • 187. Recap → Property Wrappers let us wrap properties with custom behavior → They are very good at removing boilerplate → They provide great ergonomics for ubiquitous constructs 87
  • 188. Recap → Property Wrappers let us wrap properties with custom behavior → They are very good at removing boilerplate → They provide great ergonomics for ubiquitous constructs → They can also be applied on functions 87
  • 189. Recap → Property Wrappers let us wrap properties with custom behavior → They are very good at removing boilerplate → They provide great ergonomics for ubiquitous constructs → They can also be applied on functions → They can hurt code readability if abused 87
  • 190. Recap → Property Wrappers let us wrap properties with custom behavior → They are very good at removing boilerplate → They provide great ergonomics for ubiquitous constructs → They can also be applied on functions → They can hurt code readability if abused → They will probably lead to new and exciting frameworks 87
  • 191. ! 88
  • 192. Property Wrappers or how Swift decided to become Java Vincent Pradeilles @v_pradeilles – Worldline