메모리 관리 관점에서
strong과 weak를 사용하는 이유는?
weak를 사용하면 객체가 사라졌을 때 자동으로 메모리가 해제되기 때문이다class Child { weak var age = "17" } var john = Child() //이후에 john을 사용하지 않으면 age 변수도 메모리에서 해제된다
메모리 관리
힙 객체의 생명주기가 언제 끝나는 것인가
💡실행 결과
💡User John was initialized
User 객체가 범위를 벗어나지 않기 때문에 메모리에서 해제되지 않는다
view controller that contains this object never goes out of scope, the object is never removed from memory.
import UIKit
class MainViewController: UIViewController {
let user = User(name: "John")
override func viewDidLoad() {
super.viewDidLoad()
}
}
class User {
let name: String
init(name: String) {
self.name = name
print("User \(name) was initialized")
}
deinit {
print("Deallocation user named: \(name)")
}
}
💡실행 결과
💡User John was initialized
💡Deallocation user named: John
User객체를 메소드에 넣고 viewDidLoad()에서 호출하면
메소드 내부로 범위가 정해졌기 때문에
deallocation이 발생한다
import UIKit
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
runScenario()
}
func runScenario() {
let user = User(name: "John")
}
}
class User {
let name: String
init(name: String) {
self.name = name
print("User \(name) was initialized")
}
deinit {
print("Deallocation user named: \(name)")
}
}
init
deinit
variable1과 variable2가 더 이상 쓰이지 않고
object1과 object2이 더 이상 필요하지 않지만
object1과 object2가 strong하게 참조하기 때문에
ARC가 0이 될 수 없고 메모리 누수가 발생한다
weak var owner: User?
import UIKit
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
runScenario()
}
func runScenario() {
let user = User(name: "John")
let iPhone = Phone(model: "iPhone Xs")
user.add(phone: iPhone)
}
}
class User {
let name: String
private (set) var phones: [Phone] = []
func add(phone: Phone) {
phones.append(phone)
phone.owner = self
}
init(name: String) {
self.name = name
print("User \(name) was initialized")
}
deinit {
print("Deallocation user named: \(name)")
}
}
class Phone {
let model: String
weak var owner: User? // Phone이 User를 약한 참조한다
init(model: String) {
self.model = model
print("Phone \(model) was initialized")
}
deinit {
print("Deallocation phone named: \(model)")
}
}
결과
User John was initialized
Phone iPhone Xs was initialized
Deallocation user named: John
Deallocation phone named: iPhone Xs
weak
는 항상 옵셔널
이고, ARC에 의해 nil이 될 수 있어 var
과 함께 쓰여야 한다.
unowned
는 ARC에 의해 카운트가 증가하지 않는다. 절대 옵셔널이 될 수 없다. 만약 deinitialized된 객체에 접근하면 nil이 된 옵셔널 강제 추출하는 것에 상응해 런타임 에러가 발생된다.
unowned 사용 전
import UIKit
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
runScenario()
}
func runScenario() {
let user = User(name: "John")
let iPhone = Phone(model: "iPhone Xs")
user.add(phone: iPhone)
let subscription = CarrierSubscription(name: "TelBel", countryCode: "0032", number: "3145678", user: user)
iPhone.provision(carrierSubscription: subscription) // unowned
}
}
class User {
let name: String
var subscriptions: [CarrierSubscription] = [] // [unowned] CarrierSubscription의 배열 저장
private (set) var phones: [Phone] = []
func add(phone: Phone) {
phones.append(phone)
phone.owner = self
}
init(name: String) {
self.name = name
print("User \(name) was initialized")
}
deinit {
print("Deallocation user named: \(name)")
}
}
class Phone {
let model: String
weak var owner: User? // [weak] Phone이 User를 약한 참조한다 -> User가 없으면 Phone도 deallocate
var carrierSubscription: CarrierSubscription? // unowned
func provision(carrierSubscription: CarrierSubscription) { // unowned
self.carrierSubscription = carrierSubscription
}
func decommission() { // unowned
carrierSubscription = nil
}
init(model: String) {
self.model = model
print("Phone \(model) was initialized")
}
deinit {
print("Deallocation phone named: \(model)")
}
}
class CarrierSubscription {
let name: String
let countryCode: String
let number: String
let user: User // User 객체
init(name: String, countryCode: String, number: String, user: User) {
self.name = name
self.countryCode = countryCode
self.number = number
self.user = user
user.subscriptions.append(self) // [unowned] user객체의 subscriptions배열에 CarrierSubscription 추가
print("CarrierSubscription \(name) is initialized")
}
deinit {
print("Deallocating CarrierSubscription named: \(name)")
}
}
결과
User John was initialized
Phone iPhone Xs was initialized
CarrierSubscription TelBel is initialized
unowned 사용 후
unowned let user: User
import UIKit
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
runScenario()
}
func runScenario() {
let user = User(name: "John")
let iPhone = Phone(model: "iPhone Xs")
user.add(phone: iPhone)
let subscription = CarrierSubscription(name: "TelBel", countryCode: "0032", number: "3145678", user: user)
iPhone.provision(carrierSubscription: subscription) // unowned
}
}
class User {
let name: String
var subscriptions: [CarrierSubscription] = [] // [unowned] CarrierSubscription의 배열 저장
private (set) var phones: [Phone] = []
func add(phone: Phone) {
phones.append(phone)
phone.owner = self
}
init(name: String) {
self.name = name
print("User \(name) was initialized")
}
deinit {
print("Deallocation user named: \(name)")
}
}
class Phone {
let model: String
weak var owner: User? // [weak] Phone이 User를 약한 참조한다 -> User가 없으면 Phone도 deallocate
var carrierSubscription: CarrierSubscription? // unowned
func provision(carrierSubscription: CarrierSubscription) { // unowned
self.carrierSubscription = carrierSubscription
}
func decommission() { // unowned
carrierSubscription = nil
}
init(model: String) {
self.model = model
print("Phone \(model) was initialized")
}
deinit {
print("Deallocation phone named: \(model)")
}
}
class CarrierSubscription {
let name: String
let countryCode: String
let number: String
unowned let user: User // [unowned] User 객체
init(name: String, countryCode: String, number: String, user: User) {
self.name = name
self.countryCode = countryCode
self.number = number
self.user = user
user.subscriptions.append(self) // [unowned] user객체의 subscriptions배열에 CarrierSubscription 추가
print("CarrierSubscription \(name) is initialized")
}
deinit {
print("Deallocating CarrierSubscription named: \(name)")
}
}
결과
User John was initialized
Phone iPhone Xs was initialized
CarrierSubscription TelBel is initialized
Deallocation user named: John
Deallocation phone named: iPhone Xs
Deallocating CarrierSubscription named: TelBel
객체가 클로저를 참조하고, 클로저가 self(객체)를 strong 참조한다
CarrierSubscription 객체의 내부
self.countryCode와 self.number는 CarrierSubscription이 초기화된 후에 사용할 수 있기 때문에 lazy 키워드를 써준다
lazy var completePhoneNumber: () -> String = {
self.countryCode + " " + self.number
}
print(subscription.completePhoneNumber)
함수를 실행하면 결과는 아래와 같다
CarrierSubscription이 메모리에서 해제되지 않는다.
User John was initialized
Phone iPhone Xs was initialized
CarrierSubscription TelBel is initialized
(Function)
Deallocation user named: John
Deallocation phone named: iPhone Xs
var x = 5
var y = 5
let someClosure = { [x] in
print("\(x), \(y)")
}
x = 6
y = 6
someClosure() // Prints 5, 6
print("\(x), \(y)") // Prints 6, 6
x는 closure capture list에 있어서 captured by value된다.
y는 capture list에 없어서 captured by reference된다.
y는 클로저 안에 있더라도 값이 변경될 수 있다.
weak나 unowned가 사용될 수 있는데
CarrierSubscription의 인스턴스가 사라지면 클로저가 없어질 수 있기 때문에 unowned
가 적절하다.
Capture lists come in handy for defining a
weak or unowned
relationship between objects used in a closure. In this case,unowned
is a good fit, since the closure cannot exist if the instance ofCarrierSubscription
has gone away.
self를 unowned reference로 capture한다.
새로운 self
변수를 생성하고, 원래 self의 의미를 shadows하고, 생성된 self
는 클로저 범위 안에서만 존재한다.
lazy var completePhoneNumber: () -> String = { [unowned self] in
return self.countryCode + " " + self.number
}
self와 completePhoneNumber는 unowned 관계이다.
만약 클로저에서 참조받는 객체(여기에서는 CarrierSubscription)가 deallocate하지 않으면 unowned를 사용할 수 있다. 하지만, deallocate가 발생하면 문제가 발생한다.
결과: CarrierSubscription도 해제되었다.
User John was initialized
Phone iPhone Xs was initialized
CarrierSubscription TelBel is initialized
Deallocation user named: John
Deallocation phone named: iPhone Xs
Deallocating CarrierSubscription named: TelBel
긴 버전: 클로저의 바깥 범위에서 self는 원래의 의미를 갖는다.
var closure = { [unowned newID = self] in
// Use unowned newID here...
}
newID
는 self
의 unowned
한 복사본이다.
Fatal error: Attempted to read an unowned reference but object 0x600000f0de30 was already deallocated2019-02-24 12:29:40.744248-0600 Cycles[33489:5926466] Fatal error: Attempted to read an unowned reference but object 0x600000f0de30 was already deallocated
클로저는 self.who
가 유효하길 바라지만 실행시키면서 mermaid
가 do
블록의 범위를 벗어났기 때문에 deallocated 되어 오류가 발생한다.
class WWDCGreeting {
let who: String
init(who: String) {
self.who = who
}
lazy var greetingMaker: () -> String = { [unowned self] in
return "Hello \(self.who)."
}
}
override func viewDidLoad() {
super.viewDidLoad()
runScenario()
}
func runScenario() {
let greetingMaker: () -> String
do {
let mermaid = WWDCGreeting(who: "caffeinated mermaid")
greetingMaker = mermaid.greetingMaker
}
print(greetingMaker()) //TRAP
}
weak로 변환하고 self는 guard 처리 해주기
lazy var greetingMaker: () -> String = { [weak self] in
guard let self = self else {
return "No greeting available."
}
return "Hello \(self.who)."
}
//결과
//Hello nil.
self
가 클로저 밖에서도 사는 것이 명확하지 않을 때,
[weak self]
andguard let self = self else { return }
구문을 사용해 객체의 생명주기를 연장시키는 것이[unowned self]
를 사용하는 것보다 낫다.
Contact와 Number가 서로 참조하고 있는 상황에서
Contact
는 Number
없이도 존재할 수 있지만
Number
는 Contact
없이 존재할 수 없다.
해결방안
1. Contact
와 Number
를 weak 관계로 바꾼다.
2. Number
가 Contact
에 의해 unowned
되도록 바꾼다.
컨벤션에 따르면 parent
객체가 child
객체를 strong하게 hold하는 것을 권장하기 때문에 Number가 Contact에 의해 unowned되도록 만들었다.
class Number {
unowned var contact: Contact
// Other code...
}
Self reference is not an issue for classes.
클래스에서 self 참조는 괜찮지만 Not deallocated된다.
class Person {
var name: String
var friends: [Person] = []
init(name: String) {
self.name = name
print("New person instance: \(name)")
}
deinit {
print("Person instance \(name) is being deallocated")
}
}
do {
let ernie = Person(name: "Ernie")
let bert = Person(name: "Bert")
ernie.friends.append(bert) // Not deallocated
bert.friends.append(ernie) // Not deallocated
}
해결책
friends배열이 unowned되도록 만든다
Make the friends array unowned
and Xcode will show an error: unowned
only applies to class types.
class Unowned<T: AnyObject> {
unowned var value: T
init (_ value: T) {
self.value = value
}
}
var friends: [Unowned<Person>] = []
do {
let ernie = Person(name: "Ernie")
let bert = Person(name: "Bert")
ernie.friends.append(Unowned(bert)) //deallocate
bert.friends.append(Unowned(ernie)) //deallocate
}
friends 배열은 더 이상 Person의 콜렉션이 아니다.
Person의 wrapper인 Unowned객체의 콜렉션이라서 아래와 같이 접근한다.
To access the Person object within Unowned, use the value property, like so:
let firstFriend = bert.friends.first?.value // get ernie
출처
https://www.raywenderlich.com/966538-arc-and-memory-management-in-swift
https://velog.io/@msi753/unowned-self