Swift. Closures

In-out Parameters

var num1 = 3
var num2 = 5
func swap(_ num1: inout Int, _ num2: inout Int) {
let temp = num1
num1 = num2
num2 = temp
}
swap(&num1, &num2) // num1 = 5, num2 = 3
  1. when a function is called, the value of the argument is copied
  2. in the function body, the copy is modified
  3. when the function returns, the copy is assigned to the original argument

Memory Safety Problem

  • whether the memory access is read or write
  • the duration of memory access is instantaneous or long-term
  • the location in memory
var stepSize = 1func increment(_ number: inout Int) {
number += stepSize
}
increment(&stepSize) // the runtime error// number and stepSize refer to the same variable
// in-out captures &stepSize for having write access,
// and the function captures stepSize for having read access
// A possible solutionvar stepSize = 1
var stepSizeCopy = stepSize
func increment(_ number: inout Int) {
number += stepSize
}
increment(&stepSizeCopy)
stepSize = stepSizeCopy
func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
var firstPlayerScore = 42
var secondPlayerScore = 30
balance(&firstPlayerScore, &secondPlayerScore)
// ok
balance(&firstPlayerScore, &firstPlayerScore)
// the compile-time error
struct Player {
var health: Int
mutating func shareHealth(with player: inout Player) {
player.health = health
}
}
var p1 = Player(health: 100)
var p2 = Player(health: 200)
p2.shareHealth(with: &p1) // ok
p2
.shareHealth(with: &p2) // the compile-time error
var playerInfo = (health: 10, energy: 20)         // tuple
balance(&playerInfo.health, &playerInfo.energy)
// the compile-time error
var player = Player(health: 200, energy: 150) // structure
balance(&player.health, &player.energy)
// the compile-time error
func someFunc() {
var playerInfo = (health: 10, energy: 20)
balance(&playerInfo.health, &playerInfo.energy)
}
someFunc() // ok
  • it’s accessing only to stored properties of the instance, but not computed or class ones
  • the structure is a value of a local variable, but not a global one
  • the structure is either not captured by any closure, or it’s captured only by a non-escaping one
func someFunction(a: inout Int) -> () -> Int {
return { () in return a + 1 } // the compile-time error
}
// Possible solutions// capture list
func someFunction(a: inout Int) -> () -> Int {
return { [a] () in return a + 1 }
}
// local variable
func someFunction(queue: DispatchQueue, x: inout Int) {
var localX = x
defer { x = localX }
queue.async { someMutatingOperation(&localX) }
queue.sync {} // queue should be serial
}
struct Calculator {
var a: Int
var b: Int
var sum: Int {
return a + b
}
}
var calculator = Calculator(a: 3, b: 5)let closure = {
calculator = Calculator(a: 30, b: 50)
}
// Calculator(3, 5)
closure()
// Calculator(30, 50)
var calculator = Calculator(a: 3, b: 5)let closure = {
print(calculator.sum)
}
calculator = Calculator(a: 30, b: 50)
closure()
// prints 80, but we wanted 8
var calculator = Calculator(a: 3, b: 5)let closure = { [calculator] in
print(calculator.sum)
}
calculator = Calculator(a: 30, b: 50)
closure()
// prints 8
class Animal {
public let name: String
init(name: String) {
self.name = name
}
deinit {
print("\(name) is deinited")
}
}
class AnimalHandler {
public var closure: (() -> Void)?

public func setPanda() {
let panda = Animal(name: "panda")
closure = { print(panda.name) }
}
}
let animalHandler = AnimalHandler()
animalHandler.setPanda()
animalHandler.closure = nil
// panda is deinited only when the closure is deinited
class AnimalHandler {
public var closure: (() -> Void)?

public func setPanda() {
var panda: Animal? = Animal(name: "panda")
// capture list makes one more strong reference
closure = { [panda] in print(panda!.name) }
panda = nil // nothing happens
}
}
let animalHandler = AnimalHandler()
animalHandler.setPanda()
animalHandler.closure = nil
// panda is deinited only when the closure is deinited
class AnimalHandler {
public var closure: (() -> Void)?

public func setPanda() {
var panda: Animal? = Animal(name: "panda")
closure = { [weak panda] in print(panda!.name) }
panda = nil // panda is deinited here
}
}
let animalHandler = AnimalHandler()
animalHandler.setPanda()
class SomeClass {
var x = 10
func doSomething() {
// ok because of class
someFuncWithEscapingClosure { self.x = 100 }
}
}
// butstruct SomeStruct {
var x = 10
mutating func doSomething() {
// the compile-time error because of structure
someFuncWithEscapingClosure { self.x = 100 }
}
}
// best practiceclass Service {
var requestState: String = ""
public func getData(callback: @escaping (_ data: Any) -> Void) {
requestState = "request has been stared"
apiService.getDataFromServer() { [weak self] (data) in
self?.requestState = "request has been finished"
callback(data)
}
}
}

Resources

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store