본문 바로가기

swift

[swift] ARC | 강한참조 (Strong)

강한참조 (Strong)

 

인스턴스가 계속해서 메모리에 남아있어야 할 명분을 만들어 주는 것

인스턴스는 참조 횟수가 0이 되는 순간 메모리에서 해제되는데, 인스턴스를 다른 인스턴스의 프로퍼티나 변수, 상수 등에 할당할 때 강한참조를 사용하면 참조 횟수가 1 증가한다. 

또한 강한참조를 사용하는 프로퍼티, 변수, 상수 등에 nil을 할당해주면 참조 횟수가 1 감소한다.

 

참조의 기본은 강한참조이므로 클래스 타입의 프로퍼티, 변수, 상수 등을 선언할 때 별도의 식별자를 명시하지 않으면 강한참조이다.

class Person {
    let name: String
    
    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

var ref1: Person?
var ref2: Person?
var ref3: Person?

ref1 = Person(name: "domisol") // 참조 횟수 1
// "domisol is being initialized"

ref2 = ref1 // 참조 횟수 2
ref3 = ref1 // 참조 횟수 3

ref3 = nil // 참조 횟수 2
ref2 = nil // 참조 횟수 1
ref1 = nil // 참조 횟수 0
print(ref1) // "nil"
// "domisol is being deinitialized"

ref1 에 할당된 Person 클래스 타입의 인스턴스는 처음 메모리에 생성된 후 강한참조로 할당되기 때문에 참조 횟수가 1 증가한다.

그 후 2, 3에도 할당되며 참조 횟수가 1씩 증가한다. 하나의 인스턴스가 세 개의 변수에 강한참조로 참조하고 있는 것이므로, 계속 메모리에 살아있을 명분이 충분함!

참조를 그만 둘 때마다 1씩 감소한다. 참조 횟수가 0이 되는 순간 인스턴스는 ARC의 규칙에 의해 메모리에서 해제되며, 메모리에서 해제되기 직전에 디이니셜라이저를 호출한다.

 

 

class Person {
    let name: String
    
    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

func foo() {
    let domisol: Person = Person(name: "domisol") // 참조 횟수 1
    // "domisol is ~ "
    
    // 함수 종료 시점
    // 참조 횟수 0 -> "~~ deinit"
}

foo()

foo() 라는 함수 내부에 domisol 이라 정의한 강한참조 상수가 있다.

Person 타입의 인스턴스는 이니셜라이저에 의해 생성된 후 domisol 상수에 할당할 때 참조 횟수가 1이 된다.

그리고 강한참조 지역변수(상수)가 사용된 범위의 코드 실행이 종료되면 그 지역변수(상수)가 참조하던 인스턴스의 참조 횟수가 1 감소한다.

그래서 함수가 종료되며 참조 횟수가 1 감소하여 0이 된다. 그 순간 인스턴스는 메모리에서 해제된다.

 

 

var globalRef: Person? // 전역변수 선언

class Person {
    let name: String
    
    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

func foo() {
    let domisol: Person = Person(name: "domisol") // 참조 횟수 1
    // "domisol is ~ "
    
    globalRef = domisol // 참조 횟수 2
    
    // 함수 종료 시점
    // 참조 횟수 1 -> 메모리에서 해제되지 않음
}

foo()

 

인스턴스가 강한참조를 하는 전역변수 globalRef 에 강한참조되면서 참조 횟수가 1 증가하여 2가 된다.

그 상태에서는 함수가 종료되며 1 감소되어도 여전히 참조 횟수가 1이므로 메모리에서 해제되지 않는다

 

 

강한참조 순환 문제

인스턴스끼리 서로가 서로를 강한참조 할 때, 강한참조의 규칙을 모르고 사용하게 되면 문제가 발생할 수 있다.

class Person {
    let name: String
    
    init(name: String) {
        self.name = name
    }
    
    var room: Room?
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

class Room {
    let num: String
    
    init(num: String) {
        self.num = num
    }
    
    var host: Person?
    
    deinit {
        print("Room \(num) is being deinitialized")
    }
}

var domisol: Person? = Person(name: "domisol") // Person 참조 횟수 1
var room: Room? = Room(num: "606") // Room 참조 횟수 1

room?.host = domisol // Person 참조 횟수 2
domisol?.room = room // Room 참조 횟수 2

domisol = nil // Person 참조 횟수 1
room = nil // Room 참조 횟수 1

이 코드를 실행하면 디이니셜라이저가 호출되지 않는다.

최종적으로 domisol과 room에 nil을 할당해주고 나면, 각 인스턴스의 참조 횟수는 1이 된다.

하지만 이제 domisol이 참조하던 Person 클래스의 인스턴스에 접근할 방법도, room이 참조하던 Room 클래스의 인스턴스에 접근할 방법도 없어졌다. ㅜㅜ

참조 횟수가 0이 되지 않는 한 ARC의 규칙대로라면 인스턴스를 메모리에서 해제시키지 않기 때문에 이렇게 두 인스턴스 모두 참조 횟수 1을 남겨둔 채 메모리에 남아 있게 된다. ➡️ 메모리 누수

이렇게 두 인스턴스가 서로를 참조하는 상황에서 강한참조 순환 문제가 발생할 수 있다.

 

var domisol: Person? = Person(name: "domisol") // Person 참조 횟수 1
var room: Room? = Room(num: "606") // Room 참조 횟수 1

room?.host = domisol // Person 참조 횟수 2
domisol?.room = room // Room 참조 횟수 2

room?.host = nil // Person 참조 횟수 1
domisol?.room = nil // Room 참조 횟수 1

domisol = nil // Person 참조 횟수 0
// " ~~ deinit "
room = nil // Room 참조 횟수 0
// " ~~ deinit "

이렇게 하나씩 변수 또는 프로퍼티에 nil 을 할당하면, 참조 횟수가 감소하기 때문에 인스턴스를 메모리에서 해제시킬 수 있다.

하지만 실수로 이 코드를 빼먹거나, 해제해야 할 프로퍼티가 너무 많다면? 

 

약한참조(weak) 또는 미소유참조(unowned) 를 사용하면 됩니다~ 

 

 

(unowned는 swift 5.0 이후로 변경사항이 있기 때문에 일단 weak 위주로 공부해보기 .. )

 

 

참고 : 한빛미디어 swift 프로그래밍 2판

'swift' 카테고리의 다른 글

[swift] 구조체 (struct)  (0) 2020.06.22
[swift] ARC | 약한참조 (weak)  (0) 2020.06.11
[swift] ARC란?  (0) 2020.06.11
[swift] 데이터 타입 | Any, AnyObject  (0) 2020.06.08
[swift] 스위프트의 언어적 특성 2. 함수형  (0) 2020.06.08