[Swift5] Closures 3

Junyoung Park·2022년 3월 6일
0

Swift5 Docs

목록 보기
16/37
post-thumbnail
  • 다음은 Swift 5.6 Doc의 Closures 공부 내용을 정리했음을 밝힙니다.

Closures

참조 타입

클로저와 함수는 모두 참조 타입으로 캡처 중인 변수 값을 바꿀 수 있다.

클로저나 함수에 상수나 변수를 할당한다는 말은 곧 그 값이 함수나 클로저에 대한 참조가 되도록 세팅하는 것이다. 따라서 클로저를 두 개의 상수나 변수에 할당한다면, 할당을 받은 이 값들이 동일한 클로저를 참조하게 된다.

클로저 탈출

클로저가 함수 아규먼트로 넘어갈 때 함수를 탈출하고, 함수가 리턴되면 호출된다. 클로저를 파라미터로 받는 함수를 선언할 때, @escaping이라는 코드를 파라미터 타입 전에 적으면 이스케이프할 수 있다는 뜻.

함수 밖에 정의된 변수에 클로저 값을 저장하는 방법으로 클로저는 이스케이프할 수 있다.

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

함수 바깥의 completionHandlers에 함수 someFunctionWithEscapingClosure 안에서 처리한 completionHandler 값이 저장되는데, 비동기적 처리를 할 때 유용한 기법이다. 이때 @escaping을 붙여야 한다.

자기 자신을 참조하는 이스케이핑 클로저는 클래스 인스턴스를 참조할 때 참조 사이클(reference cycle)을 만들 수도 있기 때문에 주의해야 한다. self를 직접 작성하거나 클로저 캡처 리스트에 포함시켜서 참조 사이클이 없다고 확인해주어야 한다.

completionHandlers를 호출한 뒤에 instance.x 값이 100으로 바뀐 건 someFunctionWithEscapingClosure가 실행되었다는 뜻. self를 체크하자.

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"

completionHandlers.first?()
print(instance.x)
// Prints "100"

캡처 리스트에 self를 포함시키는 doSomething()은 암묵적으로 self를 참조한다.

class SomeOtherClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { [self] in x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

오토 클로저

함수에 아규먼트로 전달되는 표현을 감쌀 때 자동으로 만들어지는 클로저를 오토 클로저라고 한다. 오토 클로저는 아무 아규먼트도 받아들이지 않고, 호출되면 바로 자기 내부에 들어 있는 값을 리턴한다. 오토 클로저는 클로저 표현 대신 일반적인 표현으로도 사용 가능하다는 점에서 코딩할 때 편리하다.

오토 클로저는 클로저를 호출하기 전까지 코드가 작동하지 않는다는 점에서 값 계산을 늦춰준다. 따라서 부작용이 있거나 연산량이 많은 코딩을 넘겨줄 때 이 계산을 늦춰주는 게 유용하다.

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// Prints "5"

let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// Prints "5"

print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// Prints "4"

remove를 통해 0번째 인덱스 원소를 삭제해야 하지만, 클로저가 실제로 호출될 때까지는 연산이 이루어지지 않는다. 이때 customerProvider() -> String형 함수로 파라미터 없이 문자열을 리턴한다.

클로저를 함수에 아규먼트로 패스할 때 값 연산을 늦추는 걸 볼 수 있다.

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// Prints "Now serving Alex!"

serve 함수를 호출할 때 파라미터로 넘겨주는 클로저 안에 remove를 실행하는데, 이때 0번 인덱스의 원소를 파라미터로 넘겨준다.

오토 클로저를 사용할 수도 있다.

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa!"
profile
JUST DO IT

0개의 댓글