iOS 개발 기록

weak self, unowned self 본문

Swift

weak self, unowned self

택꽁이 2023. 2. 16. 14:40
728x90
📄

Retain Cycle

  • Closure의 경우 reference type이기 때문에 순환 참조(retain cycle)를 발생시킬 수 있다.
class Person { 
	let name: String 

	// self가 확정난 후에 name을 참조해야 하기 때문에 lazy 변수로 설정 
	lazy var introduction: () -> String = { 
		return "저는 \(self.name) 입니다."
	}
	
	init(name: String) { 
		self.name = name
	}
	deinit{ 
		print("deinit")
	}
}

var leetaek: Person? = Person(name: "leetaek")
print(leetaek?.introduction())
leetaek = nil

// 출력: Optional("저는 leetaek 입니다.")
  • leetaek 변수가 Person 객체를 참조하고, introduction closure의 Person의 name을 참조한다. (: reference count 2) → 때문에 deinit이 호출되지 않는다.
  • weak var 의 경우 class 혹은 AnyObject를 상속받은 protocol에만 사용 가능하므로 introduction에는 사용할 수 없다.

Capture list

  • 캡처 목록은 클로저가 생성될 때에 변경되지 않고 남아있기를 바라는 값들의 리스트를 뜻한다. 캡쳐된 값들은 클로저 외부의 코드가 종료되더라도 클로저 내부에 저장되어 남아있는다.
var str = "Hello, World!"
var myClosure = { 
print (str)
}
str = "next"
let inc = myClosure
inc()
// 출력: next
var str = "Hello, World!"
var myClosure = { [str] in
print (str)
}
str = "next"
let inc = myClosure
inc()
// 출력: Hello, World!

weak, unowned

  • 위와 같은 강한 참조로 인한 순환 참조 문제를 해결하고 싶다면 Capture list를 weak, unowned를 통해 가져오면 된다.
class Person { 
	let name: String 

	// self가 확정난 후에 name을 참조해야 하기 때문에 lazy 변수로 설정 
	lazy var introduction: () -> String = { [weak self] in
		return "저는 \(self?.name) 입니다."
	}
	
	init(name: String) { 
		self.name = name
	}
	deinit{ 
		print("deinit")
	}
}

var leetaek: Person? = Person(name: "leetaek")
print(leetaek?.introduction())
leetaek = nil

// 출력: Optional("저는 leetaek 입니다.")
// deinit
  • leetaek 변수가 Person 객체를 참조하고, introduction closure의 name을 weak로 참조한다. (: reference count 1) → deinit이 호출되지 않는다.
  • unowned 로도 참조할 수 있지만 앱 크래쉬를 항상 조심해야 한다.

Non-escaping closure vs Escaping closure

Non-escaping closure

  • Non-escaping closure는 나중에 저장되거나 실행될 수 없다. 때문에 메모리가 실제보다 늦게 해제되는 delayed deinitialization 은 발생하지 않는다.
  • 그렇지만 위의 예에서와 같이 순환 참조로 인한 Memory Leak은 발생할 수 있다. → weak self로 해결

escaping closure

  • Escaping closure는 프로퍼티에 저장되거나 다른 클로저에 넘겨져 선언 이후 시점에 실행될 수 있는 함수를 의미한다.
  • Escaping closureNon-escaping closure와 동일하게 순환참조 문제로 인한 memory leak이 발생할 수 있다.
  • 뿐만 아니라 내가 원하는 타이밍에 메모리 해체를 방해해 delayed deinitialization가 생길 가능성도 있다. (ex. controller를 해체했지만 아직 closure 작업이 안끝났다던지)

→ 위 경우 모두 weak self로 해결 가능ㅎ

self? VS guard let self = self else { return }

  • 옵셔널 체이닝 (self?)
    • 옵셔널 체이닝이 달린 모든 self에 대해 nil 검사를 한다.
    • self가 nil이 된다면 해당 메소드 호출을 멈춘다.
    • 즉, 중간에 self가 해제되었는데도 계속 closure가 실행되는 delayed deinitialization 문제를 해결할 수 있다.
  • 옵셔널 바인딩 ( guard let self = self else { return })
    • self가 nil인지 확인하고 아닌 경우 일정 시간 동안 강한 참조를 일시 생성한다.
    • self를 보장하므로, self가 먼저 메모리에서 해제되더라도 메소드가 계속 진행된다.
    • 때문에 delayed deinitialization로 이어질 수 있다.

→ 때문에 경우에 따라 어떤 방법을 사용해야 할지 생각할 필요가 있다.

Reference

  • weak, unonwed
[Swift] ARC와 클로저, 클로저 memory leak - ARC 기초편 4탄
안녕하세요, 지난 시간에는 ARC의 정의, Strong Reference Cycle, weak unowned의 차이에 대해서 공부했습니다. 이번 시간에는 weak unowned의 예시로 들었었던 View Controller의 클로저 내에서 weak self를 해주는 이유를 바로 알고는 싶지만, 그 전에 closure, closure와의 순환 참조를 통해서 ARC 기초를 마무리하도록 하겠습니다! 클로저는 Reference type 클로저는 reference type이라는 걸 알고 계셨나요? 이전 포스팅에서도 말했듯, reference type의 경우 “참조”가 가능하고, reference count를 증가시키는 것 또한 가능합니다. 그렇다면, closure와 “누군가”도 서로 참조를 일으키는 “순환 참조”..
https://yudonlee.tistory.com/37
You don’t (always) need [weak self]
We will talk about weak self inside of Swift closures to avoid retain cycles & explore cases where it may not be necessary to capture self weakly.
https://medium.com/@almalehdev/you-dont-always-need-weak-self-a778bec505ef
  • capture list
Using Capture Lists in Swift
Are you closures capturing values?
https://medium.com/swlh/using-capture-lists-in-swift-19f408f986d


Uploaded by N2T

'Swift' 카테고리의 다른 글

[Swift] 정규표현식  (0) 2024.07.19
[Swift] Property wrapper  (0) 2023.02.06
에러 처리  (0) 2022.10.24
Swift - 동기와 비동기(sync, async), 직렬과 동시(serial, concurrent)  (0) 2022.07.21
Swift - 옵셔널 (Optional)  (0) 2022.07.19