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 |
Tags
- composablearchitecture
- Tuist
- ios18
- regex
- navigationsplitview
- 개발
- swiftdata
- uikit
- xcodecloud
- xcode
- Navigation
- concurrency
- tuist #xcodecloud #ios #ci/cd #swiftlint #firebase
- Alamofire
- SWIFTUI
- Git
- 모바일
- test
- github
- network
- iOS 개발자
- combine
- UI
- 정규표현식
- Firebase
- TCA
- IOS
- ObjC
- iOS 13.0+
- SWIFT
Archives
- Today
- Total
iOS 개발 기록
Swift 의 메모리 관리 (ARC, weak, unowned, lazy) 본문
728x90
Swift의 메모리 관리
- ARC(Automatic Reference Counting) : ARC가 관리해주는 Reference Counting (참조 횟수 계산) 은 참조 타입인 클래스의 인스턴스에만 적용된다. 구조체나 열거형은 값 타입으로 다른 곳에서 참조하지 않기 때문에 ARC로 관리할 필요가 없다.
ARC의 로직
- 생성 : 런타임 때 동적할당 되는 모든 Object를 Swift Runtime이라는 라이브러리의 HeapObject라는 struct로 표현되어 관리된다. 여기에는 Reference Count와 type meta data를 포함한다.
- 해제 : ARC 는 인스턴스가 참조하는 다른 프로퍼티의 변수, 상수의 갯수를 세고 1개 이상 존재하면 인스턴스를 메모리에서 없애지 않는다.
- 카운팅 시점 : 컴파일시 카운팅. default는 강한 참조 형태로 카운팅하게 된다. 그러나 복합적으로 강한 참조가 나타나는 상황이면 강한참조 순환 문제가 나타날 수 있음.
// 강한 참조 순환 문제 예
class Hop {
let hopName: String
var beerName: Beer?
init(hopName: String) {
self.hopName = hopName
}
deinit {
print("Hop: deinit")
}
}
class Beer {
let beerName: String
var hopName: Hop?
init(beerName: String){
self.beerName = beerName
}
deinit {
print("beer: deinit")
}
}
var hop: Hop? = Hop(hopName: "citra") // Hop reference count : 1
var beer: Beer? = Beer(beerName: "PseudoSue") // Beer reference count : 1
hop?.beerName = beer // Hop reference count : 2
beer?.hopName = hop // Beer reference count : 2
hop = nil // Hop reference count : 1
beer = nil // Beer reference count : 1
// 즉, 메모리에 reference count가 남아있는 상태로 deinit이 출력되지 않는다.
강한 참조 순환 문제를 막기 위해 weak와 unowned을 사용한다.
weak, unowned
약한 참조 (weak)
- 변수 앞에 weak 키워드를 작성해 사용. 항상 옵셔널 변수에만 사용할 수 있고 상수에는 사용할 수 없다.
- weak는 참조하는 인스턴스의 reference count를 추가하지 않는다.
- ARC는 인스턴스가 메모리에서 해제될 때 약한 참조하는 프로퍼티의 reference count를 nil로 초기화한다.
- 때문에 참조되고 있는 동안에도 해당 인스턴스가 할당해제 될 수 있다.
class Hop {
let hopName: String
var beerName: Beer?
init(hopName: String) {
self.hopName = hopName
}
deinit {
print("Hop: deinit")
}
}
class Beer {
let beerName: String
weak var hopName: Hop? // weak변수로 선언
init(beerName: String){
self.beerName = beerName
}
deinit {
print("beer: deinit")
}
}
var hop: Hop? = Hop(hopName: "citra")
var beer: Beer? = Beer(beerName: "PseudoSue")
hop?.beerName = beer
beer?.hopName = hop
hop = nil
beer = nil
// Hop: deinit 출력
// beer: deinit 출력
미소유 참조 (unowned)
- 항상 값을 가지고 있어야 한다고 가정한다. 때문에 옵셔널이 아니며 ARC도 무소유 참조로 선언된 값을 nil로 만들지 않는다
- 다른 인스턴스와 생명주기가 같거나 더 긴 경우에 사용.
- 할당 해제되지 않은 인스턴스를 참조한다고 확신하는 경우에만 무소유 참조를 사용(할당 해제된 값에 접근하려고 하면 런타임 오류 발생)
class Hop {
let hopName: String
var beerName: Beer?
init(hopName: String) {
self.hopName = hopName
}
deinit {
print("Hop: deinit")
}
}
class Beer {
let beerName: String
unowned var hopName: Hop
init(beerName: String, hopName: Hop){
self.beerName = beerName
self.hopName = hopName
}
deinit {
print("beer: deinit")
}
}
var hop: Hop = Hop(hopName: "citra")
var beer: Beer? = Beer(beerName: "PseudoSue", hopName: hop)
beer?.hopName = hop
beer = nil
// beer: deinit 출력
lazy
정의 :
지연 저장 프로퍼티. lazy 프로퍼티는 처음 사용되기 전까지 연산되지 않는다. 대표적인 예로 인스타 스토리 등을 떠올려보라.
사용
- lazy 프로퍼티는 항상 결과를 저장한다. 연산 프로퍼티는 매번 계산하므로 반복적으로 사용할 때 lazy 프로퍼티가 유리하다.
- let으로 사용할 수 없다. 반드시 var와 사용되어야 한다.
- 기본적으로 struct와 class에서만 사용할 수 있다.
- lazy에 값을 넣어주려면 클로저를 사용해야 한다. class나 struct의 다른 프로퍼티의 값을 넣기 위해서는 self를 통해 접근 가능하다.
class Beer {
var hop: String
lazy var likeHop: String = {
return "I like \(self.hop)"
}()
init(hop: String) {
self.hop = hop
}
}
var beer = Beer(hop:"galaxy")
print(beer.likeHop) // I like galaxy
beer.hop = "nelson sauvin"
print(beer.likeHop) // I like galaxy
다음의 경우 likeHop이 클로저를 실행하고 ()로 결과를 바로 돌려주며 끝내버리기 때문에 메모리는 실행 결과를 담게 된다. 때문에 beer.hop을 변경해도 처음 likeHop의 메모리에 "galaxy"가 올라갔기 때문에 그대로 "galaxy"가 출력된다.
때문의 lazy var likeHop: String을 lazy var likeHop: () -> String 으로 선언해 실행결과가 아니라 클로저 자체를 담을 수 있는 변수로 선언해야 한다. 또한 클로저 자체를 담고 있는 변수라면 반드시 [weak self] 로 메모리 누수를 방지해야 한다.
class Beer {
var hop: String
lazy var likeHop: () -> String = { [weak self] in
return "I like \((self?.hop)!)"
}
init(hop: String) {
self.hop = hop
}
}
var beer = Beer(hop:"galaxy")
print(beer.likeHop()) // I like galaxy
beer.hop = "nelson sauvin"
print(beer.likeHop()) // I like nelson sauvin
참고 : https://baked-corn.tistory.com/45
'Swift' 카테고리의 다른 글
Swift - 고차함수 (map, filter, reduce) (0) | 2022.07.18 |
---|---|
[iOS] PencilKit (0) | 2022.07.07 |
Swift의 특징과 프로그래밍 패러다임 (0) | 2022.05.31 |
KVC와 KVO란? (0) | 2022.05.31 |
[Swift]Combine - Publisher, Subscriber, NotificationCenter (0) | 2022.04.18 |