[weak self]
같은 캡처 리스트에 대해 잘 몰랐던 부분을 작성해보려고 합니다
var a = 0
var b = 0
let closure = { [a] in
print(a, b)
}
a = 10
b = 10
print(closure()) // 0, 10
위 코드처럼 a
를 캡처 시, 이 캡처의 내부범위는 클로저가 생성될 때, 외부 범위의 값으로 초기화를 하기 때문에 a
의 현재 값은 0 이므로, 나중에 값이 바뀌어도 변하지 않는다.
하지만 캡처된 변수 유형에 참조의 의미가 있다면 동일한 객체를 참조하므로, 해당 객체에 따라 값을 참조한다. (변할 수 있다는 의미!)
또한 캡처 리스트 내부에서 표현식이 사용 가능합니다.
myFunction { weak parent = self.parent in
print(parent!.title)
}
위 코드 처럼 클로저가 생성될 때 표현식이 평가되고, 지정된 강도(weak, unowned)로 값이 캡처 됩니다.
self
의 멤버를 참조할 때 마다, self
키워드를 작성하라고 swift
는 요구합니다. 이는 실수로 self
를 캡처할 수 있따는 것을 기억하는데 도움을 줍니다.lazy var someClosure = {
[unowned self, weak delegate = self.delegate]
(index: Int, stringToProcess: String) -> String in
// body
}
만약 컨텍스트가, 매개변수 유형과 반환 유형을 유추 가능하다면 생략하고, 그 후에 캡처리스트를 앞에 배치하고 in
키워드를 작성합니다.
lazy var someClosure = {
[unowned self, weak delegate = self.delegate] in
// body
}
클래스 인스턴스의 파라미터에 클로저를 할당하고, 해당 클로저의 본문이 인스턴스를 캡처하는 경우에도 강한 참조 주기가 발생하는 것을 볼 수 있습니다. 이는 클로저 또한 참조 유형이고, 클로저의 본문이 인스턴스 속성에 액세스 하기 때문에 발생이 가능합니다. (메서드 호출도 그 이유가 된다.)
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
}
위 코드에서 HTMLElement
인스턴스와 asHTML
값으로 사용된 클로저 간에 강한 참조 사이클을 생성합니다.
이의 해결은 캡처리스트를 활용하는 것입니다.
lazy var asHTML: () -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
다음으로, 미소유 참조와 약한 참조를 살펴보겠습니다.
먼저 unowned
를 살펴보면,
unowned
를 작성한다.다음 weak
를 살펴보면,
nil
이 될 가능성이 있는 경우 weak
를 사용한다.optional type
이고, 참조하는 인스턴스가 할당 해제시 자동으로 nil
이 됩니다. (만약, 참조가 절대로 nil
이 안된다면 unowned
로 캡처해야 합니다.)잘 모르고 쓰던 KeyPath
에 대해서 알아보겠슴당
이는 값에 대한 참조가 아닌 프로퍼티에 대한 참조 입니다. -> 어떤 타입의 값에 대한 Path
!
표현식은 \type name.path
와 같고, swift에서는 타입유추가 가능하다면 생략이 가능하다.
let str: KeyPath<String, String> = \.description
// \String.description과 동일
struct Cluster {
var name: String
}
struct Cadet {
let cluster1: cluster
let cluster2: cluster
}
여기서 cadet
내부 프로퍼티를 구하는 함수를 만든다면 다음과 같습니다.
func getCluster1(cadet: Cadet) -> Cluster {
return cadet.cluster1
}
func getCluster2(cadet: Cadet) -> Cluster {
return cadet.cluster2
}
하지만, KeyPath
를 사용한다면 다음과 같이 작성이 가능합니다.
func getCluster(cadet: Cadet, keyPath: KeyPath<Cadet, Cluster>) -> Cluster {
retrun cadet[KeyPath: keyPath]
}
// 사용 예시
print(getCluster(cadet: cadet, keyPath: \.cluster).name)
이런식으로 받은 KeyPath
값에 따라서, Cadet
내부 프로퍼티에 접근이 가능합니다.
함수의 형태로의 KeyPath는 \Root.Value
는 (Root) -> Value
로 나타낼 수 있슴다
map
함수의 경우 (Element) -> Value
의 매개변수를 받는걸 허용합니다. 이걸 바탕으로 KeyPath를 전달이 가능합니다.
let names = users.map { $0.name }
// 위의 map과 동일하다.
let nameUsingKeyPath = users.map(\.name)
이는 프로퍼티의 이름, 서브스크립트, 옵셔널 체이닝 메서드, 강제 언래핑 표현식이 올 수 있다
\User.name
\[Int][0]
\User.address?.street
\User.address!.street
기본적으로 객체와 구조체의 인스턴스에 대한 값을 참조시에 주로 사용한다. 계산 속성도 참조 가능하며 필요한 만큼 반복 호출이 가능하다.
이는 프로퍼티와 서브스크립트, 그리고 Root 타입에서 유추한다.
KeyPath
로 추론된다.WritableKeyPath
추론된다.ReferenceWritableKeyPath
로 추론된다.모든 유형에서 사용 가능한 subscript(keyPath:)
를 사용하여 키패스를 통해 값으로 액세스 가능하다
let role = user[keyPath: \User.role]
\.self
문법을 사용하여서 프로퍼티 대신 전체 인스턴스를 참조 할 수 있다.
이 KeyPath의 결과는 WritableKeyPath
이므로, 이를 사용해서 한 번에 모든 데이터에 액세스 하고 변경이 가능하다.
var foo = "Foo"
let stringIdentity = \String.self
// WritableKeyPath<String, String>
foo[keyPath: stringIdentity] = "Bar"
print(foo) // Bar
struct User {
let name: String
}
var user = User(name: "John")
let userIdentity = \User.self
// WritableKeyPath<User, User>
user[keyPath: userIdentity] = User(name: "Doe")
print(user) // User(name: "Doe")
이는 SwiftUI
에서 ForEach
를 사용할 때 주로 활용하는 방식입니다. SwiftUI
에서의 Identifiable
의 요구 조건은 Hashable
을 만족하는 ID
변수 입니다. 이를 KeyPath
를 통해 대체 초기화가 가능합니다.
import SwiftUI
struct User {
let name: String
}
struct ContentView: View {
let users: [User] = [
User(name: "John"),
User(name: "Alice"),
User(name: "Bob"),
]
var body: some View {
ScrollView {
ForEach(users, id: \.name) { user in
Text(user.name)
}
}
}
}
User
구조체를 고유하게 식별 가능하게 하는 속성에 대한 경로를 지정하여서 위 처럼 사용이 가능합니다.
\.name
은\User.name
과 같습니당
참조
https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html
https://sarunw.com/posts/what-is-keypath-in-swift/#conclusion