Language/Swift

Swift에서 Closure는 뭘까?

jaewpark 2024. 6. 29. 12:01

Closure

코드에서 전달하고 사용할 수 있는 독립된 기능 블록self-contained blocks of functionality이다. 다른 언어의 클로저, 익명 함수, 람다 및 블록과 유사하다.

// 일반적인 표현식
{ (<#parameters#>) -> <#return type#> in
     <$statements#>
}

// closure: ascendingOrder 
let ascendingOrder: (Int, Int) -> Bool = { (lhs, rhs) -> Bool in
  return lhs < rhs ? true : false
}

let a = [4, 2, 3, 1]
let b = a.sorted(by: ascendingOrder)

print(b)

value capture

클로저의 주변 context의 상수와 변수를 capture 할 수 있다. 더 이상 존재하지 않더라도 클로저 본문 내에서 값을 참조하고 수정할 수 있습니다.
접근할 수 있는 상수와 변수들을 close over 한다.

incrementer 클로저는 totalincrementAmount close over 한다.
내부에서는 변수들에 접근할 수 있으며, 호출될 때마다 상태가 유지된다.

total은 클로저가 호출될 때마다 업데이트 되며, incrementAmount는 클로저가 생성될 때 포착된 값을 유지한다.

func makeIncrementer(incrementAmount: Int) -> () -> Int {
    var total = 0

    let incrementer: () -> Int = {
        total += incrementAmount
        return total
    }

    return incrementer
}

let incrementByTwo = makeIncrementer(incrementAmount: 2)
print(incrementByTwo()) // 2
print(incrementByTwo()) // 4

closure는 참조 타입이다.
서로 다른 두 상수나 변수에 클로저를 할당하면 두 상수나 변수가 모두 동일한 클로저를 참조하게 된다.

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen() // returns a value of 50

incrementByTen() // returns a value of 60

escape closure

매개변수 유형 앞에 @escaping 넣으면 escape 한다고 말할 수 있다.
함수는 작업을 시작한 후 반환하지만 작업이 완료될 때까지 클로저가 호출되지는 않는다.

기본적으로 noescape 이며, 함수가 종료될 때 클로저도 함께 소멸됨을 의미한다. escaping 은 함수가 종료된 후에도 호출될 수 있다. 그렇기에 주로 비동기 작업에서 사용된다.

// noescape
func performOperation(operation: () -> Void) {
    print("Performing operation")
    operation()
    print("Operation performed")
}

performOperation {
    print("Hello, world!")
}

// Performing operation
// Hello, world!
// Operation performed
// escaping
func performAsyncOperation(completion: @escaping () -> Void) {
    print("Starting async operation")
    DispatchQueue.global().async {
        sleep(2)
        print("Async operation complete")
        completion()
    }
    print("Async operation started")
}

performAsyncOperation {
    print("Completion handler called")
}

// Starting async operation
// Async operation started
// (2초 후)
// Async operation complete
// Completion handler called

capture list

외부 변수나 상수를 capture 할 때 사용하는 메커니즘이다.
기본적으로 클로저는 강한 참조로 capture 한다.

매개 변수 앞에 대괄호로 둘러싸여 있으며, 순환 참조 문제를 해결하기 위해
self을 약한 참조로 캡쳐하도록 [weak self]를 사용한다.

self 뿐만 아니라 여러 항목을 포함할 수 있다.

참고: implicit weak self capture

trailing closure

함수의 마지막 인수로 클로저 식을 전달해야 하는데 클로저 식이 긴 경우, 대신 후행 클로저로 작성하는 것이 유용할 수 있다.

한 줄에 인라인으로 작성할 수 없을 때 가장 유용하다.

func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
    if let picture = download("photo.jpg", from: server) {
        completion(picture)
    } else {
        onFailure()
    }
}

loadPicture(from: someServer) { picture in
    someView.currentPicture = picture
} onFailure: {
    print("Couldn't download the next picture.")
}