공식 Swift문서의 Closures
Closures are self-contained blocks of functionality that can be passed around and used in your code
클로저는 자체 포함된 함수 블록으로, 전달이 가능하고 코드안에서 사용되어진다.
Swift의 클로저에 대한 기본적인 개념을 알고 있다고 하고 시작하겠습니다.
전역 함수, 중첩 함수, 익명 함수 모두를 Swift에선 클로저라고 부릅니다.
ARC와 관련된 값 캡쳐링은 중첩 함수와 익명함수에 유의해서 살펴보면 됩니다.
공식문서 설명:
- 전역 함수는 이름이 있고 값을 캡처하지 않는 클로저입니다.
- 중첩된 함수는 이름이 있고 둘러싸는 함수에서 값을 캡처할 수 있는 클로저입니다.
- 클로저 표현식은 주변 컨텍스트에서 값을 캡처할 수 있는 간단한 구문으로 작성된 명명되지 않은 클로저입니다.
//전역 함수, 일반적임 함수 형태
func globalFunctions () {
//something
}
//중첩 함수
func globalFunctions () {
var test: Int = 0
func nestedFunctions () {
//something
}
}
//전역함수 안에 익명함수 형태의 클로저
func globalFunctions () {
let closure = {
//something
}
}
중첩 함수와 익명함수의 경우 Capturing Values가 진행됩니다.
다음과 같은 유의사항이 있고, 확인해보겠습니다
- 값/레퍼런스(Int,Struct/Class) 형태와 상관없이 Reference Type으로 Capturing Values이 이루어진다.
- 강한 참조로 값 캡처 진행
- 캡쳐 리스트를 통해 Weak or Unowned 참조가 가능하다.
- 캡쳐 리스트를 통해 Value Type으로 Capturing Values 진행이 가능하다.
이번 포스팅의 핵심 내용입니다.
- 중첩 함수와 익명 클로저 형태에서 Strong Reference로 Capturing Values가 진행되는 상황을 살펴보고
- 캡쳐 리스트를 통해 무조건 적인 Strong Reference를 방지하는 방법을 배워보고
- 실제 프로젝트에서의 예시 코드를 보고 포스팅을 끝내겠습니다 !
중첩 함수에서의 Capturing Values 예시 입니다.
func outer() -> () -> Int {
var num: Int = 0
func nestedFunctions() -> Int {
print("중첩 함수 실행")
return num + 10
}
num = 250
return nestedFunctions
}
let testResult = outer()
let value = testResult()
print(value)
//결과
//중첩 함수 실행
//260
num을 Reference Type으로 참조하기 때문에, 250의 값에 10을 더해 260이 반환 됩니다.
익명 클로저에서의 Capturing Values 예시 입니다.
func globalFunctions2 () {
var k: Int = 10
let closure = {
print(k)
}
k += (-20)
closure()
}
globalFunctions2()
//결과
//-10
func globalFunctions2 () {
var k: Int = 10
let closure = { [k] in
print(k)
}
k += (-20)
closure()
}
globalFunctions2()
//결과
//10 (k의 초기값)
- 캡쳐 리스트를 사용하지 않으면, Reference Type으로 값 캡쳐가 이루어집니다.
- 캡쳐 리스트를 사용하면 Value Type으로 값 캡쳐가 이루어집니다.
내용을 조금 더 확장해 볼까요?
- Value 캡쳐 리스트는, Const Value로 캡쳐 리스트가 이루어져 값 변경이 불가능
- Value 캡쳐 리스트는, 클로저가 생성될때 캡쳐 리스트가 이루어짐
func globalFunctions2 () {
var k: Int = 10
k = 200
let closure = { [k] in
k -= 1
print(k)
}
k += (-20)
closure()
}
globalFunctions2()
//캡쳐 리스트된 k는 const value이므로 클로저 내부에서 값 변경이 불가능
//immutable capture
func globalFunctions2 () {
var k: Int = 10
k = 200
let closure = { [k] in
print(k)
}
k += (-20)
closure()
}
globalFunctions2()
//출력 결과:
//200
//클로저가 생성되기 직전에 k의 값이 200 이므로, k = 200 상태로 캡쳐 리스트가 진행됨
레퍼런스 타입의 캡쳐 리스트는 어떨까요?
- 클로저가 호출된 시점에 캡쳐가 이루어 집니다. (Value 타입 캡쳐리스트는 클로저가 생성된 시점)
- 캡쳐 리스트한 클래스를 다른 값을 가진 클래스로는 초기화 불가능 immutable capture
- 하지만, 클래스의 프로퍼티는 수정할 수 있습니다. (private이 아닐시, Access control에 따라)
- Reference values Capture에선, testHuman 클래스를 초기화 및 변경할 수 있습니다.
class Human {
var name: String = "First Human"
}
var testHuman: Human = .init()
let closure = { [testHuman] in
print(testHuman.name)
}
testHuman.name = "Changed Name"
closure()
//Changed Name 출력
class Human {
var name: String = "First Human"
}
var testHuman: Human = .init()
let closure = { [testHuman] in
print(testHuman.name)
}
closure()
testHuman.name = "Changed Name"
//First Human 출력
//익명 클로저에서 캡쳐 리스트된 testHuman 값 변경
class Human {
var name: String = "First Human"
}
var testHuman: Human = .init()
let closure = { [testHuman] in
//testHuman = .init() 새로운 객체로 초기화 불가능
testHuman.name = "asdf"
//프로퍼티의 값 변경은 가능 , Access control에 따라서
print(testHuman.name)
}
testHuman.name = "Changed Name"
closure()
//asdf 출력
class Human {
var name: String = "First Human"
}
var testHuman: Human = .init()
let closure = {
testHuman = .init()
//일반적인 reference values capture 진행시, 클래스 초기화 가능
print(testHuman.name)
}
testHuman.name = "Changed Name"
closure()
- 또한 이러한 익명 클로저의 값 캡쳐가 강한 참조를 만들기 때문에
- 다음과 같은 상황에서 weak와 unowned가 필수 요소입니다.
class Human1 {
var name: String = "asdf"
var getName: () -> String = {
return self.name
}
}
class Human2 {
var name: String = "asdf"
var getName: () -> String = { [weak self] in
return self?.name
}
}
- Human1 클래스 객체를 만들어서, getName클로저를 실행하면 Human1의 객체는 메모리가 해제가 되지 않습니다. 바로 getName 클로저가 Human1(self)를 강한 참조해서 Retain Cycle이 생깁니다.
- 이때 캡쳐 리스트를 통해 weak 참조를 진행하면 문제가 해결 됩니다.
공식문서의 그림 예시입니다.
구조체의 캡쳐리스트는 어떨까요? (앞에 내용을 복습해보고 미리 생각해보세요!!)
struct Human {
var name: String = "First Human"
mutating func edit() {
self.name = "dd"
}
}
var testHuman: Human = .init()
testHuman.name = "Changed Name1"
let closure = {
print(testHuman.name)
}
testHuman.name = "Changed Name2"
closure()
//레퍼런스 타입의 값 캡처
//Changed Name2 출력
struct Human {
var name: String = "First Human"
mutating func edit() {
self.name = "dd"
}
}
var testHuman: Human = .init()
testHuman.name = "Changed Name1"
let closure = { [testHuman] in
print(testHuman.name)
}
testHuman.name = "Changed Name2"
closure()
//값 타입의 값 캡처 (캡처 리스트이용)
//Changed Name1 출력
Values(구조체) 캡처리스트 이용시 클로저가 생성되기 전의 값인 'Changed Name1'이 출력되지요?~
실제 프로젝트에선 ViewModel에서 통신 객체를 통한 API 호출 과정에서 Retain Cycle을 막기위해 다음과 같이 사용됩니다.
_ = vinylName
.flatMapLatest{ [unowned self] vinyl -> Observable<[SearchModel.Data?]> in
return self.searchAPIService.searchVinyl(vinylName: vinyl)
}
.bind(to: vinylsData)
.disposed(by: disposeBag)
'Swift information' 카테고리의 다른 글
우아한 Model Data 처리 (1) (0) | 2022.06.11 |
---|---|
ARC 시리즈 2 - SideTable (0) | 2021.11.04 |
ARC 시리즈 1 - Retatin Cycle 과 Reference Count (0) | 2021.11.02 |
Struct와 Class에서의 let 과 var의 차이에 대하여 (0) | 2021.11.02 |