Swift @escaping 은 왜 쓰는 걸까? Closure와 함께 이해하기
Swift @escaping 은 왜 쓰는 걸까? Closure와 함께 이해하기
Swift에서 클로저(Closure)는 코드의 독립적인 블록으로, 변수를 캡처하고 함수나 메서드의 인수로 전달될 수 있습니다. 클로저는 강력한 기능을 제공하지만, 그 사용 방법과 메모리 관리 측면에서 주의할 점이 있습니다. 특히, @escaping
키워드는 클로저가 함수의 범위를 벗어나서 실행될 수 있도록 하기 위해 사용됩니다. 이번 글에서는 @escaping
키워드의 필요성과 사용 방법을 클로저와 함께 이해해보겠습니다.
1. 클로저(Closure)란 무엇인가?
클로저는 코드 블록으로, 함수나 메서드의 매개변수로 전달될 수 있으며, 변수와 상수를 캡처할 수 있습니다. Swift의 클로저는 세 가지 형태로 존재합니다: 전역 함수, 중첩 함수, 그리고 클로저 표현식입니다. 클로저 표현식은 주로 간결한 문법으로 작성된 익명 함수입니다.
swiftlet closureExample = { (name: String) -> String in return "Hello, \(name)!" } let greeting = closureExample("Alice") print(greeting) // "Hello, Alice!"
위 코드에서 closureExample
은 문자열을 매개변수로 받아서 문자열을 반환하는 클로저입니다.
2. @escaping 키워드란 무엇인가?
@escaping
키워드는 클로저가 함수의 실행이 끝난 후에도 실행될 수 있도록 합니다. 기본적으로 클로저는 함수의 실행 범위 안에서 실행되지만, @escaping
키워드를 사용하면 클로저가 함수의 외부에서 실행될 수 있습니다.
2.1 @escaping의 필요성
클로저가 함수의 실행 범위를 벗어나서 실행되어야 할 때 @escaping
이 필요합니다. 대표적인 예로 비동기 작업에서 클로저를 사용하는 경우가 있습니다. 네트워크 요청이나 데이터베이스 쿼리와 같은 비동기 작업은 함수가 반환된 후에도 클로저를 실행할 수 있어야 합니다.
swiftfunc fetchData(completion: @escaping (String) -> Void) { DispatchQueue.global().async { let data = "Fetched data" completion(data) } } fetchData { data in print(data) // "Fetched data" }
위 예제에서 fetchData
함수는 @escaping
키워드를 사용하여 클로저가 비동기 작업이 완료된 후에도 실행될 수 있도록 합니다.
2.2 @escaping과 비동기 작업
비동기 작업에서 클로저는 함수가 반환된 후 실행되기 때문에 @escaping
이 필요합니다. 이는 클로저가 함수의 스택 프레임을 벗어나서 실행되도록 허용하는 것입니다.
swiftfunc performAsyncTask(completion: @escaping () -> Void) { DispatchQueue.global().asyncAfter(deadline: .now() + 2) { completion() } } performAsyncTask { print("Task completed!") }
위 코드에서 performAsyncTask
함수는 2초 후에 클로저를 실행합니다. 클로저는 함수가 반환된 후에도 실행될 수 있어야 하므로 @escaping
키워드를 사용합니다.
3. @escaping 사용 시 주의사항
@escaping
키워드를 사용할 때는 몇 가지 주의사항이 있습니다. 특히, 클로저 내부에서 self
를 참조할 때 강한 참조 사이클(strong reference cycle)을 방지해야 합니다.
3.1 강한 참조 사이클 방지
클로저가 객체를 강하게 참조하고, 객체가 클로저를 강하게 참조할 때 강한 참조 사이클이 발생할 수 있습니다. 이를 방지하기 위해 클로저 내부에서 self
를 약한 참조(weak reference)로 캡처해야 합니다.
swiftclass MyClass { var name = "Swift" func doSomething() { fetchData { [weak self] data in guard let self = self else { return } print("\(self.name): \(data)") } } }
위 코드에서 [weak self]
는 클로저가 self
를 약한 참조로 캡처하여 강한 참조 사이클을 방지합니다.
3.2 비소유 참조(unowned reference)
약한 참조 외에도 비소유 참조(unowned reference)를 사용할 수 있습니다. 비소유 참조는 참조 대상이 항상 존재한다고 보장할 때 사용됩니다. 비소유 참조는 약한 참조와 달리 참조 대상이 해제되면 nil
로 설정되지 않습니다.
swiftclass MyClass { var name = "Swift" func doSomething() { fetchData { [unowned self] data in print("\(self.name): \(data)") } } }
위 코드에서 [unowned self]
는 self
를 비소유 참조로 캡처합니다. 이는 self
가 클로저가 실행되는 동안 항상 존재한다고 가정할 때 사용됩니다.
4. @escaping의 실무 활용 사례
@escaping
키워드는 다양한 실무 상황에서 유용하게 사용됩니다. 다음은 몇 가지 예시입니다.
4.1 네트워크 요청
비동기 네트워크 요청을 처리할 때 @escaping
클로저를 사용하여 네트워크 응답을 처리할 수 있습니다.
swiftfunc fetchUserData(completion: @escaping (User?) -> Void) { let url = URL(string: "https://api.example.com/user")! let task = URLSession.shared.dataTask(with: url) { data, response, error in if let data = data { let user = try? JSONDecoder().decode(User.self, from: data) completion(user) } else { completion(nil) } } task.resume() } fetchUserData { user in if let user = user { print("User name: \(user.name)") } else { print("Failed to fetch user data") } }
4.2 애니메이션
UIKit에서 애니메이션을 처리할 때도 @escaping
클로저를 사용하여 애니메이션 완료 후의 동작을 정의할 수 있습니다.
swiftfunc animateView(view: UIView, completion: @escaping () -> Void) { UIView.animate(withDuration: 1.0, animations: { view.alpha = 0 }) { finished in completion() } } animateView(view: someView) { print("Animation completed!") }
5. 결론
Swift에서 @escaping
키워드는 클로저가 함수의 실행 범위를 벗어나 실행될 수 있도록 하는 중요한 기능입니다. 이를 통해 비동기 작업이나 지연된 작업을 처리할 수 있으며, 실무에서 다양한 상황에 유용하게 사용됩니다. 그러나 @escaping
클로저를 사용할 때는 강한 참조 사이클을 방지하기 위해 약한 참조나 비소유 참조를 적절히 사용해야 합니다. 이 글이 @escaping
의 필요성과 사용 방법을 이해하는 데 도움이 되기를 바랍니다.