-
(Swift) 메모리 참조 속성 (strong, week, unowned, optional unowned)iOS💖 2024. 10. 16. 16:06
Apple Documentation 을 보고 정리하였음
1. strong 참조
- default reference type 이다.
- var / let 앞에 weak, unowned 가 붙지 않으면 strong reference type 임!!
- instance 를 확실히 유지 & strong 참조가 유지되는 한, 할당 해제를 허용하지 않음
- == "강력한 (strong)" 참조
- ARC 에서 메모리 할당을 해제하지 않는 경우
- rc가 0이 되지 않는 상태, 즉, 활성되고 있는 참조가 있는 경우
- strong 참조 사이클이 생성된 경우
1) rc (reference counting)
: rc가 0이 되면, 객체의 메모리 해제class Person { let name: String init(name: String) { self.name = name print("\(name) is being initialized") } deinit { print("\(name) is being deinitialized") } } // Person? : 이러한 변수는 선택적 유형(Person?, Person이 아님)이므로 자동으로 nil 값으로 초기화되고 현재는 Person 인스턴스를 참조하지 않음 var reference1: Person? var reference2: Person? var reference3: Person? reference1 = Person(name: "John Appleseed") // rc 1개 // Prints "John Appleseed is being initialized" // 같은 Person 인스턴스를 두 개 의 변수에 더 할당 == rc+2 reference2 = reference1 // rc 2개 reference3 = reference1 // rc 3개 reference1 = nil // rc 2개 remains (reference2, reference3) reference2 = nil // rc 1개 remains (reference3) reference3 = nil // rc 0개 == 마지막 strong 참조가 끊어질 때 까지, Person 인스턴스 할당 해제 하지 않음 // Prints "John Appleseed is being deinitialized"2) 두 객체의 상호 참조
: 두 객체가 서로를 참조하는 경우가 strong 참조이다.- 두 객체의 상호 참조시, strong 참조 사이클이 생성됨
- strong 참조 사이클 (strong reference cycle)
: 두 class instance가 서로에 대한 strong 참조를 보유하고, 각 instance 가 참조하고 있는 instance 를 계속 살아있게 하는 경우- ⭐️이 경우, 두 instance 에 nil을 할당해도, rc가 0이 되지 않고, 메모리 할당이 해제 되지 않음⭐️
- 두 instance 의 메모리 할당은 유지되며, 끊어질 수 없음
- ➡️ 앱에서 메모리 누수를 발생 시킴
- strong 참조 사이클 해결 방법
- class 간의 관계 중 일부를 strong 참조 대신 weak 참조 / unowned 참조로 정의해야 한다!
- weak 참조
- 다른 instance 보다 lifetime이 짧은(= 메모리 할당 해제 될 수 있는) 경우 사용
- 아래의 코드 개선
- Apartment의 lifetime 어느 지점에서 tenant(세입자)가 없는 것이 적절하므로,
tenant를 weak 참조로 갖고, 해당 instance 의 메모리 할당을 해제 하는것이 적절하다.
- Apartment의 lifetime 어느 지점에서 tenant(세입자)가 없는 것이 적절하므로,
- unowend 참조
- 다른 instance 와 lifetime 이 같거나, 더 긴 경우 사용
- weak 참조
- class 간의 관계 중 일부를 strong 참조 대신 weak 참조 / unowned 참조로 정의해야 한다!
아래는 strong 참조 사이클이 발생하는 코드
class Person { let name: String init(name: String) { self.name = name } var apartment: Apartment? deinit { print("\(name) is being deinitialized") } } class Apartment { let unit: String init(unit: String) { self.unit = unit } var tenant: Person? deinit { print("Apartment \(unit) is being deinitialized") } } // 🔴 1 var john: Person? var unit4A: Apartment? john = Person(name: "John Appleseed") unit4A = Apartment(unit: "4A") // 🔴 2 // linking these two instances(john,unit4A) creates a strong reference cycle between them. john!.apartment = unit4A unit4A!.tenant = john // 🔴 3 // The strong reference cycle 는 nil 이 되어도 메모리 할당이 끊어지지 않음 john = nil unit4A = nil
🔴 1

