클로저(Closure)
- 코드에서 사용할 수 있는 독립적인 코드블록으로 함수와 동일하게 사용하나 이름이 없는 익명함수로 사용 가능함
- 참조 타입으로, 힙에 저장되며 ARC가 메모리를 관리함
- 캡처 기능을 사용
- 일급 객체로 취급
- 함수의 참조를 저장하거나 변수에 할당 가능
- 다른 함수에 매개변수로 전달 가능
- 함수에서 반환 가능
클로저 기본 구문
{ (매개변수 목록) -> 반환 타입 in
실행 코드
}
- 매개 변수 목록 : 클로저가 사용하는 입력 값
- 반환 타입 : 실행 결과로 반환되는 값의 타입
- in : 클로저 헤더(매개변수 목록, 반환 타입)와 본문을 구문
예제
let multiplyClosure = { (a: Int, b: Int) -> Int in
return a * b
}
let result = multiplyClosure(4, 5) // 20
클로저의 캡처(Capture)
- 클로저는 외부 변수나 상수를 캡처하여 저장 및 참조 가능
- 값 타입은 복사 되고, 참조 타입은 참조를 유지함
캡처의 동작 원리
- 클로저는 자신이 정의된 환경(Context)에서 변수나 상수에 접근할 수 있음
- 클로저가 실행되는 시점에 관계없이, 외부 변수는 클로저 내부에서 지속적으로 참조되거나 저장됨
- 캡처 동작은 값 타입과 참조 타입에 따라 다르게 작동함
- 값 타입(Struct, Enum) : 캡처 시점에 복사되어 클로저 내부에 저장
- 참조 타입(Class) : 클로저 내부에서 해당 객체에 대한 강한 참조를 유지
래퍼런스 캡처링(Reference Capturing)
- 클로저가 외부 변수나 상수를 참조 형태로 캡처하는 방식
- 외부 변수의 변경 사항이 클로저 내에서도 반영됨
- 클로저 실행 시점의 변수 값을 사용함
특징
- 클로저는 변수의 메모리 위치를 참조함
- 외부 변수의 값이 변경되면 클로저 내부에서도 변경된 값을 볼 수 있음
var a = 0
let closure = {
print(a) // 래퍼런스 캡처링
}
a = 5
closure() // 5
→ 클로저가 실행될 때 변수 a의 현재 값을 참조함
벨류 캡처링 (Value Capturing)
- 클로저가 외부 변수나 상수의 값을 복사하여 캡처하는 방식
- 클로저 실행 시점과 무관하게, 캡처된 값은 변경되지 않음
특징
- 캡처 시점의 값을 복사하여 클로저 내부에 저장됨
- 외부 변수의 값이 이후에 변경되어도 클로저는 캡처된 초기 값만 사용
var a = 0
let closure = { [a] in // 벨류 캡처링, [a] : 캡처 리스트
print(a)
}
a = 5
closure() // 0
- [a] 를 통해 캡처 리스트를 지정하여 a의 값을 복사
- a의 값을 변경했지만, 클로저 내부에는 복사되었던 값 0을 출력함
래퍼런스 캡처링과 벨류 캡처링의 차이
특성 | 래퍼런스 캡처링(Reference Capturing) | 벨류 캡처링 (Value Capturing) |
캡처 형태 | 변수의 참조(주소)를 캡처 | 변수의 값을 복사하여 캡처 |
변경 반영 여부 | 클로저 실행 시 외부 변수의 변경 사항 반영 | 클로저 실행 시 캡처 시점 값 유지 |
사용 예시 | 클로저가 외부 변수의 실시간 값을 참조해야 할 때 | 값이 고정된 상태로 사용해야 할 때 |
캡처 리스트 (Capture List)
- 벨류 캡처링을 명시적으로 지정하려면 캡처 리스트를 사용함
- 캡처 리스트를 사용하면 특정 변수만 값을 복사하거나 참조 방식을 조정할 수 있음
var a = 0
var b = 0
let closure = { [a, b] in // 캡처 리스트로 벨류 캡처링
print(a, b)
}
a = 5
b = 10
closure() // 0, 0
- [a, b]를 캡처 리스트로 지정하여 a, b의 값을 복사함
- 이후 a, b 값을 변경해도 클로저 내부에서는 캡처 당시의 값 0, 0을 사용함
아래와 같은 클로저를 사용하면 어떤 값이 출력될까? 0? 아님 10?
class Test {
var value = 0
func setValue(value : Int){
self.value = value
}
}
let test = Test()
let closure = { [test] in // 캡처 리스트
print(test.value)
}
test.setValue(value: 10)
closure()
- 클로저를 통해 test 객체를 캡처했으니 해당 시점의 value 값 0이 출력될까?
- 하지만, 출력되는 값은 10이다. 그 이유는 아래 class와 struct에서의 클로저 동작 차이에서 알아보기로 하자
Class와 Struct에서 클로저 동작 차이
- 클로저는 외부 변수나 객체를 캡처할 때 참조 타입과 값 타입에 따라 다르게 동작함
- class와 struct는 메모리 관리와 캡처 방식에서 중요한 차이를 가지므로 클로저 동작 방식에도 영향을 받음
클래스에서 클로저 동작
- 클래스는 참조 타입
- 클로저가 인스턴스를 캡처하면 해당 객체에 대한 참조를 캡처함 → 객체 주소 캡처를 의미
- 클로저 실행 시점에 객체의 현재 상태를 참조함
class Test {
var value = 0
func setValue(value: Int) {
self.value = value
}
}
let test = Test() // 클래스 인스턴스 생성
let closure = { [test] in // 참조 캡처 (test의 참조를 캡처)
print(test.value) // 캡처된 test 인스턴스의 현재 value를 참조
}
test.setValue(value: 10) // test 인스턴스의 value를 변경
closure() // 10
- [test] 는 참조 캡처를 의미함 (주소 복사) → 클래스는 참조 타입이기 때문에 실행 시점의 value 값 10이 출력됨
구조체에서 클로저 동작
- 구조체는 값 타입
- 클로저가 구조체를 캡처하면 해당 시점의 값을 복사함
- 클로저 실행 시점에 구조체의 변경된 값은 반영되지 않음
struct Test {
var value = 0
mutating func setValue(value: Int) {
self.value = value
}
}
var test = Test() // 구조체 인스턴스 생성
let closure = { [test] in // 값 캡처 (test 자체 값을 캡처)
print(test.value) // 캡처된 test의 복사본 값
}
test.setValue(value: 10) // test 인스턴스의 value 변경
closure() // 0
- [test]는 값 캡처를 의미함(값 복사) → 구조체는 값 타입이기 때문에 캡처 시점의 value 값 0 출력
그럼 아래 코드에서는 어떤 이름이 출력될까?
struct Friend {
var name: String
init(name: String) {
self.name = name
}
func printName() {
print("Name is \(self.name)")
}
}
var friend = Friend(name: "민수")
friend.name = "철수"
let closure = friend.printName
friend.name = "영희"
closure()
정답은 “Name is 철수”이다
- 구조체는 값 타입(Value Type)
- friend가 printName 메서드를 캡처할 때, 현재 friend의 복사본을 캡처함
- 따라서, 이후 friend.name을 "영희"로 변경해도 클로저 내에서 캡처된 값은 변경되지 않음
- 클로저에서 캡처된 값
- let closure = friend.printName는 friend의 복사본에 있는 printName 메서드를 캡처
- 실행 시점에서 캡처된 복사본의 name 값인 "철수"를 출력
클래스에서의 동작
class Friend {
var name: String
init(name: String) {
self.name = name
}
func printName() {
print("Name is \\(self.name)")
}
}
var friend = Friend(name: "민수")
friend.name = "철수"
let closure = friend.printName // 클로저에 printName 메서드 캡처
friend.name = "영희" // friend의 name 변경
closure() // "Name is 영희" 출력
- 클래스는 참조 타입(Reference Type)
- friend가 printName 메서드를 캡처할 때, friend 객체의 참조 주소를 캡처
- 이후 friend.name을 "영희"로 변경하면, 클로저도 동일한 참조를 사용하기 때문에 변경된 값을 참조
- 클로저에서 참조된 값
- let closure = friend.printName는 friend의 참조를 캡처하므로, 실행 시점의 name 값인 "영희"를 출력
'Swift' 카테고리의 다른 글
Swift - 커스텀 폰트 적용 방법 및 활용 방법 (0) | 2025.01.05 |
---|---|
Swift - GCD (Grand Central Dispatch) (3) | 2024.12.10 |
Swift - Dispatch를 이용한 성능 개선 (1) | 2024.12.09 |
Swift - ARC (Automatic Reference Count) 정리 및 강한 순환 참조 해결 (2) | 2024.12.08 |
Swift - Struct와 Class 선택과 차이점 (0) | 2024.12.06 |