Old_SWIFT(221012)/기본이야기

Closure에 대하여 - 1 (Trailing Closure, @autoclosure , @escaping)

KataRN 2022. 10. 8. 19:23
반응형

안녕하세요.

 

KataRN입니다.

 

오늘은 Closure에 대해 알아보겠습니다.

 

📍 Closure란?

func으로 선언하는 것이 아닌 함수를 변수에 선언하는 형태

공식 문서에는 클로저는 어떤 상수나 변수의 참조를 캡쳐(capture)해 저장할 수 있다.라고 정의되어있다.

 

클로저는 다음 세 가지 형태 중 하나를 갖습니다.
  • 전역 함수 : 이름이 있고 어떤 값도 캡쳐하지 않는 클로저
  • 중첩 함수 : 이름이 있고 관련한 함수로 부터 값을 캡쳐 할 수 있는 클로저
  • 클로저 표현 : 경량화 된 문법으로 쓰여지고 관련된 문맥(context)으로부터 값을 캡쳐할 수 있는 이름이 없는 클로저

 

Closure는 익명함수이면서 1급객체 함수의 특성을 갖고있습니다.

📢 1급객체함수란?

더보기

1. 객체가 런타임에도 생성 가능하다.

2. 객체를 인자 값으로 전달할 수 있어야 한다.

3. 객체를 반환 값으로 사용할 수 있어야 한다.

4. 데이터 구조 안에 저장할 수 있어야 한다.

 

📍 Closure의 표현식은?

{ (parameters) -> return Type in
    statements
}

뭔가 익숙하죠?

in 이라는 키워드를 기준으로 앞은 Closure Head 뒤는 Closure Body라고 부릅니다.

 

📍 Closure(파리미터와 리턴타입이 없는 경우)

let closure = { () -> () in
    print("클로저")
}

 

📍 Closure(파라미터와 리턴타입이 있는 경우)

 let closure = { (name: String) -> String in
    return "Hello, \(name)"
}

 

주의할점은 Closure에서는 ArgumentLabel을 사용하지 않습니다.

print(closure("Kata")) // -> Hello, Kata
print(closure(name: "Kata")) // -> Error 발생

 

📍 함수의 파라미터 타입으로 클로저를 전달 가능

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

kataGo(closure: { () -> () in
	print("Closure")
})

아래를 보시면 Closure를  파라미터로 전달하였습니다.

 

📍클로저를 리턴할 수 있다!

func kataGo() -> () -> () {
    
    return { () -> () in
        print("Hello Kata!")
    }
}

여기서 처음 화살표 뒤가 리턴값이기 때문에

return () -> () 이 되는것이다.

 

📍 클로저 실행하기!

//클로저_1
let closure = { () -> String in
    return "Hello Kata!"
}
closure()


//클로저_2
({ () -> String in
    return "Hello Kata!"
})()

클로저_1은 상수 + () 로 실행한것입니다.

그리고 클로저_2는 클로저(상수X)를 ()안에 넣고 호출구문인 ()로 실행한 것입니다.

 

📍 Trailing Closure(후행 클로저)

- 함수의 마지막 파라미터가 클로저일 때, 이를 파라미터 값 형식이 아닌 함수 뒤에 붙여 작성하는 문법으로 Argument Label은 생략된다

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

kataGo(closure: { () -> () in
	print("Closure")
})

 

위에서 설명드릴때 이런 예제를 보여드렸었죠? 이렇게 클로저가 파라미터의 값 형식으로 함수 호출 구문 () 안에 작성되어 있는 것을  Inline Closure 라고 부릅니다.

 

괄호 안에 대괄호가 있다는것이 너무 헷갈리죠?

이럴때 함수의 가장 마지막에 클로저를 꼬리처럼 붙이는것이 Trailing Closure입니다.

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

 

호출구문도 생략가능합니다.ㅎㅎ

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

 

 

그럼 파라미터가 2개라면? 마지막것만 함수뒤에 가능~

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


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

 

📍 클로저의 경량 문법

제가 되게 개발자스럽게 보이는거, 멋있어보이는거를 추구하는데...

이게 바로 그런거라고 할 수 있습니다.

 

아래와 같은 함수가 있습니다.

func doSomething(closure: (Int, Int, Int) -> Int) {
    closure(1, 2, 3)
}

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

 

우선 파라미터 형식리턴 형식 생략가능!

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

 

그리고 Parameter Name은 Shortand Argument Names으로 대체

고차함수에서 썼었던 $0, $1, $2.... 아시죠? 순서대로

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

 

단일 리턴문만 남을 경우 return도 생략 가능

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

 

어라? 마지막 파라미터네 Trailing Closure로 변경

doSomething {  
     $0 + $1 + $2
}

상당히 간결해졌습니다. ^^

 

📍자동클로저(@autoclosure)

@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"

customerProvider를 상수로 설정하였지만 호출을 하지 않으면 작동하지 않습니다.

 

이처럼 자동 클로저는 실제 사용될때 지연호출 됩니다.

 

// 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!"

 

자동클로저를 함수의 인자 값으로 넣는 예제는 아래와 같습니다.

// 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!"

 

이 예제에 @aucoclosure 키워드를 이용해서 더 간결하게 사용 할 수 있다.

// 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!"

 

짠! 대괄호 하나가 사라졌습니다.

 

📍 탈출클로저(@escaping)

Swift 에서는 함수의 파라미터로 전달된 클로저는 기본적으로 "함수 내부 스코프 안에서만" 사용이 가능합니다.

탈출 클로저는 해당 클로저를 외부 변수/상수에 저장 가능하고  해당 함수가 끝나서 리턴된 이후에도 클로저 실행이 가능합니다.(함수간 실행 순서를 고려할 수 있습니다.)

원래 함수가 종료되면은 내부의 클로저도 같이 메모리상에서 없어져야 하는 것이 맞지만 탈출 가능 속성으로 정해두면 해당 클로저만을 위해 메모리 공간을 만들게 됩니다.

 

1. 함수 밖에서 정의한 변수에 저장하기.

var completionHandlers = [() -> Void]()

func someFunctionWithEscapingClosure(completionHandler: @escaping() -> Void) {
	completionHandlers.append(completionHandler)
}

클로저는 원래 함수 내부에서만 사용이 가능했으나 함수밖에서 변수에 저장을 하였습니다.

@escaping를 붙여줬구요.(없으면 에러납니다.)

 

2. 비동기실행하기

이렇게 하면 함수가 종료되었지만 클로저가 실행 된 것을 확인 할 수 있습니다.

 

클로저 내용이 많네요..

나머지 내용은 다음 글에서 소개하겠습니다.

https://katarnios.tistory.com/85

 

Closure에 대하여 - 2 (값 캡쳐, 캡쳐 리스트, ARC, 강한순환참조, 약한순환참조)

안녕하세요. KataRN입니다. 저번 글에 이어서 작성해보도록 하겠습니다. 참조 글 - https://babbab2.tistory.com/83?category=828998 너무 좋은 글이라 거의 따라썼습니다...ㅠㅠ 📍 값 캡쳐 func goKata() { var..

katarnios.tistory.com

 

 

오늘도 긴글 읽어주셔서 감사합니다.

 

반응형