Swift @escaping 은 왜 쓰는 걸까? Closure와 함께 이해하기

작성일 :

Swift @escaping 은 왜 쓰는 걸까? Closure와 함께 이해하기

Swift에서 클로저(Closure)는 코드의 독립적인 블록으로, 변수를 캡처하고 함수나 메서드의 인수로 전달될 수 있습니다. 클로저는 강력한 기능을 제공하지만, 그 사용 방법과 메모리 관리 측면에서 주의할 점이 있습니다. 특히, @escaping 키워드는 클로저가 함수의 범위를 벗어나서 실행될 수 있도록 하기 위해 사용됩니다. 이번 글에서는 @escaping 키워드의 필요성과 사용 방법을 클로저와 함께 이해해보겠습니다.

1. 클로저(Closure)란 무엇인가?

클로저는 코드 블록으로, 함수나 메서드의 매개변수로 전달될 수 있으며, 변수와 상수를 캡처할 수 있습니다. Swift의 클로저는 세 가지 형태로 존재합니다: 전역 함수, 중첩 함수, 그리고 클로저 표현식입니다. 클로저 표현식은 주로 간결한 문법으로 작성된 익명 함수입니다.

swift
let 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이 필요합니다. 대표적인 예로 비동기 작업에서 클로저를 사용하는 경우가 있습니다. 네트워크 요청이나 데이터베이스 쿼리와 같은 비동기 작업은 함수가 반환된 후에도 클로저를 실행할 수 있어야 합니다.

swift
func 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이 필요합니다. 이는 클로저가 함수의 스택 프레임을 벗어나서 실행되도록 허용하는 것입니다.

swift
func 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)로 캡처해야 합니다.

swift
class 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로 설정되지 않습니다.

swift
class 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 클로저를 사용하여 네트워크 응답을 처리할 수 있습니다.

swift
func 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 클로저를 사용하여 애니메이션 완료 후의 동작을 정의할 수 있습니다.

swift
func 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의 필요성과 사용 방법을 이해하는 데 도움이 되기를 바랍니다.