Closure에 대해서 예제와 함께 알아보쟈🙌
Closure란 저번 Function포스팅 에서도 등장한 개념이다. Function 또한 이름이 있는 Closure를 말하는 것이었으니.
오늘 포스팅할 Closure는 이름이 없는(Unnamed Closure) 클로저를 주로 칭한다.
그리고 Closure도 전에 포스팅한 First-Class Citizen이기에 해당 조건에 만족하며 특성도 같다. 이를 유념하며 시작하자.
Closure는 아래와 같은 형태가 기본이다.
{ ( Parameter ) -> Return Type in
...
Code
...
}
예를 들어 간단하게 "Hello"를 출력하는 Closure를 보자.
let helloClosure = { (name: String) -> String in
return "Hello, \(name)"
}
helloClosure("Minion")
First-Class Citizen 포스팅에서 확인했다시피 클로저도 1급 객체이기에 이렇게 변수에 대입하여 사용할 수 있다.
눈치챈 사람이 있을 수 있지만, Function과 같은 기능을 하는 Closure를 만들면 차이가 있다는 것을 알 수 있다.
//MARK: Closure
let helloClosure = { (name: String) -> String in
return "Hello, \(name)"
}
helloClosure("Minion")
//MARK: Function
func helloFunction(name: String) -> String {
return "Hello, \(name)"
}
helloFunction(name: "Minion")
helloFunction의 경우 Parameter Name과 Argument Label이 하나로 쓰였으니 'name'은 Argument Label이자, Parameter Name이다.
하지만!
Closure는 Argument Label을 사용하지 않는다! 따라서 helloClosure의 'name'은 Parameter Name이기에 저렇게 Closure 호출에서 (name:)
을 써주지 않고 그냥 "Minion"을 사용한 것이다.
func doSomtThing2(closure: () -> Void ){
closure()
}
doSomtThing2(closure: {
print("Hello")
})
1급 객체의 특성으로 함수의 파라미터로 함수를 받을 수 있었다. 클로저도 마찬가지로 1급 객체이기에 함수의 파리미터로 클로저를 받을 수 있다.
그럼 클로저의 파라미터로 클로저를??
let normalClosure = { () -> Void in
print("Hello, closureInClosure")
}
let closureInClosure = { (closure: () -> ()) -> Void in
return closure()
}
closureInClosure(normalClosure)
당연히 가능. (Void
는 ()
로 대체할 수 있다.)
({ () -> () in
...
Code
...
})()
해당 형태로 Closure를 작성하면 Closure를 변수나 함수 대입 없이 단 한번 사용할 수 있다.
위에서 알아본 Closure기본은 경량을 거의 하지 않은 기본적인 문법이다. 이를 아래의 설명에 따라 조금 더 보기 쉽도록 축약해서 사용할 수 있다.
코드부터 먹고 보자,
//MARK: 반환 타입 유추, 파라미터 타입 유추
func someFunc(closure: (String, Int) -> String) {
closure("Minion", 24)
}
someFunc(closure: { (name: String, age: Int) -> String in
return "My name is \(name), age is \(age)"
})
우리는 지금까지 위의 코드처럼 파라미터 형식과 리턴타입을 적어주며 Closure를 사용했다. 하지만 이를 타입 유추를 통해 적어주지 않아도 된다.
someFunc(closure: { (name, age) in
return "My name is \(name), age is \(age)"
})
이렇게 name의 String, age의 Int 그리고 리턴 타입인 String을 타입 유추로 적어주지 않았다. 실행하면?
이렇게 타입을 적어줘도, 적어주지 않아도 같은 결과를 유추하는 것을 확인할 수 있다.
단, 타입 유추(혹은 타입 추론(Type Inference))를 사용하면 우리가 체감할 만큼은 아니지만 타입을 적어주는 Type Annotation에 비해 속도가 느리다. 컴파일 단계에서 컴파일러가 타입을 추론하는 시간이 필요하기 때문이다.
이는 나중에 따로 포스트를 작성하려 한다.
$
를 사용해서 매개변수를 생략할 수 있다. $0, $1 이런 식으로 사용하는 것을 Shortand Argument Name이라고 한다. 애플리케이션을 개발하며 자주 사용했을 것이다. 이를 통해 우리는 매개변수도 생략할 수 있다.
//MARK: 파라미터 생략
someFunc(closure: {
return "My name is \($0), age is \($1)"
})
이렇게 파라미터를 생략하고 심지어 in 키워드 마저 생략할 수 있다. 결과를 확인하면,
이렇게 정상적으로 출력이 가능하다.
마지막으로 return까지 생략할 수 있다!
someFunc(closure: {
"My name is \($0), age is \($1)"
})
이렇게... return까지 생략이 가능하다. 단, return은 Closure 내부에 return 코드 단 한 줄만 있을 경우 생략이 가능하다. 중간에 다른 코드가 끼이면 생략할 수 없다. 누가 return문인지 모르기 때문.
Trailing Closure는 말 그대로 꼬리(후행)에 붙는 클로저를 말한다. 앞선👇
func doSomeThing2(closure: () -> Void ){
closure()
}
doSomeThing2(closure: {
print("Hello")
})
예제에서 사용한 코드는 doSomeThing2의 파라미터 부분에 클로저가 들어가 실행되는 형태였다. 이를 후행클로저로 변경하면,
//MARK: Trailing Closure
func withoutTrailing(closure: () -> ()){
closure()
}
withoutTrailing(closure: { () -> () in // <- 여기가
print("withoutTrailing")
})
func withTrailing(closure: () -> ()) {
closure()
}
withTrailing() { () -> () in // <- 여기처럼
print("withTrailing")
}
이렇게 함수의 실행 구문()
뒤에 꼬리처럼 붙혀 클로저를 실행할 수 있다. 이것이 Trailing Closure이다.
파리미터로 두 개 이상의 클로저를 받을 수 있다. 이 경우 아래의 코드에서 첫번째 방식처럼 안에 써줘도 괜찮지만, 우리가 배운 Trailing Closure를 통해 보다 간단하게 표현할 수 있다.
//MARK: Multiple Closure in Parameter with Trailing Closure
//Without Trailing
func fetchWithoutTrailing(success: () -> (), fail: () -> ()) -> Void {
//some Codes
}
fetchWithoutTrailing(success: { () -> () in
print("Success")
}, fail: { () -> () in
print("Fail")
})
//With Trailing
func fetchWithTrailing(success: () -> (), fail: () -> ()) -> Void {
//some Codes
}
fetchWithTrailing(success: { () -> () in
print("Success")
}) { () -> () in
print("Fail")
}
코드가 훨씬 간결하다!
마지막으로 위에서 배운 Trailing Closure를 사용하고 파라미터가 하나라 파라미터를 감싸는 ()
까지 생략한다면,
//MARK: 축약 최종
someFunc {
"My name is \($0), age is \($1)"
}
이렇게 극단적인 경량이 가능하다!! 경량하지 않은 코드와 비교해보자.
//MARK: 코드 비교
//기존 코드
someFunc(closure: { (name: String, age: Int) -> String in
return "My name is \(name), age is \(age)"
})
//경량 최종 버전
someFunc {
"My name is \($0), age is \($1)"
}
기가 막힌다 그죠?
클로저의 심화는 이어서 포스팅하도록 하겠다.
[Swift) 클로저(Closure) 정복하기(2/3) - 문법 경량화 / @escaping / @autoclosure] - 개발자 소들이
[[SWIFT] Closure(클로저)] - 동동이