티스토리 뷰

반응형

안녕하세요 Pingu입니다.🐧

 

오늘은 Swift에서 중요한 ARC에 대해서 알아볼 건데요, ARC에 대한 자세한 내용은 여기를 참고해주세요.

ARC는 Automatic Reference Counting이라고 해서 클래스와 같은 참조 타입에서 사용되는 메모리 관리 방법인데요,

간단하게 말하면 클래스의 인스턴스가 있을 때 인스턴스를 참조하는 곳을 계속 카운트하다가 언젠가 0이 되면 인스턴스를 메모리에서 할당 해제하는 아이디어입니다.

 

이 말은 바꿔말하면 참조 카운트가 1 이상이라면 메모리에서 할당 해제되지 않는다는 것을 말합니다.

 

때문에 아무곳에서 사용하지 않고 있음에도 불구하고 메모리를 유지하는 문제가 발생할 수 있고 오늘은 이러한 참조 사이클이 발생하는 상황과 해결 방법을 알아보려고 합니다.

Strong Reference (강한 참조)

우선 ARC에서 참조 카운트를 증가시키는 것은 강한 참조라고 하는 녀석들입니다.

얘네들은 직접 카운트를 감소시켜주지 않으면 카운트가 내려가지 않습니다.

class Person {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    deinit {
        print("메모리에서 해제!")
    }
}
// Strong Reference - 강한 참조

var pingu: Person?
pingu = Person(name: "pingu", age: 5) // reference count = 1
pingu = nil // reference count = 0 -> 메모리에서 해제!

위와 같이 pingu라는 Person 클래스 인스턴스를 만들고 여기에 직접 nil을 넣어줘야 메모리에서 해제되어 deinit이 실행되게 되는 것이죠.

// Strong Reference - 강한 참조

var pingu: Person?
var pinga: Person?
var roby: Person?

pingu = Person(name: "pingu", age: 5) // reference count = 1
pinga = pingu // reference count = 2
roby = pingu // reference count = 3

pingu = nil // reference count = 2

만약 위와 같이 코드를 만들게 되면 아래와 같은 상황이 됩니다.

즉 제일 처음 인스턴스를 만든 pingu는 참조를 없애줬지만 pinga, roby가 여전히 해당 인스턴스를 참조하고 있기 때문에 인스턴스가 메모리에 존재하게 되는 것이죠. 이를 해제하기 위해서는 모든 참조를 해제해줘야 합니다.

var pingu: Person?
var pinga: Person?
var roby: Person?

pingu = Person(name: "pingu", age: 5) // reference count = 1
pinga = pingu // reference count = 2
roby = pingu // reference count = 3

pingu = nil // reference count = 2
pinga = nil // reference count = 1
roby = nil // reference count = 0 -> 메모리 해제!

Strong Reference Cycle (강한 참조 사이클)

그럼 이러한 강한 참조로 인해 발생하는 강한 참조 사이클을 살펴보겠습니다.

아래와 같은 코드가 강한 참조 사이클을 발생시킵니다.

class Room {
    var roomNumber: Int
    var customer: Customer?

    init(roomNumber: Int) {
        self.roomNumber = roomNumber
    }

    deinit {
        print("Room 인스턴스 메모리에서 해제")
    }
}

class Customer {
    var name: String
    var room: Room?

    init(name: String) {
        self.name = name
    }

    deinit {
        print("Customer 인스턴스 메모리에서 해제")
    }
}

var suiteRoom: Room?
var tom: Customer?

suiteRoom = Room(roomNumber: 1004) // Room RC = 1
tom = Customer(name: "tom") // Customer RC = 1

suiteRoom?.customer = tom // Customer RC = 2
tom?.room = suiteRoom // Room RC = 2

suiteRoom = nil // Room RC = 1
tom = nil // Customer RC = 1

위의 상황을 그림으로 나타내면 아래와 같아집니다.

그림에 적힌 대로 강한 참조가 유지되어 무의미한 데이터가 메모리에 유지되고 이로 인한 메모리 누수가 발생하게 됩니다.

Weak Reference

이를 해결하기 위해서는 weak reference를 사용하면 되는데요, weak reference는 참조 카운트를 증가시키지 않고 참조를 할 수 있도록 만들어줍니다. 그럼 방금 코드를 약한 참조를 사용하여 해결해보겠습니다.

class Room {
    var roomNumber: Int
    weak var customer: Customer? // 약한 참조로 선언

    init(roomNumber: Int) {
        self.roomNumber = roomNumber
    }

    deinit {
        print("Room 인스턴스 메모리에서 해제")
    }
}

class Customer {
    var name: String
    var room: Room?

    init(name: String) {
        self.name = name
    }

    deinit {
        print("Customer 인스턴스 메모리에서 해제")
    }
}

var suiteRoom: Room?
var tom: Customer?

suiteRoom = Room(roomNumber: 1004) // Room RC = 1
tom = Customer(name: "tom") // Customer RC = 1

suiteRoom?.customer = tom // Customer RC = 1
tom?.room = suiteRoom // Room RC = 2

suiteRoom = nil // Room RC = 1
tom = nil // Customer RC = 0 -> Customer, Room 인스턴스 메모리에서 해제!

