클로저는 정의된 컨텍스트에서 모든 상수 및 변수에 대한 참조를 캡처하고 저장할 수 있습니다.
공식문서의 표현에 따르면 클로저는 상수와 변수를 닫는 것(close)이라고 하네요. 그래서 이름이 클로저가 됐나봐요.
그래서 닫는게 뭐냐?! 참조를 캡처하는게 뭐냐?! 에 대해 이해를 해보도록 하겠습니다.
우선 스위프트에서 클로저에 포함되는 것은 세 가지 종류가 있습니다.
- Global Function : 이름이 있고 값을 캡처하지 않는 클로저
- Nested Function : 이름이 있고 값을 캡처할 수 있는 클로저
- (Unnamed) Closure : 이름이 없고 주변 컨텍스트에서 값을 캡처할 수 있는 구문
Global Function과 Nested Function(함수 내부에 정의된 함수, 중첩함수)처럼 이름이 있는 우리가 흔히 알고 있는 함수도 클로저에 포함이 되지만,
이번 포스트에선 이름이 없는 Unnamed Closure를 다뤄보겠습니다!
다음과 같은 순서로 설명을 해볼게요!!
- 1급객체(First Class Object)과 클로저
- 클로저 표현 방식과 구문 최적화
- Inferring parameter and return value types from context
- Implicit returns from single-expression closures
- Shorthand argument names
- Trailing closure syntax
- Capturing Value
- Escaping Closure
- Autoclosures
First Class Object
함수와 마찬가지로 클로저는 1급 객체입니다.
1급 객체의 조건은?! 아래의 세가지를 말합니다.
- 변수나 상수에 저장할 수 있다.
- 파라미터로 전달할 수 있다.
- 함수와 메서드에서 리턴할 수 있다.
1급 객체에 대한 설명은 함수편을 참고해주세요~!
클로저는 1급 객체라는 점을 기억하고 표현 방식을 배워보도록 해요!
클로저 표현 방식과 구문 최적화
{(파라미터) -> 반환타입 in
구문
}
클로저는 다음과 같은 형식을 가집니다. 함수의 func
키워드가 없고 모든 요소가 {}
내부에 작성되어 있는 형태입니다.
여기서 잠깐!! 클로저의 parameter는 인-아웃 매개변수, 가변 매개변수일 수 있지만 기본값을 가질 수는 없다고 합니다!
var reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
위와 같이 함수로 따로 정의된 형태가 아닌 인자로 들어가 있는 형태의 클로저를 Inline Closure라고 부릅니다.
클로저의 본문은 in 키워드로 시작 됩니다.
이 키워드는 클로저의 매개변수 및 반환 타입의 정의가 완료된 후 클로저 본문이 시작됨을 알려줍니다.
클로저의 표현식은 4가지 단계를 거쳐 최적화를 시킬 수가 있는데요! 최적화 후에 clean하고 clear~한 스타일을 가진다고.. 하네요?
- Inferring parameter and return value types from context
- Implicit returns from single-expression closures
- Shorthand argument names
- Trailing closure syntax
그럼 클로저를 클린하고 클리어하게 만들어 볼까요?
1단계! Inferring parameter and return value types from context
스위프트는 매개변수의 타입과 리턴 타입을 추론할 수 있습니다.
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
위에서 (String, String) -> Bool
유형의 함수임을 추론할 수 있기 때문에 타입을 클로저 정의에 사용하지 않아도 됩니다.
또한 모든 타입을 추론할 수 있으므로 리턴 화살표(->
)와 parameters 주변의 괄호()
도 생략할 수 있습니다.
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
1번 단계를 거치고 나면 위와 같은 모양이 됩니다.
2단계! Implicit returns from single-expression closures
sorted의 인수의 함수 타입은 Bool값을 반환해야 함이 분명하기 때문에 단일 리턴문이라면 return
키워드를 생략할 수 있습니다.
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
3단계! Shorthand argument names
스위프트는 인라인 클로저에 Shorthand argument name(축약형 인수 이름)을 제공합니다.
$0
, $1
, $2
와 같은 표현으로 클로저의 파라미터를 참조하는 데 사용할 수 있습니다.
Shorthand argument name을 사용하면 클로저 정의에서 파라미터들을 생략할 수 있고,
본문으로만 구성되기 때문에 in 키워드 또한 생략할 수 있습니다.
reversedNames = names.sorted(by: { $0 > $1 } )
4단계! Trailing closure syntax
클로저 표현식을 함수의 마지막 파라미터로 함수에 전달된다면 trailing closure로 사용합니다.
trailing closure는 클로저가 함수의 파라미터지만, 함수 호출 뒤에 클로저를 작성하는 걸 말합니다.
또한 인수레이블을 생략합니다.
reversedNames = names.sorted() { $0 > $1 }
reversedNames = names.sorted { $0 > $1 }
클로저가 함수 또는 메소드의 유일한 인수이고 trailing closure로 전달한다면 괄호()도 제거할 수 있습니다.
trailing closure는 클로저가 길어서 인라인으로 작성할 수 없을 때 유용합니다.
func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
if let picture = download("photo.jpg", from: server) {
completion(picture)
} else {
onFailure()
}
}
이렇게 여러개의 클로저를 사용하는 경우에는 첫 번째 trailing closure만 인수 레이블을 생략하고 나머지 trailing closure에 레이블을 지정합니다.
loadPicture(from: someServer) { picture in
someView.currentPicture = picture
} onFailure: {
print("Couldn't download the next picture.")
}
즉 클로저를 클린하고 클리어하게 만드는 최적화 네가지 스텝!을 한국어로 다시 정리하자면!!
- 파라미터 형식과 리턴 형식을 생략한다.
- 단일 return문이라면 return 키워드를 생략한다.
- 파라미터이름과 in 생략 후 shorthand argument형식($0)으로 바꾼다.
- 클로저가 마지막 파라미터라면 trailing closure로 사용한다.
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
위의 모양이 4step optimizing.....후엔?
reversedNames = names.sorted { $0 > $1 }
깔끔하게 변했죠?!
Capturing Value
젤 처음에 클로저에는 세가지 종류가 있다고 했습니다. 그 중 중첩함수는 값을 캡처할 수 있는 클로저라고 했는데요!!
중첩함수로 설명해보도록 할게요!!
과연 캡처한다는게 뭘 말하는 걸까요?
아래의 예시를 보겠습니다.

