일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 시계방향
- 결혼식장계약후기
- 베뉴
- 레이아웃
- optional binding
- Linked List
- swiftUI
- 생각
- 더채플엣청담
- SWIFT
- 다짐
- enum
- Optional
- stack
- Double Linked List
- nodejs
- hstack
- vstack
- JavaScript
- Optional Chaining
- 각도
- Universal Hashing
- Test
- AlignmentGuide
- 자료구조
- layout
- Hashing
- 좌표공간
- Today
- Total
klioop for iOS
ARC, weak, and weak self 본문
ARC 는 Automatic Reference Counting 의 약자이다. Swift 에서는 직접 메모리 관리를 하지 않고 ARC 가 메모리를 알아서 관리한다.
어떤 클래스의 instance 가 만들어질때 마다 ARC 는 메모리의 특정 영역을 할당해서 해당 instance 의 정보를 저장한다. 구체적으로 instance 의 type 과 함께 관련된 properties 의 값들을 저장한다.
instance 의 주소를 가리키는 변수가 nil 값을 부여받는 등 instance 가 필요없어지면 ARC 는 할당한 메모리를 회수한다.
그런데 instance 가 아직 필요한데 ARC 가 해당 instance 에 할당한 메모리를 회수하면 안되므로 ARC 는 항상 어떤 properties, 변수 또는 상수가 해당 instance 를 참조하고 있는지 추적한다.
하나의 참조라도 활성되어있으면 ARC 는 instance 에 할당한 메모리를 회수하지 않는다.
이렇게 변수나 상수 등으로 특정 instance 를 참조 하는 것을 strong reference 라고 부른다.
instance 가 회수되지 않도록 꽉 잡고 있기 때문에 strong 이 붙는다고 한다.
이런 특성 때문에 swift 에서는 클래스 instance 사이에서 strong reference cycle 이 발생할 수 있다.
instance 끼리 서로 강하게 참조하고 있기 때문에 해당 instance 가 필요없어서 할당된 메모리를 회수해야 하는데도 그렇지 못한 상황을 의미한다. 메모리 누수가 발생하는 것이다.
struct 이나 enum 을 다룰 때는 이런 상황이 발생하지 않는다. 모두 value type 이기 때문이다.
따라서 swift 에서는 필요한 경우가 아니면 class 보다 struct 으로 코드를 작성하는 게 안전하다.
다음의 예시 하나를 살펴보자.
class Blog {
let name: String
let url: URL
var owner: Blogger?
init(name: String, url: URL) { self.name = name; self.url = url }
deinit {
print("Blog \(name) is being deinitialized")
}
}
class Blogger {
let name: String
var blog: Blog?
init(name: String) { self.name = name }
deinit {
print("Blogger \(name) is being deinitialized")
}
}
var blog: Blog? = Blog(name="Amor", url="klioop.tstory.com") // nil 을 받기 위해 optional type 으로 선언
var blogger: Blogger? = Blogger(name="sam")
blog?.owner = blogger
blogger?.blog = blog
blog = nil
blogger = nil
// nothing printed
blog 와 blogger 의 deinit 이 호출되지 않는 것을 알 수 있다.
blog 에 nil 값을 부여했지만 blogger 가 blog 를 강하게 참조하고 있기 때문에 blog 는 사라지지 않는다.
마찬가지로 blogger 에 nil 을 부여해도 blog 가 강하게 참조하고 있기 때문에 blogger 는 사라지지 않는다.
blog 는 자신을 잡고있는 blogger 를 놓지 않는다. blogger 도 자신을 잡고있는 blog 를 놓지 않는다. 사이클이다!
Swift 에서는 이러한 문제를 해결 하기 위해 weak 개념이 있다.
한 쪽의 instance 가 다른 instance 를 약하게 참조하도록 만드는 것이다. 참조는 하고 있지만 해당 instance 가 사라지는 것을 방해하지 않는다는 의미에서 약하다.
실제 코드로는 var 앞에 weak 를 적어주면 된다. blog 가 blogger 를 약하게 참조하도록 만들어보자.
class Blog {
let name: String
let url: URL
weak var owner: Blogger?
init(name: String, url: URL) { self.name = name; self.url = url }
deinit {
print("Blog \(name) is being deinitialized")
}
}
blog?.owner = blogger
blogger?.blog = blog
blog = nil
blogger = nil
// Blogger sam is being deinitialized
// Blog amor is being deinitialized
blog 에 nil 을 먼저 부여했지만 blogger 의 deinit 이 먼저 호출되는 것을 확인하자.
blog 는 blogger 를 약하게 참조하지만 blogger 는 blog 를 강하게 참조하기 때문에 blogger 가 사라져야 blog 도 사라질 수 있다.
강참조 사이클(Strong Reference Cycle)이 instances 사이뿐 아니라 instance 와 closure 사이에서도 발생할 수 있다.
Swift 에서 closure 도 reference type 이다.
Post 클래스를 새로 정의하고 Blog 클래스에 출판기능을 더해보자.
class Post {
let title: String
var isPublished: Bool = false
init(title: String) { self.title = title }
}
class Blog {
let name: String
let url: URL
weak var owner: Blogger?
var publishedPosts: [Post] = []
var onPublish: ((_ post: Post) -> void)?
init(name: String, url: URL) {
self.name = name
self.url = url
onPublish = { post in
self.publishedPosts.append(post)
print("Published post count is now \(self.publishedPosts.count)")
}
deinit {
print("Blog \(name) is being deinitialized")
}
func publish(post: Post) {
// 가짜 네트워크 request
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.onPublish?(post)
}
}
}
class Blogger {
let name: String
var blog: Blog?
init(name: String) { self.name = name }
deinit {
print("Blogger \(name) is being deinitialized")
}
}
var blog: Blog? = Blog(name="Amor", url="klioop.tstory.com") // nil 을 받기 위해 optional type 으로 선언
var blogger: Blogger? = Blogger(name="sam")
blog?.owner = blogger
blogger?.blog = blog
blog!.publish(Post(title: "weak self 알아보기"))
blog = nil
blogger = nil
// Blogger sam is being deinitialized
blog 가 onPublish closure 를 강하게 참조하고 있고 onPublish 도 blog 를 self 를 통해서 강하게 참조 하고 있다.
(정확히는 main.asyncAfter closure 를 강하게 참조하고 main.asyncAfter closure 가 onPublish closure 를 강하게 참조하고 있다. 또한 onPublish closure 는 blog 를 강하게 참조하고 있다)
여기서도 사이클이 발생하고 있는 것이다.
이런 상황을 해결하기 위해 Swift closure 에는 capturing list 라는 개념이 존재한다. closure 영역 인자 앞에서 정의된다.
closure 가 영역바깥의 어떤 것을 참조할 때 약하게 참조하도록 하는 장치이다. 코드로 살펴보면 다음과 같다.
class Blog {
let name: String
let url: URL
weak var owner: Blogger?
var publishedPosts: [Post] = []
var onPublish: ((_ post: Post) -> void)?
init(name: String, url: URL) {
self.name = name
self.url = url
onPublish = { [weak self] post in
self?.publishedPosts.append(post)
print("Published post count is now \(self?.publishedPosts.count)")
}
deinit {
print("Blog \(name) is being deinitialized")
}
func publish(post: Post) {
// 가짜 네트워크 request
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.onPublish?(post)
}
}
}
blog?.owner = blogger
blogger?.blog = blog
blog!.publish(Post(title: "weak self 알아보기"))
blog = nil
blogger = nil
// Blogger sam is being deinitialized
// Published post count is now: Optional(1)
// Blog SwiftLee is being deinitialized
onPublish closure 영역안, 인자를 써주는 부분에서 post 앞에 [weak self] 를 추가한다. capturing list 라는 이름답게 여러 참조 값에 적용할 수 있다.
e.g. [weak self, weak someProperty ...]
weak self 는 self 의 nil 값을 허용한다는 의미도 되기 때문에 closure 영역 안에서 self? 로 참조해야 한다.
DispatchQueue.main.asyncAfter 의 closure 가 self.onPublish 를 호출한다. DispatchQueue.main.asyncAfter closure 는 blog 를 강참조한다.
onPublish closure 가 자신의 역할을 수행한다. 이 때 blog 를 약참조하고 있다.
onPublish 가 일을 끝내면 DispatchQueue.main.asyncAfter closure 가 사라진다.
자신을 강참조 하고 있던 closure 가 사라졌기 때문에 현재 blog 를 참조하는 것은 onPublish 밖에 없다.
하지만 onPublish closure 의 참조는 약하기 때문에 blog 는 사라질 수 있고 사라진다.
deinit 이 호출되고 해당 statement 가 출력되는 것을 알 수 있다.
참조
docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html
www.avanderlee.com/swift/weak-self/
www.quora.com/What-does-weak-self-mean-in-a-Swift-Closure
www.swiftbysundell.com/articles/swifts-closure-capturing-mechanics/
'swift' 카테고리의 다른 글
Static, Dynamic(table), 그리고 Message dispatch (0) | 2021.12.15 |
---|---|
[Swift] Optional 이해하기 (0) | 2021.06.09 |
value type vs reference type (0) | 2021.03.17 |