[Swift] Ch13. 클로저

JJUDEV·2024년 3월 28일
1

Swift

목록 보기
12/21
post-thumbnail

본 글은 야곰의 스위프트 프로그래밍: Swift5 교재를 토대로 공부한 내용과 찾아본 내용을 요약한 것입니다.

스위프트의 클로저는 C 언어나 Objective-C의 블록(Block) 또는 다른 프로그래밍 언어의 람다(Lambda)와 유사합니다. 함수는 클로저의 한 형태입니다. 클로저는 변수나 상수가 선언된 위치에서 참조(Reference)를 획득(Capture)하고 저장할 수 있습니다. 이를 변수나 상수의 클로징(잠금)이라고 하며 클로저는 여기서 착안된 이름입니다.

13.1 기본 클로저

{ (parameters) -> return type in
    statements
}

스위프트에서 클로저의 형태는 위와 같습니다.

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

var reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

이 예제에서는 sorted(by:) 메서드에 클로저를 전달하여 문자열 배열을 역순으로 정렬합니다. 클로저는 sorted(by:) 메서드의 인자로 전달되며, 배열의 각 요소를 비교하는 데 사용됩니다. 클로저 내부의 s1 > s2 비교 연산자는 문자열의 사전 순 비교를 수행하여 배열을 역순으로 정렬합니다.

// 타입 추론을 사용하여 매개변수 및 반환 타입을 생략할 수 있습니다.
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 })
// 단일 표현식 클로저에서는 return 키워드조차 생략할 수 있습니다.
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 })

13.2 후행 클로저

클로저가 조금 길어지거나 가독성이 조금 떨어진다 싶으면 후행 클로저(Trailing Closure) 기능을 사용하면 좋습니다. Xcode에서도 자동완성 기능을 사용하면 자동으로 후행 클로저로 유도합니다.
후행 클로저(Trailing Closure)는 함수의 마지막 전달인자로 전달되는 클로저를 함수의 괄호 밖에 작성할 수 있는 Swift의 문법적 특성입니다. 이 문법은 특히 클로저가 길거나 코드의 가독성을 높이고 싶을 때 유용하게 사용됩니다. 후행 클로저를 사용하면 함수의 인자 목록이 간결해지고, 클로저의 본문이 더욱 명확하게 드러나게 됩니다.

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

// 후행 클로저를 사용하지 않는 경우
var reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

// 후행 클로저를 사용하는 경우
reversedNames = names.sorted { s1, s2 in
    return s1 > s2
}

13.3 클로저 표현 간소화

클로저 표현을 간소화하는 몇 가지 방법에 대해 하기 원래 표현식과 비교하여 설명해 보겠습니다.

// 원래 표현
let numbers = [1, 2, 3, 4, 5]
let doubledNumbers = numbers.map({ (number: Int) -> Int in
    return number * 2
})

1) 문맥을 이용한 타입 유추

Swift 컴파일러는 컨텍스트를 사용하여 클로저의 매개변수 타입과 반환 타입을 추론할 수 있습니다. 이를 통해 매개변수와 반환 타입을 명시하지 않고 클로저를 작성할 수 있습니다.

let doubledNumbers = numbers.map({ number in number * 2 })

2) 단축 인자 이름

Swift 클로저는 $0, $1, $2 등의 단축 인자 이름을 사용하여 매개변수를 간단히 참조할 수 있습니다. 이 방식을 사용하면 클로저의 본문에서 매개변수 이름을 직접 지정할 필요가 없어 코드가 더욱 간결해집니다.

let doubledNumbers = numbers.map({ $0 * 2 })

3) 암시적 반환 표현

단일 표현식 클로저에서는 return 키워드를 생략할 수 있습니다. 컴파일러는 클로저의 본문에서 단일 표현식만 존재할 경우 자동으로 이 표현식의 결과를 반환값으로 간주합니다.

let doubledNumbers = numbers.map { $0 * 2 }

4) 연산자 함수

클로저가 필요한 곳에, 특히 매개변수 타입과 반환 타입이 명확한 연산자를 직접 사용할 수 있습니다. 예를 들어, 비교 연산자는 두 매개변수를 받아 Bool 값을 반환하는 함수이므로, 이를 클로저 대신 사용할 수 있습니다.

let sortedNumbers = numbers.sorted(by: <)

13.4 값 획득