🔴 2 _ strong reference cycle 생성됨

🔴 3 _ strong reference cycle 이 생성되었으므로, 메모리 할당 끊기지 않음
2. weak 참조
- 프로퍼티 or 변수 앞에 선언
- ARC의 참조된 인스턴스를 삭제하는 행위를 막지 못한다.
- ➡️ weak 참조가 참조하는 동안, 해당 instance의 메모리 할당 해제 될 수 있음!
- ➡️ ARC에 의해 메모리 할당 해제된 weak 참조는, ARC에 의해 자동으로 nil 로 설정됨
- runtime에도 weak 참조가 nil로 변경 될 수 있어야 하므로,
- weak 참조는, optional type 이다!
위의 strong 참조 사이클이 발생된 코드를, 발생하지 않도록 개선한 코드
Apartment의 lifetime 어느 지점에서 tenant(세입자)가 없는 것이 적절하므로,
tenant를 weak 참조로 갖고, 해당 instance 의 메모리 할당을 해제 하는것이 적절하다.: weak var tenant: Person?
class Person { let name: String init(name: String) { self.name = name } var apartment: Apartment? deinit { print("\(name) is being deinitialized") } } class Apartment { let unit: String init(unit: String) { self.unit = unit } weak var tenant: Person? // ⭐️⭐️ deinit { print("Apartment \(unit) is being deinitialized") } } var john: Person? var unit4A: Apartment? john = Person(name: "John Appleseed") unit4A = Apartment(unit: "4A") // 🔴 1 // ⭐️strong 참조때와 같이, 두 인스턴스 (john, unit4A) 간에 string 참조는 이전과 같이 생성된다.⭐️ john!.apartment = unit4A unit4A!.tenant = john // 🔴 2 john = nil // Prints "John Appleseed is being deinitialized" // >> Person 인스턴스에 대한 강력한 참조가 더이상 없으므로, 해당 인스턴스(john)의 할당이 해제되면, // unit4A!.tenant은 nil 이되고, // unit4A 은 여전히 strong 참조가 남는다. // 🔴 3 unit4A = nil // 이로써, unit4A의 strong 참조도 끊겼다.
🔴 1

🔴 2 _ unit4A!.tenant은 nil, unit4A 은 여전히 strong 참조

🔴 3 _ unit4A이 nil이 되면서, 결과적으로 strong 참조도 해제
3. unowned 참조
- lifetime이 다른 instance와 같거나, 더 길때 사용
- properties or 변수 앞에 선언
- weak 참조와 다른 점
- unowned 참조는, 항상 값을 갖을 것을 전제로 한다.
- ➡️ unowned 참조는, '항상' 참조가 할당 해제되지 않는 instance를 참조할 때만 사용한다.
- 그렇지 않은 경우, runtime error 발생 ‼️
- optional type 이 아니기 때문에, ARC는 값을 nil로 설정하지 X
unowned 참조를 safe 하게 사용하는 예시
고객은 신용카드를 가지고 있을 수도 / 않을 수도 있지만, 신용카드는 - 항상 고객과 연결되어있다.
- CreditCard instance 는 그것이 참조하는 Customer 보다 lifetime이 더 오래 지속되지 않는다.
- Customer 의 card attribute는 optional type (신용카드가 있을 수 도, 없을 수 도)
- CreditCard 의 customer는 non-optional type
- 새로운 CreditCard instance는, number 값, cutomer instance를 CreditCard 생성자에 전달하여야먄 생성 가능!
- CreditCard 에는 항상 cutomer이 있어야 존재하므로, 항상 값을 갖는 것을 전제로 하고 & Customer이 있어야 CreditCard도 있으니 즉 항상 참조가 끊기지 않는!! unowned 참조로 선언하였음
class Customer { let name: String var card: CreditCard? init(name: String) { self.name = name } deinit { print("\(name) is being deinitialized") } } class CreditCard { let number: UInt64 unowned let customer: Customer // ⭐️⭐️⭐️ init(number: UInt64, customer: Customer) { self.number = number self.customer = customer } deinit { print("Card #\(number) is being deinitialized") } } var john: Customer? // 초기값 nil // 🔴 1 john = Customer(name: "John Appleseed") john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!) // 🔴 2 john = nil // Prints "John Appleseed is being deinitialized" // Prints "Card #1234567890123456 is being deinitialized"* UInt64 사용 이유
: number 프로퍼티의 용량이, 32비트 및 64비트 시스템 모두에서 16자리 카드 번호를 저장할 만큼 충분히 커지도록 한다.

🔴 1 _ 두 instance 연결 (john의 strong 참조를 끊으면, CreditCard에 대한 strong 참조가 없으니 할당 해제됨

🔴 2
optional type 이 아닌 unowned 참조를 optional 하게 할 수 있는데..
(+) unowned optional 참조
- 즉, optional 참조를 unowned로 표시할 수 있는데,
- 항상 valid (유효한) 객체를 참조하거나
- nil 로 설정해야한다
- ARC 소유권 모델의 관점에서는, unowned optional referece 와 weak reference 는 동일한 context에서 사용될 수 있다.
- unowned 참조 (= non-optional unowned 참조) 와 동일하게 동작하지만, nil 이 될 수 있음
- == ARC가 instance 메모리 할당 해제 할 수 있음
- == 위험성 있고, 메모리 할당 해제시 값이 nil 이 될 수 있으므로, optional type
unowned optional reference 사용 예시
- 모든 과목(Course)은 어떤 학과(Department)의 일부이므로 학과 속성은 optional type이 아니다.
- But, 일부 과목에는 권장 후속 과목이 없기 때문에 nextCourse 속성은 optional type
- 학과는 학과가 제공하는 각 과목에 대한 strong 참조를 유지
- Course 입장에서 Department 가 존재해야 존재할 수 있으니, optional type 이 아닌, 항상 값을 갖고있는 것을 전제로 하는, 항상 참조가 있는 unowned 참조를 한다.
- non-optional unowned 참조와 동일하게, nextCourse 가 '항상' 메모리 할당 해제 되지 않은 course 를 참조하도록 하는 것은 개발자의 책임이다. (= 🔺course 를 삭제 시, 해당 instance를 참조하는 내용도 모두 삭제!🔺)
class Department { var name: String var courses: [Course] init(name: String) { self.name = name self.courses = [] } } class Course { var name: String unowned var department: Department unowned var nextCourse: Course? init(name: String, in department: Department) { self.name = name self.department = department self.nextCourse = nil } } let department = Department(name: "Horticulture") let intro = Course(name: "Survey of Plants", in: department) let intermediate = Course(name: "Growing Common Herbs", in: department) let advanced = Course(name: "Caring for Tropical Plants", in: department) intro.nextCourse = intermediate intermediate.nextCourse = advanced department.courses = [intro, intermediate, advanced]
위의 코드를 그림으로 표현 ARC > strong, weak, unowned reference
Documentation
docs.swift.org
ARC 정리글
https://hortenssiaa.tistory.com/37
(Swift) ARC (Automatic Reference Counting, 자동 참조 계산)
Apple Documentation 을 보고 정리하였습니다. ARC (Automatic Reference Counting, 자동 참조 계산)앱의 메모리 사용량을 추적, 관리class instance 가 더 이상 필요하지 않을 때, 해당 instance 에서 사용하는 메
hortenssiaa.tistory.com
'iOS💖' 카테고리의 다른 글
(Swift) closure @escaping (1) 2024.10.16 (Swift) the concept of capturing (capturing 개념) (2) 2024.10.16 (Swift) ARC (Automatic Reference Counting, 자동 참조 계산) (1) 2024.10.15 (Swift) Result<Success, Failure> 타입 (1) 2024.10.14 (Swift) @frozen (3) 2024.10.14 - default reference type 이다.