Swift: Capture List

틀틀보·2025년 5월 15일

Swift

목록 보기
7/19

Capture

클로저는 자신이 정의된 주변 환경의 변수나 상수를 캡처하여, 실행 시점에 값들이 존재하지 않더라도 클로저 내부에서 해당 값들에 접근 가능하게 하는 것

func makeIncrementer(by amount: Int) -> () -> Int {
    var total = 0
    let incrementer: () -> Int = {
        total += amount
        return total
    }
    return incrementer
}

let incrementByFive = makeIncrementer(by: 5)
print(incrementByFive()) // 출력: 5
print(incrementByFive()) // 출력: 10
print(incrementByFive()) // 출력: 15

위의 코드처럼 total, amount가 존재하지 않게 되더라도 여전히 동작.

값 타입과 참조 타입의 Capture

일단 기본적으로 두 타입은 Reference Capture를 한다.

값 타입

var x = 10
let closure = { print(x) }
x = 20
closure() // 출력: 20

closure 클로저가 x 변수를 참조 캡처하여 x의 값이 바뀌면 그대로 클로저에서도 동일하게 값이 바뀌어 적용된다.

참조 타입

class Counter {
    var count = 0
}

let counter = Counter()

let closure = {
    print("Count is \(counter.count)")
}

counter.count = 5
closure() // 출력: Count is 5

동일하게 closureCounter 객체를 참조 캡처하여 객체의 값을 바꾸면 클로저에도 동일하게 적용된다.

힙에 저장되는 캡처 값들

보통 클로저들은 escaping closure와 같이 선언된 곳에서 바로 사용되지 않고 선언된 곳 외부에서 사용되거나 비동기적으로 사용된다.
그 때문에 캡처된 값들은 언제 다시 사용될 지 모르기 때문에 탈출 클로저와 함께 heap에 저장하여 클로저가 실행 될 때 heap에서 값들을 찾아 사용된다.

Capture List

closure 내부에 캡처해오는 값들의 참조 강도를 명시해서 캡처하는 방식

기존의 캡처방식은 모두 Reference Capture

값 타입의 Capture List

var value1 = 0
var value2 = 0

let closure = { [value1, value2] in
    print("value1: \(value1), value2: \(value2)")
}

value1 = 10
value2 = 20

closure() // 출력: value1: 0, value2: 0
  • closure생성될 때 캡처
  • 값을 복사하여 캡처
  • 캡처한 값이 바뀌었음에도 캡처 당시의 값이 출력된다.

참조 타입의 Caputure List

weak와 unowned로 나뉘어 순환 참조 문제 해결 가능

  • 기존의 Capture와 동일하게 Reference Capture
  • closure실행될 때 캡처
  • 즉, 생성될 때와 실행 될 때의 값이 다를 수 있음.

weak

class ViewController: UIViewController {
    var dataLoader: DataLoader?

    func fetchData() {
        dataLoader?.loadData(completion: { [weak self] result in
            guard let self = self else { return }

            switch result {
            case .success(let data):
                self.updateUI(with: data)
            case .failure(let error):
                self.showErrorMessage(error)
            }
        })
    }

    private func updateUI(with data: Data) {
        // UI 업데이트 코드
    }

    private func showErrorMessage(_ error: Error) {
        // 에러 메시지 표시 코드
    }
}

기존 self를 캡처하여 참조 횟수가 증가하던 걸 weak로 명시함으로써 ViewController 객체가 사라졌을 때 ARC가 메모리 해제할 수 있게 가능

unowned

class DataLoader {
    var data = "Initial Data"

    func loadData() {
        let closure = { [unowned self] in
            print("Loading: \(self.data)")
        }
        closure()
    }
}

let loader = DataLoader()
loader.loadData()

unowned 기능과 동일하게 클로저가 실행될 때DataLoader 객체가 존재할 것이라는 가정 하에 안전하게 사용 가능

self 뿐만 아니라 다른 것도 캡처 선언 가능

class ViewController {
    var button: UIButton?

    func setup() {
        let handler = { [weak self, weak button = self.button] in
            guard let self = self, let button = button else { return }
            self.doSomething()
            button.setTitle("Tapped", for: .normal)
        }
        // handler를 어디선가 사용
    }

    func doSomething() {
        // 어떤 작업 수행
    }
}

ViewController 객체 뿐만 아니라 내부의 UIButton객체의 메모리 해제를 위해 weak 명시를 통해 약한 참조 가능

참고

https://stackoverflow.com/questions/40978533/why-swift-closure-not-capture-self

https://skytitan.tistory.com/496

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/

https://www.dhiwise.com/post/swift-capture-lists-solving-real-developer-problems-in-2024

profile
안녕하세요! iOS 개발자입니다!

0개의 댓글