[문과 코린이의 IT 기록장] 클로저(Closure) - 클로저(Closure)란?, 클로저 문법, 클로저 호출, 1급 객체 특징을 지닌 클로저, 클로저 경량 문법, @autoclosure, @escaping
[문과 코린이의 IT 기록장] 클로저(Closure) - 클로저(Closure)란?, 클로저 문법, 클로저 호출, 1급 객체 특징을 지닌 클로저, 클로저 경량 문법, @autoclosure, @escaping
1. 클로저(Closure)란?
- 사용자의 코드 안에서 전달되어 사용할 수 있는 로직을 가진, 중괄호({})로 구분된 코드의 블럭을 의미한다.
- 이는 일급 객체의 역할을 할 수 있다.
* 일급 객체 : 전달 인자 가능 / 변수 및 상수 등 저장, 전달 가능 / 함수의 반환값 가능
[ 클로저의 종류 ]
1) Named Cloure (= 함수)
func NamedClosure(){
...
}
2) Unnamed Clousre
- 이름을 붙이지 않고 사용하는 함수, 즉 익명함수를 의미한다.
- 일반적으로 클로저라고하면, 이 Unnamed Clousre를 이야기한다.
let UnnamedClosure = { ... }
2. 클로저 문법 (Closure Expression Syntax)
{ (Parameters) -> return type in // Clousre Head
statements // Clousre Body (실행 구문)
}
// in 키워드를 기준으로 Clousre Head와 Closure Body가 구분된다.
ex 1. 파라미터 및 반환값이 존재하지 않는 클로저
let closure = { () -> () in // 파라미터, 반환값 모두 존재 X
...
}
ex 2. 파라미터 및 반환값이 존재하는 클로저
let closure = { (str : String) -> String in // String형 파라미터 str, 반환값 String형
...
}
3. 클로저 호출
1) 클로저가 대입된 변수 및 상수로 호출
// 클로저 정의
let ClosureStr = { (str : String) -> () in
...
}
// 클로저 호출
ClosureStr("Hello World") // 호출구문 ()를 이용해서 실행 가능
// ClosureStr(str : "Hello World") // error 발생
2) 클로저를 직접 실행 (변수 및 상수에 클로저 대입 X)
({ () -> () in
...
})()
// 일회성으로 사용한다.
// 클로저를 ()로 감싸고, 마지막에 호출 구문 ()를 추가해주면 된다.
4. 1급 객체 특징을 지닌 클로저
1) 클로저는 변수 및 상수에 대입 가능하다.
// 변수 및 상수에 대입, 실행 가능
let closure1 = { () -> () in
...
}
// 새로운 변수 및 상수에 대입 가능
let closure2 = closure1
2) 함수의 파라미터 타입으로 클로저 전달 가능
// 함수 정의
func ClosureFunc(closure : () -> ()){ // ClosureFunc함수는 파라미터를 closure형태로 받음
closure() // 클로저 실행
}
// 함수 호출
ClosureFunc(closure : { () -> () in
...
})
// = ClosureFunc(closure : { () -> () in ... })
3) 함수의 반환형으로 클로저를 사용할 수 있다.
// 함수 정의
func ClosureFunc() -> () -> (){ // 파라미터 X, 반환형 : ()->() (파라미터와 반환형이 없는 클로저)
return { () -> () in // 반환형이 파라미터와 반환형이 없는 클로저형태
...
}
}
// 함수 호출
let closure = ClosureFunc() // 클로저를 리턴받아서 closure변수에 저장
closure() // 클로저 실행
5. 클로저 경량 문법
1) 트레일링 클로저 (Trailing Closure)
- 함수의 마지막 파라미터가 클로저일 때, 이를 파라미터 값 형식이 아닌 함수 뒤에 붙여 작성하는 문법
- 이 때 Argument Label은 생략된다.
ex 1. 인라인 클로저 방식
// 함수 정의 : 파라미터가 클로저 하나뿐
func ClosureFunc(closure : () -> ()){
closure()
}
// 함수 호출
ClosureFunc(closure : { () -> () in
...
})
// 이와 같이 함수 호출구문() 안에 작성되어 있는 것을, 인라인 클로저(Inline Closure)라고 함.
ex 2. 트레일링 클로저 방식 (파라미터가 클로저 하나인 경우)
// 함수 정의 : 파라미터가 클로저 하나뿐
func ClosureFunc(closure : () -> ()){
closure()
}
// 함수 호출
// 1) 이와 같이 함수의 가작 마지막에 클로저를 덧붙여 쓰는 것도 가능
ClosureFunc(){ () -> () in
...
}
// 2) 또한 호출구문 ()도 생략 가능
ClosureFunc{ () -> () in
...
}
// 트레일링 클로저에서는 ArgumentLabel이 생략된다.
// 즉 {closure : () -> () in ... } 이러한 형태로 쓰여지지 않고, closure : 가 생략된다는 것이다.
ex 3. 인라인 클로저 & 트레일링 클로저 방식 (파라미터가 여러개인 경우)
// 함수 정의
func fetchData(success: () -> (), fail : () -> ()){
...
}
// Inline Closure
fetchData(success : { () -> () in
...
}, fail : { () -> () in
...
})
// Trailing Closure
fetchData(success : { () -> () in
...
}) { () -> () in
...
}
2) 클로저의 경량 문법
- 문법을 최적화해, 클로저를 단순하게 쓸 수 있도록 하는 것
// 기존 방법
func ClosureFunc(closure : (Int, Int, Int) -> Int){
closure(1,2,3)
}
ClosureFunc(closure : { (a:Int, b:Int, c:Int) -> Int in
return a + b + c
})
- 경량문법 특징
a. 파라미터와 리턴 형식을 생략 가능하다.
ClosureFunc(closure : {(a,b,c) in
return a + b + c
})
b. 파라미터 이름은, Shortand Argument Names으로 대체하고, 이 경우 파라미터이름과 in 키워드를 삭제한다.
* Shortand Argument Names : 파라미터 이름 대신에, $0, $1, ...과 같은 형태를 사용하는 것
ClosureFunc(closure: {
return $0 + $1 + $2
})
c. 단일 리턴문만 남을 경우, return도 생략 가능하다.
ClosureFunc(closure : {
$0 + $1 + $2
})
d. 클로저 파라미터가 마지막 파라미터라면, 트레일링 클로저로 작성한다.
ClosureFunc(){
$0 + $1 + $2
}
// 파라미터가 하나인 경우는 ()도 생략 가느아핟
ClosueFunc{
$0 + $1 + $2
}
6. @autoclosure
- 파라미터로 전달된 일반 구문 & 함수를 클로저로 래핑(Wrapping) 하는 것
func AutoClosureFunc(closure : @autoclosure () -> ()){ // autoClosure는 파라미터가 반드시 존재하지 않아야 한다.
closure()
}
// 이제 AutoClosureFunc함수는, 실제 클로저를 전달받지는 않지만 클로저처럼 사용이 가능하다.
AutoClosureFunc(closure : 1 > 2) // 일반 구문 전달
// 그러나 실제 클로저를 전달하는 것은 아니기에, 파라미터로 값을 넘기는 것처럼 ()를 통해 넘겨줄 수 가 없다.
- autoclosure는 작성되자마자 실행되는 것이 아닌, 지연되어 실행된다는 특징을 가지고 있다.
* 즉, autoclosure는 함수가 실행될 시점에 구문을 클로저로 만들기 때문에, 함수 내에서 클로저를 실행할 때까지 구문이 실행되지 않는다.
7. @escaping
1) non-escaping Closure
- 함수 내부에서 직접 실행하기 위해서만 사용하는 것
- 따라서 파라미터로 받은 클로저를 변수 및 상수에 대입할 수 없고, 중첩함수에서 클로저를 사용할 경우 중첩함수 리턴이 불가능함
- 함수의 실행흐름을 탈출하지 않아, 함수가 종료되기 전에 무조건 실행되어야 한다.
// 지금까지 사용한 클로저 : non-escaping Closure
func ClosureFunc(closure : () -> ()){
let a : () -> () = closure // 에러 발생 (상수 대입 불가능)
DispatchQueue.main.asyncAfter(deadline : .now() + 10){ // 에러 발생 (함수가 종료된 후 클로저 실행 불가능)
closure()
}
...
}
func OverlapClosureFunc(closure : () -> ()) -> () -> () {
func inner(){ // 중첩함수
closure() // 중첩함수 내부에서 클로저 사용
}
return inner // 에러 발생 (중첩함수 리턴 불가능)
}
2) @escaping
- 위의 문제점들을 해결하기 위해 사용하는 것이, @escaping 키워드다.
func ClosureFunc(closure : @escaping () -> ()){ // @escaping 키워드 사용
let a : () -> () = closure // 대입 가능
print("start")
DispatchQueue.main.asyncAfter(deadline: .now() + 10){ // 함수 종료 후 클로저 실행 가능
closure()
}
print("end")
}
ClosureFunc { print("closure") }
// 출력
// start
// end
// closure