[swift] 클로저 성능 최적화하기

Swift에서 클로저(closure)는 많은 유연성과 편의성을 제공하지만, 실행 시간에도 비용이 들 수 있습니다. 때로는 클로저를 사용하는 코드가 성능 저하를 일으킬 수 있기 때문에, 클로저를 최적화하는 방법에 대해 알아보고자 합니다.

1. 클로저 캡처 방식 이해하기

클로저는 주변의 변수와 상수를 캡처하여 사용할 수 있습니다. 이때 캡처 방식에 따라서 클로저의 성능이 크게 달라질 수 있습니다. 두 가지 주요 캡처 방식은 값 캡처(value capture)와 참조 캡처(reference capture)입니다.

값 캡처

값 캡처는 클로저가 캡처한 변수와 상수를 복사하여 사용하는 방식입니다. 이는 클로저와 원본 변수 간에 독립적인 상태를 유지할 수 있어서 안정적인 동작을 보장하지만, 캡처한 값이 바뀌지 않는 한 새로운 값을 할당해야 하므로 성능에 부담을 줄 수 있습니다.

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

참조 캡처

참조 캡처는 클로저가 캡처한 변수와 상수에 대한 참조를 유지하는 방식입니다. 이는 클로저와 원본 변수 간에 상호작용이 가능하고 메모리 사용을 절약할 수 있지만, 참조된 객체가 이미 해제된 경우 발생할 수 있는 잠재적인 문제가 있습니다.

class ReferenceType {
    var value = 0
}

func makeIncrementer() -> () -> Int {
    let reference = ReferenceType()
    let incrementer: () -> Int = {
        reference.value += 1
        return reference.value
    }
    return incrementer
}

2. 클로저 탈출 방지하기

클로저는 기본적으로 탈출 불가능한(non-escaping) 형태로 정의됩니다. 이는 클로저를 함수 밖으로 전달할 수 없음을 의미합니다. 하지만 필요에 따라 클로저를 탈출 가능한(escaping) 형태로 정의할 수도 있습니다. 이때는 클로저 내부에서 self 참조를 명시적으로 명시해야 합니다. 하지만 탈출 클로저는 메모리 관리에 추가적인 부담을 줄 수 있으므로, 사용 시 주의가 필요합니다.

func performOperation(completion: @escaping () -> Void) {
    DispatchQueue.main.async {
        completion()
    }
}

3. 클로저 사용 시 메모리 누수 방지하기

클로저는 참조 캡처를 사용할 때 메모리 누수의 원인이 될 수 있습니다. 이를 방지하기 위해 클로저와 참조된 객체 간의 강한 순환 참조를 방지해야 합니다. 이때에는 [weak self]나 [unowned self]와 같은 클로저 캡처 리스트를 사용할 수 있습니다. [weak self]는 약한 참조로 클로저와 참조된 객체 간의 순환 참조를 방지할 수 있고, [unowned self]는 강한 참조로 클로저와 참조된 객체 간의 순환 참조를 방지할 수 있습니다.

class SomeClass {
    var closure: (() -> Void)?
    
    func configureClosure() {
        closure = { [weak self] in
            self?.performAction()
        }
    }
    
    func performAction() {
        print("Action performed")
    }
}

결론

클로저는 많은 유연성과 편의성을 제공하지만, 성능 저하와 메모리 누수의 위험 요소를 가질 수 있습니다. 따라서 클로저를 사용할 때에는 적절한 캡처 방식을 선택하고, 클로저의 탈출 여부를 고려하며, 메모리 누수에 주의해야 합니다. 이를 통해 Swift에서 클로저를 효율적으로 활용할 수 있습니다.