Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
Tags
- Observable
- 스위프트
- combine
- Human interface guide
- RxCocoa
- tableView
- map
- SWIFT
- 리펙토링
- UITextView
- Clean Code
- 리팩토링
- swift documentation
- Refactoring
- swiftUI
- 애니메이션
- UICollectionView
- 리펙터링
- rxswift
- 클린 코드
- Protocol
- collectionview
- Xcode
- uiscrollview
- ios
- MVVM
- clean architecture
- ribs
- HIG
- uitableview
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] 1. 클로저를 사용할 때 주의할 점 - Memory Leaks, Retain Cycle, nested closure 첫 번째 본문
iOS 응용 (swift)
[iOS - swift] 1. 클로저를 사용할 때 주의할 점 - Memory Leaks, Retain Cycle, nested closure 첫 번째
jake-kim 2023. 1. 3. 23:341. 클로저를 사용할 때 주의할 점 - Memory Leaks, Retain Cycle, nested closure 첫 번째
2. 클로저를 사용할 때 주의할 점 - nested closure 두 번째
3. 클로저를 사용할 때 주의할 점 - nested closure 세 번째
Memory Leak이란?
- 메모리에 할당 되었지만, 절대 release되지 않고 reference되지 않는 것
- 이것을 참조할 수 없기 때문에 절대 release할 수 없는 상태
Retain Cycle이란?
- 생성자가 할당 해제된 후에도 인스턴스가 할당 해제되지 않도록 하는 reference 상 순환을 이루는 상태
- 둘 이상의 인스턴스가 서로에 대한 strong 참조를 보유할 때 발생
retain cycle이 발생하지 않는 케이스
- UIView.animate 클로저
- 한 번만 실행된 다음 할당이 취소되므로 블록이 실행 될 때 애니메이션 블록의 strong 참조도 해제
UIView.animate(withDuration: 0.22) {
self.view
}
- DispatchQueue 클로저
- 해당 클로저가 외부의 변수를 strong으로 capture했고, 이게 escaping(해당 메소드 블록이 끝난 다음에 실행) 되더라도 참조가
- 단, asyncAfter일 경우, weak self 없이 storng으로 참조 시 해당 시간이 지난 후 memory 해제 (retain은 안되므로 메모리릭 x)
DispatchQueue.main.async {
self.view
}
DispatchQueue.main.asyncAfter(deadLine: .now() + 2) {
self.view
}
- computed property에서도 memory leak x
var intValue: Int {
self.age
}
Memory leak 유발하는 closure 구분하기
- 일반적인 상황, closure에서 self를 쓰면 retain cycle되는 이유
- B에서 a의 클로저에 접근하는 상황: A는 내부적으로 사용하는 모듈이며, closure를 받아서 escaping하는 상태
- A -> B 참조(B프로퍼티에 a선언), A <- B 참조(B init부분 클로저에서 self로 참조)
// self에서 escaping하지 않아도 retain되는 원리
// B가 사용하는쪽
// B에서 클로저로 self를 넘기는 상황 -> A에서 내부적으로 escaping하면 retain cycle 발생
class A {
private var closureEscaper: ((String) -> ())?
func escape(closure: @escaping (String) -> ()) {
print("escaping!")
closureEscaper = closure
}
}
class B {
var name = "Jake"
let a = A() // 1. B에서 A참조
init() {
a.escape { string in
self.name = string // 2. A에서 B참조
}
}
deinit {
print("DEINIT: B")
}
}
var b: B? = B()
b = nil
// retain cycle
// A -> B 참조(B프로퍼티에 a선언), A <- B 참조(B init부분 클로저에서 self로 참조)
- closure에서 단순히 self를 사용하여 캡쳐했지만, 해당 클로저가 escaping되지 않으면 retain cycle이 발생하지 않음
// retain cycle x
class Jake0 {
let name = "Jake"
init() {
// self.closure가 참조 -> print(self.name)
{
print(self.name) // print(self.name)이 참조 -> self
}()
}
deinit {
print("DEINIT Jake0")
}
}
var jake0: Jake0? = Jake0()
jake0 = nil
// DEINIT Jake0
- 캡쳐하고 escaping되면 retain cycle 발생
// memory leak o
class Jake {
let name = "Jake"
var closure: (() -> ())?
init() {
// self.closure가 참조 -> print(self.name)
closure = {
print(self.name) // print(self.name)이 참조 -> self
}
}
deinit {
print("DEINIT")
}
}
var jake: Jake? = Jake()
jake = nil
// 메모리해제 x
- DispatchQueue.main.asyncAfter(deadLine:)을 사용하면 n초 후 deinit
class Jake2 {
let name = "Jake2"
init() {
// self.closure가 참조 -> print(self.name)
let someClosure = {
print(self.name) // print(self.name)이 참조 -> self
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
someClosure()
}
}
deinit {
print("DEINIT2")
}
}
var jake2: Jake2? = Jake2()
jake2 = nil
// 3초 후 DEINIT2
- nested closure인 경우, 안쪽에 weak self를 쓸 경우 retain cycle
// retain cycle
class Jake3 {
let name = "Jake3"
var closure: (() -> ())?
var closure2: (() -> ())?
init() {
let outerClosure = {
let internalClosure = { [weak self] in // 안쪽 closure에서 weak으로 self를 잡을때 이미 outerClosure에서 strong
print(self?.name)
}
internalClosure()
}
closure = outerClosure
closure?()
}
deinit {
print("DEINIT3")
}
}
var jake3: Jake3? = Jake3()
jake3 = nil
- 만약 outerClosure에 weak self 쓸 경우 retain cycle x
class Jake3 {
let name = "Jake3"
var closure: (() -> ())?
var closure2: (() -> ())?
init() {
let outerClosure = { [weak self] in // <- 외부에서 weak으로 self했으므로 retain cycle x
let internalClosure = {
print(self?.name)
}
internalClosure()
}
closure = outerClosure
closure?()
}
deinit {
print("DEINIT3")
}
}
var jake3: Jake3? = Jake3()
jake3 = nil
// DEINIT3
- lazy var 클로저인 경우 주의
// retain cycle o
class Jake4 {
let name = "Jake"
lazy var closure: (() -> ())? = {
print(self.name) // 참조: closure -> self
}
init() {
closure?() // 참조: self -> closure
}
deinit {
print("DEINIT: Jake4")
}
}
var jake4: Jake4? = Jake4()
jake4 = nil
* 참고
https://help.apple.com/instruments/mac/current/#/dev7b09c84f5
https://developer.apple.com/videos/play/wwdc2018/416/
https://blogs.halodoc.io/methods-to-prevent-memory-leaks-in-ios/
'iOS 응용 (swift)' 카테고리의 다른 글
Comments