정보/레벨 1

Swift에서 클로저(Closure)란 무엇이며, 어떻게 사용하나요?

밤새는 탐험가89 2024. 8. 21. 13:11

Swift에서 클로저(Closure)란 무엇이며, 어떻게 사용하나요?

**클로저(Closure)**코드에서 일급 객체로 취급되는 함수의 일종입니다. 함수와 유사하게 코드 블록을 캡처하고 전달할 수 있지만, 이름이 없고 주변의 변수와 상수를 캡처할 수 있다는 점에서 함수와 구분됩니다. 클로저는 보통 함수나 메서드의 인수로 전달되어, 나중에 실행될 코드 블록을 정의하는 데 사용됩니다.

 

클로저의 기본 형태는 다음과 같습니다

{ (parameters) -> returnType in
    // 클로저 바디
}

 

 

예를 들어, 정수를 두 개 받아 그 합을 반환하는 클로저는 다음과 같이 작성할 수 있습니다

let sumClosure: (Int, Int) -> Int = { (a, b) in
    return a + b
}

let result = sumClosure(3, 5) // result는 8

 

 

Swift에서는 클로저 문법을 간소화할 수 있습니다:

  • 파라미터와 리턴 타입을 추론할 수 있으면 생략할 수 있습니다.
  • 단일 표현식으로 구성된 클로저는 return 키워드를 생략할 수 있습니다.
  • 축약 인자 이름(e.g., $0, $1)을 사용할 수 있습니다.

간단히 말하면 다음과 같이 축약할 수 있습니다:

let sumClosure: (Int, Int) -> Int = { $0 + $1 }
let result = sumClosure(3, 5) // result는 8

 

 

⭐️ 클로저가 왜 중요한가요?

Swift에서는 클로저가 자주 사용돼요. 예를 들어, 어떤 작업을 끝낸 후에 해야 할 일을 나중에 지정할 수 있는데, 이때 클로저가 유용해요. "작업이 끝나면 이 코드를 실행해"라고 말하는 것과 같죠.

 

 

⭐️ 캡처 리스트(Capture List)란 무엇인가요?

 클로저는 주위의 변수들을 "캡처"할 수 있어요. 쉽게 말해, 클로저는 그 클로저를 만든 순간에 존재하던 변수들을 기억하고 있어요. 하지만, 이런 캡처된 변수들이 메모리에 계속 남아있게 만들 수도 있어요. 그래서 필요하지 않은 변수가 메모리에 남아 있지 않도록 관리하는 방법이 바로 캡처 리스트에요.

 

캡처 리스트는 클로저가 특정 변수들을 "약하게" 잡아서 메모리 문제가 생기지 않도록 도와줘요.

 

클로저의 캡처 리스트(Capture List)는 어떤 역할을 하나요?

**캡처 리스트(Capture List)**클로저가 주변 환경에서 변수나 상수를 캡처할 때, 그 변수나 상수가 어떻게 관리될지를 명시하는 데 사용됩니다. 클로저는 캡처한 변수나 상수를 strong 참조로 캡처하는데, 이로 인해 메모리 누수가 발생할 수 있습니다.

이러한 문제를 해결하기 위해 캡처 리스트를 사용하여 캡처할 변수의 참조 타입을 weak 또는 unowned로 지정할 수 있습니다.

 

캡처 리스트는 클로저의 파라미터 리스트 앞에 위치하며, 대괄호 []로 묶어 사용합니다. 예를 들어:

class MyClass {
    var value = 10

    func doSomething() {
        let closure = { [weak self] in
            guard let strongSelf = self else { return }
            print(strongSelf.value)
        }

        closure()
    }
}

 

위의 코드에서 [weak self]는 self를 약한 참조로 캡처하여, 클로저가 실행될 때 메모리 누수를 방지합니다.

 

@escaping 클로저와 non-escaping 클로저의 차이점은 무엇인가요?

클로저는 기본적으로 non-escaping입니다. 이는 클로저가 함수의 실행이 종료되기 전에 호출된다는 것을 의미합니다. 클로저가 함수의 범위를 벗어나서 실행될 필요가 없는 경우 non-escaping으로 동작합니다. 이런 경우 Swift는 메모리 관리와 최적화를 위해 더 많은 최적화를 수행할 수 있습니다.

 

@escaping 클로저는 함수의 실행이 종료된 후에도 호출될 수 있는 클로저입니다. 클로저가 비동기 작업과 같이 함수가 반환된 이후에 호출될 경우 @escaping으로 선언해야 합니다.

 

⭐️ 보통 클로저는 함수를 호출할 때 그 자리에서 바로 실행돼요. 이런 클로저를 non-escaping 클로저라고 해요. 그냥 클로저라고 하면 대부분 이 타입이에요.

 

하지만, 어떤 클로저들은 함수가 끝난 후에도 나중에 실행될 수 있어요. 이런 클로저는 @escaping이라는 표시를 붙여야 해요. 예를 들어, 시간이 걸리는 작업을 할 때, 그 작업이 끝난 후에 실행할 코드를 클로저로 넘겨줄 수 있어요. 이때 그 클로저는 함수가 다 끝난 후에도 남아있다가 나중에 실행되죠. ⭐️ 

 

예시:

func performAsyncTask(completion: @escaping () -> Void) {
    DispatchQueue.global().async {
        // 비동기 작업 수행
        completion() // 함수가 반환된 후 클로저 호출
    }
}

 

@escaping이 필요한 이유는 클로저가 함수의 스택 프레임을 벗어나기 때문입니다. 스택 프레임이 종료되더라도 클로저가 계속 참조되어야 하기 때문에, @escaping을 통해 이를 명시적으로 선언해야 합니다.

 

반대로, non-escaping 클로저는 함수 내부에서만 실행되며, 해당 함수가 종료되기 전에 반드시 실행됩니다. 이는 기본 동작이기 때문에 별도의 어노테이션이 필요하지 않습니다.

 

트레일링 클로저(Trailing Closure) 문법은 언제 사용하면 좋나요?

 트레일링 클로저(Trailing Closure) 문법은 함수 호출 시 클로저가 마지막 인수로 전달될 때 사용합니다. 이 문법을 사용하면 함수 호출이 더욱 읽기 쉽고, 간결하게 작성될 수 있습니다.

 

트레일링 클로저의 사용 예는 다음과 같습니다:

func performTask(completion: () -> Void) {
    // 작업 수행
    completion()
}

// 일반적인 클로저 전달 방식
performTask(completion: {
    print("Task completed")
})

// 트레일링 클로저 사용
performTask {
    print("Task completed")
}

 

 

트레일링 클로저는 특히 고차 함수(map, filter, reduce)나 비동기 작업에서 많이 사용됩니다. 코드의 가독성을 높이고, 함수 호출부를 더욱 직관적으로 보이게 할 때 유용합니다.

 

예를 들어, UIView의 애니메이션 블록에서 트레일링 클로저를 자주 사용합니다:

UIView.animate(withDuration: 0.3) {
    // 애니메이션 코드
    view.alpha = 0
}