Closure 기본

DevMinion·2022년 8월 5일
2

Closure에 대해서 예제와 함께 알아보쟈🙌

Closure?

Closure란 저번 Function포스팅 에서도 등장한 개념이다. Function 또한 이름이 있는 Closure를 말하는 것이었으니.

오늘 포스팅할 Closure는 이름이 없는(Unnamed Closure) 클로저를 주로 칭한다.

그리고 Closure도 전에 포스팅한 First-Class Citizen이기에 해당 조건에 만족하며 특성도 같다. 이를 유념하며 시작하자.

Closure 기본

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()로 대체할 수 있다.)

일회용 Closure

({ () -> () in
	...
    Code
    ...
})()

해당 형태로 Closure를 작성하면 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에 비해 속도가 느리다. 컴파일 단계에서 컴파일러가 타입을 추론하는 시간이 필요하기 때문이다.

이는 나중에 따로 포스트를 작성하려 한다.

파라미터, in 키워드 생략

$를 사용해서 매개변수를 생략할 수 있다. $0, $1 이런 식으로 사용하는 것을 Shortand Argument Name이라고 한다. 애플리케이션을 개발하며 자주 사용했을 것이다. 이를 통해 우리는 매개변수도 생략할 수 있다.

//MARK: 파라미터 생략
someFunc(closure: {
    return "My name is \($0), age is \($1)"
})

이렇게 파라미터를 생략하고 심지어 in 키워드 마저 생략할 수 있다. 결과를 확인하면,

이렇게 정상적으로 출력이 가능하다.

return 키워드 생략

마지막으로 return까지 생략할 수 있다!

someFunc(closure: {
    "My name is \($0), age is \($1)"
})

이렇게... return까지 생략이 가능하다. 단, return은 Closure 내부에 return 코드 단 한 줄만 있을 경우 생략이 가능하다. 중간에 다른 코드가 끼이면 생략할 수 없다. 누가 return문인지 모르기 때문.

후행클로저(Trailing Closure)

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)"
}

기가 막힌다 그죠?

클로저의 심화는 이어서 포스팅하도록 하겠다.

References(Thx🤙)

[Swift) 클로저(Closure) 정복하기(2/3) - 문법 경량화 / @escaping / @autoclosure] - 개발자 소들이
[[SWIFT] Closure(클로저)] - 동동이

profile
iOS를 개발하는 미니언

0개의 댓글