Swift - Closures 5

 

Swift의 Closures 정리 5 - Closures Are Reference Types, Escaping Closure

Closures #5


Closures Are Reference Types

  • 앞선 예에서 incrementBySeven과 incrementByTen이 상수임에도 불구하고 runningTotal 변수를 증가시킬 수 있는 것은 함수들이나 closure들이 참조 Type이기 때문이다.
  • 함수나 closure를 상수나 변수에 할당하면 실제로는 그 상수나 변수는 함수나 closure를 참조하게 된다.
  • 결국 하나의 closure를 서로 다른 상수나 변수에 할당하더라도 할당된 두 상수나 변수는 같은 closure를 참조하게 되는 것이다.
let alsoIncreamentByTen = incrementByTen

alsoIncreamentByTen()
// 50을 리턴

alsoIncreamentByTen()
// 60을 리턴

Escaping Closures (탈출 Closures)

  • closure가 함수의 인자로 전달되었으나 함수가 결괏값을 리턴한 후(종료된 후) 호출되는 경우, 함수를 탈출했다고 표현한다.
  • 함수 선언 시 closure 파라미터 타입 앞에 @escaping을 적어 탈출이 허용됨을 표시한다.
  • closure가 탈출을 가능하게 하는 한가지 방법은 함수 외부에서 선언된 변수에 저장시키는 방법이다.
  • 탈출 Closures는 비동기 방식으로 실행되는 함수의 completion handler로 주로 사용된다.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
	// 파라미터로 전달된 closure를 외부에서 선언된 completionHandlers 추가한다.
	// 위 파라미터 선언부에서 @escaping을 빼면 컴파일 에러가 발생한다.
	// Converting non-escaping parameter 'completionHandler' to
	// generic parameter 'Element' may allow it to escape
	completionHandlers.append(completionHandler)
}
  • self가 클래스 인스턴스를 참조하는 경우 이 self를 참조하는 탈출 closure는 특별히 고려할 사항이 있는데, 탈출 closure 내에서 self를 capture 하는 경우 예기치 않게 강력한 참조 싸이클을 만들기가 쉽다.
  • 보통 closure는 그 변수를 사용함으로써 암묵적으로 capture를 하게 되는데, self를 참조하고자 하는 경우 명시적으로 self를 적어주거나 closure의 capture list에 self를 포함해야 한다.
func someFunctionWithNonescapingClosure(closure: () -> Void) {
	closure()
}

// 명시적으로 self를 사용하는 방법
class SomeClass {
	var x = 10
	func doSomething() {
		// 명시적으로 self를 사용
		someFunctionWithEscapingClosure { self.x = 100 }
		// self를 사용하지 않음
		someFunctionWithNonescapingClosure { x = 200 }
	}
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)
// 200이 출력됨

completionHandlers.first?()
print(instance.x)
// 100이 출력됨

// capture list에 self를 포함시키고 암묵적으로 참조하는 방법
class SomeOtherClass {
	var x = 10
	func doSomething() {
		// capture list에 self를 포함
		someFunctionWithEscapingClosure { [self] in x = 100 }
		// self를 사용하지 않음
		someFunctionWithNonescapingClosure { x = 200 }
	}
}
  • 만일 self가 structure나 enumeration의 인스턴스라면 항상 self를 암묵적으로 참조하게 된다.
  • 하지만 이 경우 탈출 closure에서는 self에 대해 변경 가능한 참조를 할 수 없다.
struct SomeStruct {
	var x = 10
	mutating func doSomething() {
		// Error: "Escaping closure captures mutating 'self' parameter"
		// mutating 함수 내에서 참조되었기 때문에 self는 변경 가능하다. 따라서
		// 탈출 closure가 struct의 변경 가능한 self를 참조할 수 없다는 규칙에 따라
		// 에러가 발생을 한다.
		someFunctionWithEscapingClosure { x = 100 }
		// Ok
		someFunctionWithNonescapingClosure { x = 200 }
	}
}