티스토리 뷰

728x90

지난 클로저에는 값 캡처, 클로저 표현식 에 대해 알아봤는데요!

오늘은 클로저의 심화 과정인 
Escaping 클로저

Auto 클로저

에 대해 정리해 보려고합니다! 

 


 

탈출 클로저 (Escaping Closures)

 

간단하게 표현하면, 함수가 끝나고 실행되는 클로저 이며, 비동기 작업을 하기 위해 사용합니다.

클로저를 함수의 파라미터로 넣을 수 있는데, 이때 파라미터 타입 앞에 @escaping 을 적어줌으로써 클로저가 escaping 할 수 있게 해줍니다. 

 

var completionHandlers: [() -> Void] = []
func withEscaping(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

위 함수에서 인자로 전달된 completionHandler 는 someFunctionWithEscapingClosure함수가 끝나고 나중에 처리 됩니다.

이때 @escaping 키워드를 붙여주지 않으면 컴파일 에러가 납니다. 

 

@escaping 클로저를 사용할 때는 self를 명시적으로 사용해야 합니다.

var completionHandlers: [() -> Void] = []
func withEscaping(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

func noneEscaping(closure: () -> Void) {
    closure()
}

class SomeClass {
    var x = 10
    func doSomething() {
        withEscaping { self.x = 100 }
        noneEscaping { x = 200 }
    }
}

// 1
let instance = SomeClass()    // 로직풀이 참고
instance.doSomething()
print(instance.x)


// 2
completionHandlers.first?()
print(instance.x)

 

로직풀이 

 

더보기

가장 상단에 함수 외부에 completionHandlers 변수를 선언하여 빈 배열을 가지는 클로저를 만듭니다.

 

1번 로직

  1. let instance = SomeClass() 인스턴스화 
  2. doSomething 호출 하면서 withEscaping, noneEscaping 클로저 실행
  3. withEscaping 클로저를 completionHandlers 배열에 저장 (저장만 하며 호출되지 않습니다.)
  4. noneEscaping 클로저가 호출되면서 x 값 200으로 변경

2번 로직

  1. completionHandlers.first?() 를 호출
  2. 저장 되어있던 withEscaping 클로저가 호출되며 x값을 100으로 변경

 


 

자동 클로저 (Autoclosures)

 

함수의 인자로 전달되는 코드를 감싸서 자동으로 클로저를 만들어 줍니다.

일단 자동클로저를 사용하면 어떻게 달라지는지 부터 보겠습니다.

둘의 차이점은 중괄호 { } 가 있고 없고의 차이인데  위에가 자동클로저 사용전, 아래가 사용 후 입니다. 좀더 간결해 보이죠?

 

다시 돌아와서, 자동클로저는 클로저를 실행하기 전까지 실행 되지 않습니다.

아래 예제를 같이 보면서 이해해 보겠습니다.

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// Prints "5"


// 1
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// Prints "5"


// 2
print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// Prints "4"

 

1. let customerProvider = { customersInLine.remove(at: 0) } 를 해도

    customersInLine 배열의 갯수는 그대로 5입니다.

 

2. print("Now serving \(customerProvider())!") 여기서 customerProvider()를 호출하니

    그제서야 배열의 갯수가 4로 줄어들죠. 

 

 

이번에는 클로저를 함수의 인자 값으로 넣는 예제를 보겠습니다.

// customersInLine = ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) })
// Now serving Alex!

print(customersInLine.count)
// customersInLine.count: 3

serve() 함수는 인자로 () ->  String을 반환하는 클로저를 받는 함수 입니다.

 

함수를 호출할 때는 아래 두가지로 표현할 수 있습니다.

serve(customer: { customersInLine.remove(at: 0)})
serve { customersInLine.remove(at: 0) }

 

아까 자동클로저 시작 부분에서 봤던 모양이죠? 

이제 @autoclosure 키워드를 사용해 위의 코드를 좀더 간결하게 표현해 보겠습니다.

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) { // @autoclosure 사용
    print("Now serving \(customerProvider())!")
}

serve(customer: customersInLine.remove(at: 0))
// Now serving Ewa!

 

@autoclosure 키워드를 사용함으로써, 인자 값은 자동으로 클로저로 변환이 되어 중괄호 { } 를 생략하고 사용할 수 있습니다.

 

NOTE

자동클로저를 너무 많이 사용하면 코드를 이해하기 어려울수 있으니, 
문맥과 함수이름이을 autoclosure와 연관있음을 명시해야 합니다.

 

자동클로저(@autoclosure)는 탈출클로저(@escaping)와 같이 사용할 수 있습니다. 

// customersInLine = ["Barry", "Daniella"]

// 클로저를 저장하는 배열 선언
var customerProviders: [() -> String] = []

// (클로저)customerProvider를 인자로 받아
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
// customerProviders 배열에 추가
    customerProviders.append(customerProvider)
}

// 클로저를 customerProviders 배열에 추가 
collectCustomerProviders(customersInLine.remove(at: 0))  // autoclosure를 사용함으로써 중괄호{ } 생략
// 클로저를 customerProviders 배열에 추가
collectCustomerProviders(customersInLine.remove(at: 0))

print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."

for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"

 

autoclosure를 사용하지 않았다면 이렇게 사용할 수 있습니다.

func collectCustomerProviders(_ customerProvider: @escaping () -> String) {
    customerProviders.append(customerProvider)
}
collectCustomerProviders { customersInLine.remove(at: 0) }
// 또는
collectCustomerProviders( {customersInLine.remove(at: 0) })

 

728x90

'iOS' 카테고리의 다른 글

[Swift] 열거형 (Enumerations)  (0) 2022.02.09
[iOS] CocoaPods 설치 및 실행  (0) 2022.02.06
[Swift] 클로저 (Closure) (1)  (0) 2022.01.30
ModerRIBs_tutorial 2 - 2  (0) 2022.01.26
ModernRIBs_tutorial2 - 1  (0) 2022.01.23