Introduce

  • 클로저를 함수 또는 메소드의 전달 인자로 사용할 때, @escaping@autoclosure 속성을 적용할 수 있다.

Escaping

  • 함수 인자로 전달된 클로저는 기본적으로 비탈출 클로저이다.

  • 클로저의 탈출이란 함수 인자로 전달된 클로저가 함수 실행 종료 이후에 실행될 수 있는 것을 말한다.

  • 클로저가 탈출하면 함수 밖 어딘가에서 변수 등에 할당되면서 클로저의 참조 횟수를 증가시킬 가능성이 있다. 이 경우, 클로저가 인자로 전달된 함수의 외부에서도 참조 횟수를 관리해야 하는 어려움이 있다.

  • 코드 최적화 및 성능 향상을 위해 전달인자로 전달된 클로저는 다음 규칙을 지켜 사용해야 한다.

    1. 변수에 할당하지 않고 직접 호출해서만 사용한다.

      func nonEscapingClosure(closure: () -> ()) {
         // Error
         let c = closure()
      
         // Ok
         clousre()
      }
    2. 내부함수 또는 다른 클로저 안에서 사용할 수 없다.

      func nonEscapingClosure(closure: () -> ()) -> () -> (){
         // Error
         func innerFunction() {
         closure()
       }
      
         return innerFunction
      }
  • 상황에 따라 전달인자로 전달된 클로저를 함수 밖으로 탈출시켜야 하는 경우, @escaping 키워드를 이용해 탈출 가능하도록 만든다.

Autoclosing

  • 함수 전달인자로 일반 구문을 전달하면 자동으로 클로저로 바꿔 전달할 수 있게 한다.

Escaping Closure

  • @escaping 키워드를 사용하면 함수 전달인자로 전달되는 클로저가 함수를 탈출할 수 있다.
var closures = [()->()]()
func addClousre(closure: @escaping ()->()) -> ()->() {
  // 전역 변수 closures 배열에 추가되어 함수 밖에서 사용될 가능성이 있음
  closures.append(closure)
}
  • addClosure(closure:) 함수를 실행하면 함수 인자로 전달된 클로저closure가 함수 외부에서 선언된 배열 closures에 추가된다. 함수 실행이 종료된 이후에도 closures 배열을 이용해 closure를 실행시킬 수 있으므로 @escaping 키워드를 이용해 탈출 가능 클로저로 설정한다.

Escaping Closure in Class

  • 탈출 클로저가 클래스 안에서 사용될 때, 클래스 멤버(프로퍼티, 메서드, 서브스크립트 등)를 클로저 안에서 사용하려면 함수 외부에서 인스턴스에 접근할 수 있도록 반드시 self를 통해 접근해야 한다.
  • 비탈출 클로저는 클래스 밖으로 탈출할 수 없으므로, self 키워드를 붙이지 않아도 클로저가 실행될 때 언제든지 클래스 인스턴스에 접근이 가능하다.
class Sample {
  var x = 10
  var y = 20
  func executeEscapingClosure() {
    someEscapingClosure { 
      self.x = 100 
      y = 200
    }
  }

  func executeNonescapingClosure() {
    someNonEscapingClosure { 
      self.x = 100 
      y = 200    // Error
    }
  }  
}

withoutActuallyEscaping

  • 함수 전달인자로 전달된 클로저가 실제로는 함수를 탈출할 일은 없지만 다른 함수의 탈출 클로저로 전달되어야 하는 경우가 있다.
  • withoutActuallyEscaping(_:do:) 함수를 이용하면 비탈출 클로저를 탈출 클로저처럼 사용할 수 있다. 탈출 클로저 역할을 하지만 실제로는 탈출하지 않는다.
    func hasElement(in array: [Int], match predicate: (Int)->Bool) -> Bool {
    return withoutActuallyEscaping(predicate, do: { escapablePredicate in
      return (array.lazy.filter { escapablePredicate($0) }.isEmpty == false)
    })
    }

Auto Closure

  • @autoclosure 키워드를 사용하면 함수 인자에 일반 구문 또는 함수 형태의 코드를 전달하먼 자동으로 클로저 형태로 바꿔서 사용한다.
  • 전달 인자가 없는 클로저의 형태만 사용 가능하다.
  • 클로저의 중괄호와 함수 소괄호가 겹쳐서 이해하기 어려울 때, 중괄호 없이 사용할 수 있어 읽기 쉬워진다.
  • 실행되는 코드를 직접 전달하므로 직관적으로 이해할 수 있다.
  • 무분별하게 사용하면 오히려 가독성을 해칠 수 있다.
func check(boolean: @autoclosure ()->Bool) {
  if boolean() == true {
    print("TRUE")
  } else {
    print("FALSE")
  }
}
check(boolean: 4 > 2)    // print: TRUE
check(boolean: (2 > 4))    // print: FALSE

Lazy Execution

  • Autoclosure를 일반 구문의 관점에서 보면, 실제 호출하기 전 까지 실행되지 않는 클로저의 특성을 이용한 일반 코드 또는 함수를 지연 실행 시키는 기능으로 볼 수도 있다.
  • removeElement(_:) 함수에 number.removeFirst() 함수를 전달해도 실제로 실행되지 않는다.
  • removeElement(_:)가 반환한 클로저를 할당받은 remover를 실행한 뒤에서야 실제로 number.removeFirst()가 실행된다.
var number: [Int] = [1, 2, 3, 4]
func removeElement(_ removeClosure: @autoclosure @escaping ()->Int) -> () -> Int {
  return removeClosure
}

let remover = removeElement( number.removeFirst() )
print(number.count)
remover()
print(number.count)
/* print */
// 4
// 3