공부 연습장 :-)

스위프트 클로저(Closure)

|

클로저의 값 획득 Capture

  • 클로저는 자신이 정의된 위치의 주변 문맥을 통해 상수나 변수를 캡쳐할 수 있다.
  • 이를 통해 클로저는 주변에 정의한 상수나 변수가 더이상 존재하지않더하도 자신 내부에서 그 값을 참조하거나 수정할 수 있음
  • 이는 클로저가 비동기 작업에 많이 사용되기때문
  • 클로저를 통해 비동기 콜백을 사용하는경우 현재 상태를 미리 획득해두지않으면 실제로 클로저의 기능을 실행하는 순간에는 주변의 상수나 변수가 이미 메모리에 존재하지 않을 수 있기때문

Capture의 문제

  • 클로저 내부에서 참조를 획득한 값은(클로저가 캡쳐한 값) 클로저 내부에서 계속 살아있다. (중첩함수 내부의 지역변수인 경우에도 클로저가 캡쳐했다면 클로저 내부에서 계속 없어지지 않고 있음)
  • 따라서 캡쳐된 값은 언제 호출이 되더라도 클로저 각각 자신만의 참조를 미리 획득했기때문에 계속해서 사용할 수 있으며, 이는 클로저와 인스턴스 사이의 강한 참조 순환 문제 가 발생될 수 있다.

클로저는 참조타입

  • let increment: (()->Int) = makeIncrement(for:2)
  • 위와같이 함수와 클로저를 변수에 할당 하는 것은 상수나 변수에 참조를 설정하는것임
  • 만약 클로저의 참조를 다른상수에 할당해준다면 이는 두 상수가 모두 같은 클로저를 가리킨다는뜻임

탈출클로저 @escaping

  • 함수의 인자로 전달된 클로저가 함수 종료 후에 호출될때 클로저가 함수를 탈출(escape)한다고 표현함
  • @escaping키워드 사용
  • 비동기작업으로 함수가 종료되고 난 후 작업이 끝나고 호출할 필요가 있는 클로저를 탈출클로저로 사용함으로써 함수 실행의 순서를 보장해줄 수 있다.
  • 탈출클로저임이 명확한데 @escaping키워드를 명시하지 않으면 컴파일 오류가 발생함
  • 또한, 탈출클로저에서 self키워드 표시는 필수임 ```swift

typealias VoidvoidClosure = () -> Void

let firstClosure: VoidvoidClosure = { print(“Closure A”) }

let secondClosure: VoidvoidClosure = { print(“Closure B”) }

func returnOneClosure(first: @escaping VoidvoidClosure, second: @escaping VoidvoidClosure, shouldReturnFirstClosure: Bool) -> VoidvoidClosure { // 파라미터로 받은 클로저를 다시 리턴하기때문에 함수 밖으로 탈출하는 탈출 클로저여야함 return shouldReturnFirstClosure ? first : second }

let returnedClosure = returnOneClosure(first: firstClosure, second: secondClosure, shouldReturnFirstClosure: true)

returnedClosure() // returnOneClosure함수의 첫번째 파라미터 firstClosure가 리턴되어 실행. Closure A 출력

var closures: [VoidvoidClosure] = []

// 파라미터로 전달받은 클로저가 함수 외부의 변수에 저장되므로 탈출클로저 func appendClosure(closure: @escaping VoidvoidClosure) { closures.append(closure) }


## withoutActuallyEscaping
> [공식문서
](https://developer.apple.com/documentation/swift/2827967-withoutactuallyescaping)

- 실제로는 탈출하지 않는데 다른 함수에서 탈출 클로저를 요구하는 상황에 해당
```swift
// withoutActuallyEscaping
func hasElements(in array: [Int], match predicate: (Int) -> Bool) -> Bool  {
    return (array.lazy.filter{ predicate($0) }.isEmpty == false)
} //Error: Closure use of non-escaping parameter 'predicate' may allow it to escape

func hasElements(in array: [Int], match predicate: (Int) -> Bool) -> Bool  {
    return withoutActuallyEscaping(predicate, do: { escapePredicate in
         return (array.lazy.filter{ escapePredicate($0) }.isEmpty == false)
    })
}
  • withoutActuallyEscaping의 첫번째 파라미터로 탈출클로저인 척 해야하는 비탈출클로저 전달
  • withoutActuallyEscaping(_:do:)에서 do파라미터는 이 비탈출클로저를 또 매개변수로 전달받아 실제로 작업을 실행할 탈출클로저를 전달함

Comments