함수는 1급 객체이므로 리턴할 수 있죠.
위에 makeIncrementer
함수는 incrementer
라는 중첩 함수를 포함하고 있고, incrementer
함수를 리턴해주고 있습니다.
분명 makeIncrementer는 return 후에 생명주기가 끝이 났습니다. 하지만 내부함수
상수에 담긴 incrementer
함수를 호출하면?!
결과가 5가 나오네요?!!
다시 한번 호출해볼까요?

오... 계속 값이 증가하고 있어요!!
내부 함수 incrementer
는 runningTotal
과 amount
를 캡처하고 있습니다.
그래서 외부 함수은 make
의 생명주기가 끝났지만, 계속해서 runningTotal
과 amount
를 사용할 수 있는 겁니다.
캡처하는 방법은 두가지가 있는데요
1. 복사본을 캡처한다(objective-C)
2. 참조를 캡처한다.(Swift)
스위프트는 외부에 있는 값을 가져올 때 참조를 캡처한다고 합니다.
그리고 클로저는 참조 타입입니다.
위에서 let
키워드로 상수로 내부함수
를 선언했지만 캡처한 변수 runningTotal
를 계속 증가시킬 수 있습니다.
왜냐면, 함수와 클로저가 참조 타입이기 때문입니다!!!
Escaping Closure
NonEscaping하다는 것은 함수의 실행흐름을 벗어나지 않는 것입니다. 즉 함수가 종료되기 전에 종료되는 것인데요.
그럼 반대로 Escaping은 함수 밖에서 실행되는 경우를 말하겠죠??

클로저가 함수의 인자로 전달되지만 함수 밖에서 실행되는 것을 Escaping Closure라고 합니다.
예시를 들어볼게요!!


함수안에서 종료되는 클로저를 NonEscaping 클로저라고 합니다.


하지만 이런식으로 함수 안에서 클로저가 종료되지 않고 함수밖에서 클로저가 실행되는 걸 Escaping 클로저라고 합니다.
이러한 경우 클로저에 @escaping
키워드를 명시해야 합니다. 그렇지 않으면 컴파일 에러가 발생합니다!!!
다음과 같은 경우에 자주 사용되게 됩니다.
- 비동기로 실행되는 경우
- 완료에 따른 처리로 사용되는 경우
쓰이는데가 많지만 대표적으로 서버와 통신할 때 자주 쓰게 되더라구요.
AutoClosure
오토클로저는 인자값이 없으며 함수로 전달될 표현식을 포장하기 위한 클로저입니다.
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"
호출하기 전까지는 실행되지 않습니다.
// 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(customer:)
함수의 인자로 명시적으로 클로저를 넣는 대신
// 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!"
@autoclosure
키워드를 붙여 중괄호를 생략할 수 있습니다.
// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))
print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"
@escaping
키워드와도 함께 사용할 수 있습니다.
출처
'Swift' 카테고리의 다른 글
Swift | Array capacity, reserveCapacity (3) | 2021.10.20 |
---|---|
Swift | 강한 참조와 약한 참조! 순환 참조 사이클 해결방법 (0) | 2021.09.01 |
Swift | 코코아 메모리 관리 모델, ARC (0) | 2021.08.31 |
Swift | 구조체와 클래스의 차이 (0) | 2021.08.31 |