[Swift] 자바스크립트와 스위프트 클로저 차이

2 분 소요

Swift와 JavaScript는 모두 함수를 값처럼 전달할 수 있고, 함수가 선언된 주변 스코프의 값을 캡처할 수 있다. 두 언어에서 클로저의 핵심 개념은 같지만, 문서에서 사용하는 용어와 메모리 관리 방식에는 차이가 있다.

클로저란 무엇인가

클로저는 함수와 그 함수가 선언될 당시의 렉시컬 환경을 함께 보존한 것이다. 바깥 함수의 실행이 끝난 뒤에도 안쪽 함수가 외부 변수를 사용할 수 있는 이유가 바로 이 환경을 캡처하기 때문이다.

function makeCounter() {
  let count = 0;

  return function () {
    count += 1;
    return count;
  };
}

const counter = makeCounter();
counter(); // 1
counter(); // 2

반환된 함수는 makeCounter() 실행이 끝난 뒤에도 count에 접근한다. 함수와 count가 포함된 렉시컬 환경이 함께 유지되기 때문이다.

Swift에서 말하는 클로저

Swift 문서는 다음 세 가지를 모두 클로저의 형태로 설명한다.

  • 전역 함수: 이름이 있고 값을 캡처하지 않는 클로저
  • 중첩 함수: 이름이 있고 주변 값을 캡처할 수 있는 클로저
  • 클로저 표현식: 이름이 없고 주변 값을 캡처할 수 있는 클로저
func makeCounter() -> () -> Int {
    var count = 0

    return {
        count += 1
        return count
    }
}

let counter = makeCounter()
counter() // 1
counter() // 2

Swift에서는 { ... } 형태의 익명 함수 문법을 “클로저 표현식”이라고 부른다. 현재 캡처하는 값이 없더라도 이 문법으로 만든 값은 클로저다.

JavaScript에서 말하는 클로저

JavaScript 함수는 생성될 때 자신의 렉시컬 환경과 연결된다. 따라서 기술적으로는 함수가 만들어질 때마다 클로저가 형성된다. 실무 문서에서는 특히 내부 함수가 외부 스코프의 변수를 계속 사용하는 상황을 설명할 때 “클로저”라는 표현을 강조하는 경우가 많다.

JavaScript의 변수가 모두 전역인 것은 아니다. let과 const는 블록 스코프를, var는 함수 스코프를 가진다. 현대 JavaScript에는 class 문법과 #privateField 형태의 private element도 있다. 클로저는 private 상태를 구현하는 유일한 방법이 아니라, 렉시컬 스코프를 활용하는 여러 도구 중 하나다.

class Counter {
  #count = 0;

  increment() {
    this.#count += 1;
    return this.#count;
  }
}

두 언어의 차이

구분 Swift JavaScript
용어 전역 함수, 중첩 함수, 클로저 표현식을 클로저의 형태로 설명 함수와 렉시컬 환경의 결합을 클로저로 설명
캡처 주변의 상수와 변수를 캡처하며 캡처 목록으로 동작을 조정 함수 생성 시 렉시컬 환경에 연결
메모리 ARC를 사용하므로 self의 강한 캡처가 참조 순환을 만들 수 있음 가비지 컬렉션이 도달 가능한 렉시컬 환경을 유지
명시적 제어 [weak self], [unowned self], [value] 같은 캡처 목록 제공 Swift와 같은 캡처 목록 문법은 없음

Swift 캡처 목록

Swift의 클로저는 기본적으로 클래스 인스턴스를 강하게 캡처한다. 인스턴스가 클로저도 강하게 보유하면 참조 순환이 생길 수 있다.

final class Loader {
    var onFinish: (() -> Void)?

    func start() {
        onFinish = { [weak self] in
            self?.handleFinish()
        }
    }

    private func handleFinish() {
        print("완료")
    }
}

값을 클로저 생성 시점에 복사해 고정하고 싶다면 캡처 목록에 값을 명시할 수 있다.

var value = 0
let printCapturedValue = { [value] in
    print(value)
}

value = 10
printCapturedValue() // 0

정리

Swift와 JavaScript의 클로저는 서로 다른 개념이 아니다. 둘 다 렉시컬 스코프의 값을 함수와 함께 유지한다. 차이는 Swift가 여러 함수 형태를 클로저라는 큰 범주로 명시하고 캡처 목록과 ARC 규칙을 제공하는 반면, JavaScript는 모든 함수가 렉시컬 환경과 연결되고 가비지 컬렉션으로 수명을 관리한다는 점이다.

참고 자료

댓글남기기