Closuer

Closure의 캡쳐 방식

  • closure 내부에서 사용하는 값이 어떠한 변수에 저장되는 것을 캡쳐라고 한다!!
    Closure는 값을 캡쳐할 때 Value/Reference 타입에 관계없이 Reference Capture 한다.
    Reference Type인 경우에는 값의 주소값을 저장하는 것이 당연한 방법이지만 Swift에서는 Value Type인 값도 캡쳐할 때는 Reference 방식으로 캡쳐를 한다.
    즉, 클로저의 변수가 사용되는 시점의 변수의 값을 평가한다.

Capture List

let closure = { [capture1, capture2] in
 
 }

이런식으로 캡쳐할 Value 타입의 변수를 []안에 나열한다. -> Closure를 선언할 당시의 값을 Const Value Type으로 캡쳐한다.
하지만 Const Value Type(상수)이기 떄문에 클로저 내부에서 이 변수의 값을 변경할 수 없다.

클로저와 ARC

  • ARC = 인스턴스의 Reference Count를 자동으로 계산하여 메모리를 관리하는 방법

클로저의 강한 순환 참조

클로저는 참조 타입으로 Heap 영역에 할당! -> Reference 값을 캡쳐할 때는 기본적으로 “strong” 캡쳐! -> 이때 인스턴스의 Reference Count가 증가! -> 인스턴스는 클로저를 참조하고 클로저는 인스턴스의 변수를 참조하는 강한 순환 참조 발생!

클로저의 강한 순환 참조 해결법

  • weak & unowned + Capture Lists 클로저가 프로퍼티에 접근할 때 참조하는 부분을 Closure Capture List를 이용해 weak, unowned 캡쳐 하는 방법이 있다.
class A {
    lazy var b: () -> String? = { [weak self] in
        return self?.a
    }
}

or

class A {
    lazy var b: () -> String = { [unowned self] in
        return self?.a
    }
}

여기서 weak의 경우 nil을 할당받을 가능성이 있기에 Optional-Type으로 self에 대한 Optional Binding을 해주어야 하지만, unowned의 경우엔 Non-Optional Type으로 self에 대한 Optional Binding 없이 사용이 가능하다.(Capture List로 동작할때)

중첨 함수와 @escaping

@escaping이란 키워드 없이 받는 클로저는 모두 non-escaping 클로저이고 다음과 같은 특징을 갖는다.

  • 함수 내부에서 직접 실행하기 위해서만 사용한다.
  • 파라미터로 받은 클로저는 변수나 상수에 대입할 수 없다.
  • 중첩 함수 내부에서 클로저를 사용할 경우, 중첩함수를 리턴할 수 없다.
  • 함수의 실행 흐름을 탈출하지 않아, 함수가 종료되기 전에 무조건 실행 되어야 한다. = 함수가 외부로 탈출하지 못하게 하기위한 방법들

non-escaping

기본적으로 클로저는 non-escaping 클로저이다.
그래야만 클로저가 이 함수 내부에서만 사용되고 컴파일러가 메모리 관리를 지저분하게 하지 않아도 되어서 성능이 향상되기 때문이다.
escaping 클로저의 경우, 함수가 종료되더라도 실제 클로저가 사용되지 않을 때까지 메모리를 추적해야하므로 관리가 필요하다.

Trailing Closure

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

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

  • Inline Closure
A(closure: { () -> () in
    // TODO
})

-> 함수의 가장 마지막에 클로저를 꼬리처럼 덧붙여서 사용이 가능하다.

  • Trailig Closure
A() { () -> () in
    // TODO
 }
  1. 파라미터가 클로저 하나일 경우에 이 클로저는 첫 파라미터이자 마지막 파라미터이므로 트레일링 클로저가 가능
  2. “closure” 라는 ArgumentLabel은 트레일링 클로저에선 생략

-> 여기서 파라미터가 오직 클로저 하나일 경우에는 호출구문인 ()도 생략 가능

A { () -> () in
    // TODO
 }

파라미터가 여러 개인 함수

B(C: { () -> () in
    // TODO
}, D: {() -> () in
    // TODO
})

-> 트레일링 클로저이므로?

B(C: { () -> () in
    // TODO
}) {() -> () in
    // TODO
}

클로저의 경량 문법

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

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

E(closure: { (a: Int, b: Int, c: Int) -> Int in
    // TODO
 })

-> -> -> ->

E(closure: { (a, b, c) ->  in
    // TODO
 })
 

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

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

-> Parameter Name 대신에 $ index 사용

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

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

  • 단일 리턴문 - 클로저 내부에 코드가 return 구문 하나만 남은 경우
E(closure: {
    $0 + $1 + $2
})

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

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

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

E {  
    $0 + $1 + $2
}

@autoclosure (모르겠음…)

  • autoclosure - 파라미터로 전달된 일반 구문 & 함수를 클로저로 래핑하는 것
func F(closure: @autoclosure () -> ()) {
    // TODO
}

closure란 파라미터는 실제 클로저를 전달받지 않지만, 클로저처럼 사용이 가능하다.
클로저와 다른 점은 실제 클로저를 전달하는 것이 아니기 떄문에 파라미터로 값을 넘기는 것 처럼 ()를 통해 구문을 넘겨줄 수가 있다.

F(closure: 1 > 2)

일반 구문을 클로저처럼 사용 가능! (클로저로 래핑한 것이기 때문에)

func F(closure: @autoclosure () -> ()) {
    closure()
}
  • 주의점 - 파라미터가 반드시 없어야한다!

autoclosure 특징 : 지연된 실행

autoclosure로 작성하면, 함수가 실행될 시점에 구문을 클로자로 만들어주기 때문에 함수 내에서 클로저를 실행할 때까지 구문이 실행되지 않는다.

댓글남기기