[iOS/Swift] Closure
이전 포스팅에서는 life cycle에 대해서 알아보았는데 오늘은 스위프트의 클로저에 대해서 알아보겠다!
스터디에서 가져온 질문은 아래와 같으며 답변하고 정리하는 식으로 블로그 포스팅을 진행해보겠다.
1️⃣ Swift에서 클로저란 무엇이며 어떻게 사용하나요?
클로저에는 함수처럼 이름 있는 클로저도 있고, 이름 없는 클로저도 존재한다. 보통 클로저는 이름 없는 클로저(unnamed closure)를 의미하는데, 여기서 클로저란 사용자의 코드 내에서 전달되어 사용할 수 있는 로직을 가진 중괄호 “{}” 로 구분된 코드의 블럭이며 코드 블록을 캡쳐하고 사용할 수 있는 일급 객체이다.
클로저는 '( )' 괄호를 사용하여 직접 호출하거나, 함수에 인자로 전달하여 나중에 해당함수 내에서 호출할 수 있다.
기본 클로저 표현식은 아래와 같다.
let greetingClosure = { () -> Void in
print("Hello, world!")
}
greetingClosure() // "Hello, world!" 출력
파라미터와 리턴타입이 없는 클로저도 있고, 반대로 있는 클로저도 있다.
클로저에서는 리턴타입이 존재하는데 생략 가능하며, 파라미터도 생략가능하다!
2️⃣ 클로저의 캡쳐 리스트(Capture List)의 역할
클로저는 생성될 때 클로저를 둘러싸는 변수를 캡쳐하는데, 이 변수를 클로저 내에서 사용할 수 있다. 하지만 클로저가 외부에서 실행되더라고 캡처된 변수는 클로저 내에서 유지하게 되는데 캡처리스트는 이 과정을 제어시켜준다!
캡처리스트란 클로저 안에서 외부 변수를 캡처할 때 strong, weak, unowneded 등의 참조 강도를 명시해서 캡처해오는 방법이다.
let closure: () -> Void = { [unowned self, weak newResource] in
// self와 newResource에 대한 약한 참조를 캡쳐
if let res = newResource {
print("Managing \(res.name)")
}
}
위의 예시에서 볼 수 있듯이 [unowned self, weak newResource] 캡쳐리스트를 가지고 있고 self와 newResource에 대한 weak reference를 가지게 된다.
캡쳐리스트를 통해 self와 newResource에 대한 참조를 캡쳐함으로써 다른 클래스간에 발생할 수 있는 순환참조를 방지하고 메모리 누수를 방지할 수 있다.
3️⃣ @escaping 클로저와 non-escaping 클로저의 차이점
@escaping 클로저는 클로저가 함수의 인자로 전달되지만, 함수가 반환된 후 실행되는 것을 escape라고 하며 이러한 경우 매개변수의 타입앞에 @escaping 키워드를 명시해줘야 한다. 비동기 네트워크 요청이나 타이머 콜백, 완료에 다른 처리(completionHandler)로 사용되는 클로저의 경우에 자주 사용되며 @escaping을 사용하는 클로저에는 self를 명시적으로 언급해줘야 한다.
반면 non-escaping클로저는 클로저가 함수 내부에서 실행되어야 하며, 함수 실행이 끝나기 전에 클로저가 호출되어야 한다.
그래서 함수가 반한된 후 실행하는 escaping클로저와 달리 함수가 종료된 후에는 클로저를 사용할 수 없다
4️⃣ 트레일링 클로저(Trailing Closure) 문법은 언제 사용하면 좋나요?
트레일링 클로저는 함수 호출 시, 인자로 전달되는 클로저를 마지막으로 인자로서 함수 호출 괄호 뒤에 작성하는 문법이다. 즉 클로저가 함수의 마지막 인자로 올 때 가독성을 높이는 데 유용하다.
클로저의 코드 블록이 길거나, 중첩된 경우에 사용하게 된다면 가독성을 향상 시킬수 있고 많은 api가 클로저를 사용하여 콜백을 처리하거나 연속적인 작업을 수행할 때 사용하면 효율적이고 명확하게 사용할 수 있다.
createClosure(closure: () -> ()) {
print("hi")
}
클로저 하나만 파라미터로 받는 함수가 있다고 가정했을 때 호출하려면 위와 같이 동작했다. 이걸 traling clousre를 사용하여 호출하면 아래와 같이 표현된다.
createClosure() { () -> () in
print("hi")
}
이렇게 예제로 표현하니까 훨씬 이해가 잘 되는 느낌이다!
하지만 트레일링 클로저도 () -> () 이게 거추장스러워 보일 수도 있는데, 그건 또 클로저의 경량 문법에 의하여 생략가능하다.
즉 정리해보면
1. 파라미터 클로저가 하나일 경우 이 클로저는 첫 파라미터이자 마지막 파라미터이므로 트레일링 클로저가 가능하다.
2. 클로저에 argumentLabel은 트레일링 클로저에서는 생략된다.