Swift를 배우면서 가장 많이 마주치는 개념 중 하나가 바로 클로저(Closure)이다.
근데 이게 생긴 것도 낯설고, 문법도 헷갈리고, 대체 왜 쓰는지도 잘 모르겠다.
(이게 무슨소리인지 이해하는데 대략 2일정도 소요된거같다.... 허허)
(사실 완전히 이해하지는 못했지만, 계속 마주치면서 이해해보려고 한다.)
클로저는 이름 없는 함수이다.
그리고 이 함수는 변수처럼 저장하거나, 함수의 인자로 전달하거나, 함수에서 리턴할 수도 있다.
Swift에서는 아래처럼 쓴다:
let greet = {
print("안녕하세요")
}
greet() // 출력: 안녕하세요
그럼 여기서 궁금한 게 하나 생긴다.
"굳이 이렇게까지 해야 돼? 그냥 함수 쓰면 되는 거 아니야?"
클로저는 실전에서 정말 많이 쓰인다. 그 이유는 이렇다.
예를 들어 이런 함수가 있다고 하자.
func makeCounter() -> () -> Int {
var count = 0
return {
count += 1
return count
}
}
이건 count라는 값을 내부에서 유지하면서 점점 늘려주는 클로저를 리턴한다.
let counter = makeCounter()
counter() // 1
counter() // 2
이처럼 클로저는 함수 이상의 역할을 할 수 있다.
‘기억하는 함수’라고 생각하면 된다.
Swift에서 클로저는 여러 방식으로 정의할 수 있다.
(예시를 쭉 보면 감이 올지도.......?)
| 방식 | 예시 코드 |
|---|---|
| 기본 정의 | let greet = { print("hi") } |
| 파라미터 & 리턴 | let add = { (a: Int, b: Int) -> Int in return a + b } |
| 타입 추론 | let add = { a, b in a + b } |
| 축약 표현 | let add = { $0 + $1 } |
| 함수 인자로 전달 | sorted(by: { $0 < $1 }) 또는 sorted { $0 < $1 } |
| 함수에서 반환 | func makeAdder(x: Int) -> (Int) -> Int |
| 변수 캡처 | 클로저 안에서 외부 변수 사용 가능 |
| @escaping | 비동기 작업 시 클로저를 나중에 실행할 때 사용 |
| @autoclosure | 표현식 자체를 넘겨서 나중에 평가할 때 사용 |
예를 들어 이런 식으로도 쓸 수 있다:
func perform(action: () -> Void) {
action()
}
perform {
print("실행됨!")
}
→ 이런 방식은 trailing closure 문법이라고 부른다. 가독성이 훨씬 좋다.
func makeAdder(_ x: Int) -> (Int) -> Int {
return { y in x + y }
}
let add5 = makeAdder(5)
add5(3) // 결과: 8
let nums = [1, 2, 3]
let squared = nums.map { $0 * $0 } // [1, 4, 9]
func asyncTask(completion: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
completion()
}
}
→ 비동기에서 나중에 클로저를 실행해야 하니 @escaping을 붙여줘야 한다.
클로저는 이름 없는 함수로, 콜백 전달이나 상태 유지, 코드 축약 등에 매우 유용하다.
Swift에서는 클로저 문법이 매우 강력하고 다양하기 때문에
map, filter, sorted 같은 고차 함수에서도 자주 활용된다.
() => {}, Python의 lambda, Java의 () -> {}와 개념적으로 비슷하다.[weak self] 같은 문법도 꼭 알아두자.