@attheodo

Demystifying Swift keywords you use every day

Agenda

  • Closures and value-capturing

    • weak/self/unowned

  • @autoclosure

  • Metatypes

 

 

Closures

There are two types of closures...

  • @escaping

    • ​Can be stored, passed around to other closures, get executed in the future...

  • @noescape

    • ​They execute the code immediately and cannot be run later or passed over...

By default, closures in swift are marked as @noescape!

Closures

There are two types of closures...

  • @escaping

 

 

  • @noescape

 

DispatchQueue.main.async {
	print("Async in main thread")
}
[1, 2, 3].forEach { number in
	print(number)
}

Closures & Value capturing

  • When we use a closure, most of the time, we need to use variables from the surrounding context

  • We are able to use these variables by capturing them inside the closure.

getUser(withId: id) { [userRepository] result in
	userRepository.addUsers(result.users)
}

Capture list

Closure arg

getUser(withId id: String, 
       completion: @escaping RequestResult<User>) 
{
	...
    completion(.success(users))
}

Closures & Value capturing

struct Calculator {
    var a: Int
    var b: Int

    var sum: Int {
        return a + b
    }
}

Closures & Value capturing

let calculator = Calculator(a: 3, b: 5)

let closure = {
    print("The result is \(calculator.sum)")
}

closure() // "The result is 8"

calculator.a = 100

closure() // "The result is 105"

Closure without capturing calculator

Closures & Value capturing

var calculator = Calculator(a: 3, b: 5) // 0x618000220400

let closure = {
    calculator = Calculator(a: 33, b: 55) // 0x610000221be0
}

print(calculator) // calculator has address 0x618000220400

closure()

print(calculator) // calculator has address 0x610000221be0

Closure without capturing calculator

Closures & Value capturing

Calculator "leaks" outside the scope of the closure...

Calculator
closure

Closures & Value capturing

We need to "capture" calculator, so that the closure has an immutable copy only... and changes outside the closure's scope won't affect the "local" copy...

let calculator = Calculator(a: 3, b: 5)

let closure = { [calculator]
    print("The result is \(calculator.sum)")
}

closure() // "The result is 8"

calculator.a = 100

closure() // "The result is 8"

Closures & Value capturing

You can capture multiple reference or value types in

closures...

let closure = { [user, userRepository] item in
    userRepository.add(user, item: item)
}

... or even create aliases for them...

let closure = { [face = user, repo = userRepository] in
    repo.add(face)
}

Closures & Value capturing

Implicit use of 'self' in closure; use 'self.' to 
make capture semantics explicit

Closures & Value capturing

Relax. The compiler just wants to save you from trouble...

Closures & Value capturing

... cause now, your might have a retain cycle!

Closures & Value capturing

  • Remember @escaping?
  • self might actually "escape" to who knows where event though its original instance should be long gone...
  • Closures by default, create a strong reference to their captured values!
  • Enter weak/unowned

Closures & Value capturing

let closure = { [weak self] item in
    self?.addItemToInventory(item)
}
  • weak  turns every captured value into an optional
let closure = { [unowned self] item in
    self.addItemToInventory(item)
}
  • unowned  turns every captured into an explicitly unwrapped optional ☢️
    • ​very, very, very unsafe
  • Both weak & unowned, allow you to avoid retain cycles.

Closures & Value capturing

@autoclosure

  • @autoclosure attribute enables you to define an argument that automatically gets wrapped in a closure.

  • It’s primarily used to defer execution of a (potentially expensive) expression to when it’s actually needed, rather than doing it directly when the argument is passed.

  • But in day to day coding, let's say it just makes some stuff a little more readable...

@autoclosure

func animate(_ animation: @escaping () -> Void) {
    UIView.animate(withDuration: duration, animations: { animation() })
}

func animate(_ animation: @autoclosure @escaping () -> Void)
    UIView.animate(withDuration: duration, animations: animation)
}

Metatypes

String.Self vs String.Type
  • A metatype type refers to the type of any type (including class, structures, enums and protocols)

  • The name of that type is the name of that type followed by .Type

    • SomeClass  SomeClass.Type

    • SomeProtocol SomeProtocol.Protocol

  • Instance.self returns just the value of the Metatype

Metatypes

  • The Self type isn't a specific type. It rather lets you conveniently refer to a type without knowing it or repeating it.
  • It can only appear:
    • As a return type of a method
    • As a return type of a subscript
    • As a return type of a read only property
    • In the body of a method
  • Self type refers to the same type returned by type(of:) (which is always a Metatype)

Metatypes

func printType<T>(of type: T.Type) {
    // or you could do "\(T.self)" directly and
    // replace `type` parameter with an underscore
    print("\(type)") 
} 

printType(of: Int.self) // this should print Swift.Int

class Superclass {
    func f() -> Self { return self }
}
let x = Superclass()
print(type(of: x.f())) // Prints "Superclass"

Metatypes

  • Generic, street-smart, rule-of-thumb:
    • Use .Type when declaring parameters
    • Use .self when passing arguments
    • Use Self mostly when "returning" 

Questions?

Demystifying Swift keywords

By Thanos Theodoridis

Demystifying Swift keywords

  • 184