본문 바로가기

iOS/Swift

Swift) 클로저(Closure) 정복하기(2/3) - 문법 경량화 / @escaping / @autoclosure

 

 

 

안녕하세요 :) 소들입니다

이번 포스팅인 저번 포스팅에 이어

클로저에 대해 다뤄보려고 해요!!!

 

저번 포스팅이 클로저의 표현식과 특징?에 대해 알아봤었어요!!!

근데 생각보다  클로저의 구문은 길고.... 또 되게 난해한 형태라 헷갈리기도 하고..;;;

이걸 매번 작성해서 쓴다고? 싶을 정도로 불편했었어요.... 뷁

따라서 이번 포스팅에선

 

클로저를 간단하게 사용할 수 있는 경량 문법

 

에 대해 다뤄보려고 해요!

물론 @escaping, @autoclosure에 대해서두 다뤄봅니다 :D

 

그럼 고고씽~~~~

모든 포스팅은 편의 말투로 합니다~!!

 

 

 

 

1. 트레일링 클로저(Trailing Closure)

 

경량 문법이라더니 웬 이름도 어려운 트레일링 클로저???

싶지만.. 이또한 클로저를 좀 더 보기 편하게 문법을 변형하는 것이기에

경량 문법 중 하나인데, 얘는 이해를 돕기 위해 먼저 알아보고 가겠음!

 

먼저, 트레일링 클로저가 무엇이냐면

 

함수의 마지막 파라미터가 클로저일 때,

이를 파라미터 값 형식이 아닌 함수 뒤에 붙여 작성하는 문법

이때, Argument Label은 생략된다

 

오.. 뭔소린지 모르겠고

자, 여기서 중요한 것은 마지막 파라미터가 클로저, Argument Label은 생략 이 두가지임!!!

 

예제로 보면 어렵지 않으니 예제로 봐보자!!! :)

우리가 이전 포스팅에서 클로저는 1급 객체이기 때문에,

함수의 파라미터로 클로저를 전달할 수 있다고 한 것을 유념해두고!

 

 

 

1-1. 파라미터가 클로저 하나인 함수

 

 다음과 같이 클로저 하나만 파라미터로 받는 함수가 있을 때

 

 

 

func doSomething(closure: () -> ()) {
    closure()
}
 

 

 

이 함수를 호출하려고 하면 어떻게 해야 했냐면

 

 

doSomething(closure: { () -> () in
    print("Hello!")
})
 

 

 

이렇게 해야 했음!

이렇게 클로저가 파라미터의 값 형식으로 함수 호출 구문 () 안에 작성되어 있는 것을 

 Inline Closure 라고 부름!

 

근데 이 경우 마지막에 괄호도 }) 이렇게 되어 있어 헷갈리기 쉽고,

코드를 딱 봤을 때 해석도 쉽지 않음..

 

따라서 이 클로저를 파라미터 값 형식으로 보내는 것이 아닌,

함수의 가장 마지막에 클로저를 꼬리처럼 덧붙여서 쓸 수 있는데,

 

 

doSomething () { () -> () in
    print("Hello!")
}
 

 

 

이렇게 쓰는 것이 바로 Trailing Closure!!!!

자, 여기서 중요한 핵심은 다음 두 가지임

 

1. 파라미터가 클로저 하나일 경우,

이 클로저는 첫 파라미터이자 마지막 파라미터이므로 트레일링 클로저가 가능

2. "closure"라는 ArgumentLabel은 트레일링 클로저에선 생략

 

어렵지 않져 !? :)

근데 여기서 파라미터가 클로저 하나일 경우엔 더 진화해서

호출구문인 ()도 생략할 수도 있음!!!

 

 

doSomething { () -> () in
    print("Hello!")
}
 

 

 

요롷게!!!

 

 

 

1-2. 파라미터가 여러 개인 함수

 

자, 다음과 같이 첫 번째 파라미터로 success라는 클로저를 받고,

두 번째 파라미터로 fail이라는 클로저를 받는 함수가 있음

 

 

func fetchData(success: () -> (), fail: () -> ()) {
    //do something...
}
 

 

 

이런 함수가 있을 때

Inline Closure의 경우

 

 

fetchData(success: { () -> () in
    print("Success!")
}, fail: { () -> () in
    print("Fail!")
})

 

 

이렇게 호출할 거임!!!

근데 트레일링 클로저의 경우 뭐다??

마지막 파라미터의 클로저는 함수 뒤에 붙여 쓸 수 있다!!!

 

 

fetchData(success:  { () -> () in
    print("Success!")
}) { () -> () in
    print("Fail!")
}
 

 

 

따라서 요런 모양으로 사용이 가능한 것:)

파라미터가 여러 개일 경우, 함수 호출 구문 ()를 마음대로 생략해선 안됨!

success란 파라미터는 파라미터 값 타입으로 넘겨주어야 하니까!

 

.

.

 

자 우린 트레일링 클로저에 대해서도 이제 다 알았음!

근데 보면 볼 수록

 

 

 

 

이부분이 너무 지저분하지 않음??