아까와는 다르게 Room 인스턴스의 customer 변수를 약한 참조로 선언해줬습니다.

weak을 앞에 써주면 약한 참조로 사용할 수 있어요!

위의 코드를 그림으로 그리면 아래와 같이 됩니다.

저는 이 부분이 조금 헷갈렸었는데요, 번호를 붙여서 순서를 이해하니까 이해가 되더라고요.

 

약한 참조로 참조한 인스턴스가 사라지게 되면 해당 인스턴스의 값도 nil이 됩니다.

 

따라서 강한 참조 주기가 발생하지 않고 잘 해제될 수 있게 되는 것이죠.

Unowned Reference

강한 참조 사이클을 해결하기 위한 방법에는 unowned 참조도 있습니다.

약한 참조와 동일하게 참조 카운트를 올려주지 않는 unowned 참조인데요, 얘는 인스턴스의 수명이 동일하거나 더 길 때 사용한다고 합니다. 즉 메모리에서 해제가 되지 않는다는 확신이 있을 때 쓴다고 하는데요, 예를 보며 이해해보겠습니다.

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit {
        print("Customer 인스턴스 메모리 해제!")
    }
}
class CreditCard {
    let cardCompany: String
    unowned let customer: Customer
    init(cardCompany: String, customer: Customer) {
        self.cardCompany = cardCompany
        self.customer = customer
    }
    deinit {
        print("CreditCard 인스턴스 메모리 해제!")
    }
}

var pingu: Customer?
pingu = Customer(name: "Pingu") // Customer RC = 1
pingu?.card = CreditCard(cardCompany: "Korea", customer: pingu!) // CreditCard RC = 1
pingu = nil // Customer RC = 0 (메모리 해제), CreditCard RC = 0 (메모리 해제)

위의 코드를 그림으로 그리면 위와 같아지는데요, unowned 참조로도 아까와 같은 강한 참조 주기를 해결할 수 있습니다.

 

근데 weak 참조와 unowned 참조의 차이점은 뭘까요??

 

아까의 예를 다시 보며 이해해보겠습니다.

suiteRoom에는 customer가 없을 수도 있습니다.

 

즉 suiteRoom 인스턴스가 있는데 그 안에 있는 customer는 언제든지 생길 수도 사라질 수도 있는 것이죠.

이렇게 인스턴스가 사라지면 weak 참조한 인스턴스도 nil이 됩니다.

실행중에 nil이 될 수도 있는 weak 참조이기 때문에 옵셔널 변수로 선언해줘야합니다.

 

이렇게 자신보다 더 빨리 사라질 수도 있는 인스턴스에 대해서는 weak 참조를 사용하면 됩니다.

 

 

unowned의 경우를 살펴보면 고객(customer)이 없는 신용카드는 존재할 수 없겠죠?

하지만 customer의 경우 신용카드가 없을 수도 있습니다.

그리고 한 번 만들어진 신용카드의 경우 고객보다 먼저 사라질 거예요.

유효기간이 보통 몇 년이니... 아마도..?

 

즉 이렇게 자신보다 더 오래 유지될 수 있는 인스턴스에 대해서는 unowned 참조를 사용하면 됩니다.

또한 unowned 참조를 하는 값에는 nil이 들어갈 수 없다고 합니다.

즉 weak과 다르게 참조 해제를 해줘도 nil이 되지 않는다는 말이 됩니다.

 

따라서 만약 자신보다 덜 오래갈 인스턴스에 unowned 참조를 사용하게 된다면?

아래와 같은 문제가 발생할 수 있습니다.

 

아까와 같은 예에서 신용카드와 고객 클래스를 조금만 수정해보겠습니다.

이렇게 unowned 프로퍼티 값을 옵셔널로 만들고 (nil이 될 수 없는데 swift 5.0부터 옵셔널로도 선언 가능하도록 바뀌었습니다..?)

customer를 먼저 해제한 뒤 card 인스턴스로 해당 값에 접근하면 위와 같이 오류를 발생합니다.

즉 위와 같이 존재하지 않는 데이터를 가리키고 있기 때문에 오류가 발생하게 됩니다.

이러한 오류는 dangling 포인터라고 합니다. (운영체제에서 공부한 게 나오니 반갑네요)

 

따라서 자신보다 먼저 해제될 인스턴스에는 unowned를 사용하지 않는 것이 좋습니다.

 

오늘 알아본 참조들을 요약으로 정리하고 글을 마무리하도록 하겠습니다.

Strong Reference

Reference count를 1 증가시킴

참조 카운트 감소시키려면 직접 해제하여 감소해줘야 함

Strong Referece Cycle 발생시킬 수 있음

Weak Reference

Reference count에 영향을 주지 않음

Strong Referece Cycle 해결할 수 있음

자신보다 덜 오래갈 인스턴스에 사용

런타임 중에 변할 수 있기 때문에 변수로만 선언 가능

런타임 중 nil로 변할 수 있기 때문에 옵셔널로 선언되어야 함

Unowned Reference

Reference count에 영향을 주지 않음

Strong Referece Cycle 해결할 수 있음

자신보다 오래갈 인스턴스에 사용

nil이 되지 않음

 

감사합니다.

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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
글 보관함