Swift에서 클로저는 주변의 변수나 상수를 획득(capture)할 수 있는 능력을 가지고 있습니다. 이 특성 덕분에 클로저는 정의된 컨텍스트를 벗어나서도 이러한 값을 참조하고 수정할 수 있습니다. 클로저가 값을 획득하는 방식은 클로저의 바디 안에서 비동기적으로 작업을 수행하거나, 함수가 실행을 마치고 반환된 이후에도 해당 값에 접근할 필요가 있을 때 유용하게 사용됩니다.

  • 값 획득의 기본 원리
    클로저가 값을 획득한다는 것은, 클로저가 선언된 시점의 환경에 있는 변수나 상수에 대한 참조를 클로저가 내부적으로 저장한다는 의미입니다. 클로저에 의해 획득된 변수나 상수는 클로저가 실행되는 동안 계속해서 접근 가능하며, 클로저의 본체 내에서 사용될 수 있습니다.

  • 값 획득의 종류
    비획득: 클로저 내부에서 사용되지만 외부 변수나 상수의 값을 복사하거나 참조하지 않는 경우. 이 경우 클로저는 독립적인 값을 가지며 외부 스코프의 변수나 상수에 영향을 주지 않습니다.
    참조 획득(Reference Capture): 클로저가 클래스 인스턴스와 같은 참조 타입의 객체를 획득하는 경우, 클로저는 해당 인스턴스에 대한 참조를 저장합니다. 이로 인해 클로저 내부에서 해당 인스턴스의 속성을 변경할 수 있으며, 변경사항은 인스턴스에 반영됩니다.
    값 획득(Value Capture): 클로저가 구조체나 열거형과 같은 값 타입을 획득하는 경우, 해당 값의 복사본을 생성하여 저장합니다. 클로저 내부에서 이 값을 변경하더라도, 외부 스코프의 원본 값에는 영향을 주지 않습니다. 하지만 Swift의 escaping 클로저에서는 @escaping 속성을 사용하여 클로저 외부의 변수를 획득하고 변경할 때, 해당 변수를 var로 선언하고 클로저 내부에서 self를 통해 명시적으로 획득해야 합니다.

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

let incrementByTen = makeIncrementer(forIncrement: 10)

print(incrementByTen()) // 10
print(incrementByTen()) // 20

위 예시에서 makeIncrementer 함수는 클로저 incrementer를 반환합니다. 이 클로저는 amount와 runningTotal을 획득합니다. 클로저가 amount와 runningTotal을 획득함으로써, incrementByTen을 호출할 때마다 runningTotal의 값이 증가하게 됩니다. 이는 클로저가 선언된 환경의 변수들과 상호작용할 수 있음을 보여줍니다.

13.5 클로저는 참조 타입

클로저가 참조 타입이라는 것은 클로저가 변수에 할당되거나 함수에 인자로 전달될 때, 실제 클로저의 복사본이 아닌 클로저를 참조하는 방식으로 동작함을 의미합니다. 따라서, 하나의 클로저를 여러 변수에 할당하고 해당 클로저를 통해 값을 변경할 경우, 모든 변수에 할당된 클로저를 통해 동일한 데이터에 접근하고 수정하는 것이 가능합니다.

클로저가 참조 타입이라는 사실은 클로저 내부에서 캡처된 변수나 상수에 대해 특별한 동작을 야기할 수 있습니다. 예를 들어, 클로저가 외부 변수를 캡처할 때, 클로저는 해당 변수의 참조를 캡처합니다. 이러한 동작은 클로저가 변수의 현재 값을 캡처하는 것이 아니라, 해당 변수 자체를 캡처한다는 점에서 중요한 의미를 가집니다. 결과적으로, 클로저 내부 또는 외부에서 변수의 값이 변경되면, 클로저를 통해 해당 변경 사항을 반영할 수 있습니다.

이러한 특성 때문에 클로저를 사용할 때는 메모리 관리에 주의해야 합니다. 특히 클로저가 클래스 인스턴스의 속성을 캡처할 경우, 순환 참조(Circular Reference)가 발생할 수 있어, 이를 방지하기 위해 캡처 목록(Capture List)과 약한 참조(Weak Reference) 또는 미소유 참조(Unowned Reference)를 사용하는 것이 좋습니다.

참고자료

  • 스위프트 프로그래밍 3판(지은이: 야곰, 출판사: 한빛미디어(주))
  • ChatGPT 4
profile
4년차 앱개발자입니다.

0개의 댓글