이제 경량 문법을 통해 클로저를 간단하게 바꿔보자 :))))))

 

 

 

 

2. 클로저의 경량 문법

 

클로저의 경량 문법이란, 말 그대로

 

 문법을 최적화 하여 클로저를 단순하게 쓸 수 있게 하는 것

 

을 말함 :-) 왜 진작 안 알려줬냐면,

모든 건 순서가 있으니까.. ^^; 경량화된 문법만 알면

나중에 풀로 작성된 클로저 보면 모르는 문법이라 하게 될 수도 있..고...

 

쨌든 다음과 같은 함수가 있다고 가정 해보셈

 

 

func doSomething(closure: (IntIntInt-> Int) {
    closure(123)
}
 

 

 

이 함수는 파라미터로 받은 클로저를 실행하는데,

이때 클로저의 파라미터로 1, 2, 3이란 숫자를 넘겨주고 있음

 

그렇다면, 실제 이 함수를 호출할 때 어떻게 했어야 했냐면,

 

 

doSomething(closure: { (a: Int, b: Int, c: Int-> Int in
    return a + b + c
})
 

 

 

 

이렇게 클로저를 full로 작성 했어야 했음! ( + Inline Closure 방식)

이를 경량 문법으로 간단하게 바꿔보겠움!!

 

 

 

2-1. 파라미터 형식과 리턴 형식을 생략할 수 있다

 

여기서 파라미터 형식과 리턴 형식은

 

 

doSomething(closure: { (a: Int, b: Int, c: Int-> Int in
    return a + b + c
})
 

 

 

이것들이고! 이들을 다음과 같이

 

 

doSomething(closure: { (a, b, c) in
    return a + b + c
})
 

 

 

생략해서 쓸 수 있음!

 

 

 

2-2. Parameter Name은 Shortand Argument Names으로 대체하고, 이 경우 Parameter Name과 in 키워드를 삭제한다

 

예.. Shortand Argument Names가 뭐냐면여..

Parameter Name 대신 사용할 수 있는 것임..

 

먼저 위에서 Parameter Name과 in 키워드는 다음과 같잖음?

 

 

doSomething(closure: { (abcin
    return a + b + c
})
 

 

 

이때, 이 a b c 라는 Parameter Name 대신에

 

a  $0

b  $1

c  $2

 

이런 식으로 $index를 이용해 Parameter에 순서대로 접근하는 것이 

바로 Shortand Argument Names임!!

($의 갯수는 Parameter의 갯수만큼 있겠지!)

 

따라서, 경량 문법 규칙에 의해 위 구문은

 

 

doSomething(closure: {  
    return $0 + $1 + $2
})
 

 

 

이렇게 간단화 될 수 있음!!

 

 

 

2-3. 단일 리턴문만 남을 경우, return도 생략한다

 

단일 리턴문이란 것은,

 

 

doSomething(closure: {  
    return $0 + $1 + $2
})
 

 

 

이렇게 클로저 내부에 코드가 return 구문 하나만 남은 경우를 말함

이때는 return이란 키워드도 다음과 같이

 

 

doSomething(closure: {  
     $0 + $1 + $2
})
 

 

 

생략할 수 있음!!!

만약 단일 리턴문이 아닐 경우엔,

 

 

 

 

에러가 뜬답니다.. :D

 

 

 

2-4. 클로저 파라미터가 마지막 파라미터면, 트레일링 클로저로 작성한다

 

우린 위에서 트레일링 클로저를 배웠음 :)

따라서 마지막 파라미터인 클로저를 다음과 같이 

 

 

doSomething() {  
     $0 + $1 + $2
}
 

 

 

이렇게 트레일링 클로저로 작성이 가능하단 것을 우린 안다!!!!!!!!!!!!!!!

또한 파라미터가 하나인 경우 ()도 생략 가능하다고 배웠음!!!!!!!

 

 

 

2-5. ()에 값이 아무 것도 없다면 생략한다

 

 

doSomething {  
     $0 + $1 + $2
}
 

 

 

쨔쟌 :D...

이것이 최종화된 클로저의 경량 문법임!!!!!!!!!!!

이렇게 간단할 수가!!!!!!!!!!!!! 여러분은 이제 클로저를 간단하게 쓸 수 있게 되었습니다

ㅊㅋㅊㅋ

 

 

 

 

3. @autoclosure

 

autoclosure가 무엇이냐면

 

파라미터로 전달된 일반 구문 & 함수를 클로저로 래핑(Wrapping) 하는 것

 

을 말함..

무슨 말인지 이해가 또 안 가네 데헷... 이럴 땐 예제를 보자.....!!!!!!!!!!!!

 

먼저, autoclosure는 파라미터 함수 타입 정의 바로 앞에다가 붙여야 함!

다음과 같이

 

 

func doSomething(closure: @autoclosure () -> ()) {
}
 

 

 

요롷게!

이렇게 했을 경우, 이제 closure란 파라미터는

실제 클로저를 전달받지 않지만, 클로저처럼 사용이 가능함!!

 

다만, 클로저와 다른 점은

실제 클로저를 전달하는 것이 아니기 때문에

파라미터로 값을 넘기는 것 처럼 ()를 통해 구문을 넘겨줄 수가 있음

 

 

doSomething(closure: 1 > 2)
 

 

 

이렇게!!! 여기서 1 > 2 는 클로저가 아닌 일반 구문이지만,

실제 함수 내에서는

 

 

func doSomething(closure: @autoclosure () -> ()) {
    closure()
}
 

 

 

이렇게 일반 구문을 클로저처럼 사용할 수 있음!

왜냐? 클로저로 래핑한 것이니까 🌚

 

근데, 주의점은

 

 

 

 

autoclosure를 사용할 경우, 파라미터가 반드시 없어야 함...!!!!

리턴 타입은 상관 없음!

 

 

 

3-1. autoclosure 특징 : 지연된 실행

 

원래, 일반 구문은 작성되자마자 실행되어야 하는 것이 맞음

근데 autoclosure로 작성하면, 함수 내에서 클로저를 실행할 때까지 구문이 실행되지 않음

왜냐? 함수가 실행될 시점에 구문을  클로저로 만들어주니까..

 

따라서, autoclosure의 특징은 

원래 바로 실행되어야 하는 구문이 지연되어 실행한다는 특징이 있음

 

뭐.. ..... 잘쓰면 좋다는데... 아직 와닿진 않네 =_=

와닿을 떄 더 추가하겠음

 

 

 

 

4. @escaping

 

으.. 드디어 이번 포스팅의 마지막이닷

왜 escaping을 마지막에 뒀냐면 escaping과 다음 포스팅과

밀접한 관계이기 때문임 ㅎㅎ

 

자, 우리가 지금까지 짜왔던 다음과 같은 클로저는

 

 

func doSomething(closure: () -> ()) {
}
 

 

 

모두 non-escaping Closure임!! 

무슨 말이냐면, 

 

함수 내부에서 직접 실행하기 위해서만 사용한다

따라서 파라미터로 받은 클로저를 변수나 상수에 대입할 수 없고,

중첩 함수에서 클로저를 사용할 경우, 중첩함수를 리턴할 수 없다

함수의 실행 흐름을 탈출하지 않아, 함수가 종료되기 전에 무조건 실행 되어야 한다

 

실제로 상수에 클로저를 대입해보면,

 

 

 

 

non-escaping parameter라고 에러가 뜸

또한 함수의 흐름을 탈출하지 않는다는 말은,

함수가 종료되고 나서 클로저가 실행될 수 없다는 말임!

 

 

 

따라서, 10초 뒤 클로져를 실행하는 구문을 넣으면,

함수가 끝나고 클로저가 실행되기 때문에 에러가 남!!!

 

또한, 만약 중첩함수 내부에서 매개변수로 받은 클로저를 사용할 경우,

 

 

 

 

중첩함수를 리턴할 수 없음

 

이 모든 에러의 원인은 non-escaping closure의 주변 값 capture 방식에 있는데,

이는 다음 포스팅에서 ARC와 Closure를 다루며 자세히 설명 하겠고!

(중첩함수가 클로저를 return할 경우 capture 문제 등)

 

쨌든, 이렇게 함수 실행을 벗어나서 함수가 끝난 후에도 클로저를 실행하거나,

중첩함수에서 실행 후 중첩 함수를 리턴하고 싶거나, 변수 상수에 대입하고 싶은 경우!!

 

이때 사용하는 것이

 

@escaping 

 

키워드임!!!

 

 

func doSomething(closure: @escaping () -> ()) {
}
 

 

 

이렇게 클로저 파라미터 타입 앞@escaping을 붙여주면 되고,

이럴 경우! 

 

 

 

 

변수나 상수에 파라미터로 받은 클로저를 대입할 수 있고,

또 다음과 같이

 

 

 

 

함수가 종료된 후에도 클로저가 실행될 수 있음!!!!

 

 

자, 근데 이 escaping 클로저를 사용할 경우 주의해야할 점이 하나 있는데

메모리 관리와 관련된 부분임!!!!!!!!!!

 

예를 들어,

만약 함수가 종료된 후 클로저를 실행하는데, 이때 클로저가 함수 내부 값을 사용함

그럼 이때 함수는 이미 종료 되었는데, 클로저는 함수 내부 값을 어떻게 사용할까?

 

이런 메모리 관련 부분 ㅎㅎㅎ!!!!

이 부분에 대해서 마지막 포스팅인 다음 포스팅에서 자세히 다뤄보겠음 :)

 

 

 

 

 

 

.

.

.

힘 들 ㄷㅑ..........

차근차근 이해하면 클로저 개념이 크게 어렵지 않을 거예요ㅠㅠㅠㅠ

다음에 배울 ARC와 클로저만 공부하고 나면

 

클로저 정복하기 끗!!!!!!!!!!!! (사실 내가 정복하기 위해 쓰는 포스팅ㅎ)

읽어주셔서 감사합니다 :)

 

 



Calendar
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
최근 댓글
Visits
Today
Yesterday