약한참조, 강한참조 (weak, strong)

RxSwift를 공부하고 있을때 순환 참조를 막기 위해 [weak self]를 사용습니다. 이때 weak 가 정확히 무슨뜻인지 몰라 찾아보았습니다.

[weak self] -> weak ->순환참조-> ARC 이런순으로 꼬리를 물다보니 ARC부터 공부를 했습니다.

 

ARC란?

ARC란 메모리 자동 관리를 해주는 장치를 말합니다. 앱이 실행될때 쓰이지 않는 객체들을 메모리에서 제거해줘야지 앱이 효율적으로 실행이 됩니다. 반면 변수에 할당이 되어 쓰이는 객체들은 제거를 하면 안되겠죠? 그래서 ARC가 해당 객체가 쓰이는지, 안쓰이는지를 자동으로 관리해줍니다.

코드로 한번 설명해보죠.

class Fruit{
    var name : String?
    var price : Int?
    init(name : String , price : Int){
        self.name = name
        self.price = price
    }
}
var banana : Fruit?
banana = Fruit(name: "banana", price: 1000)

var banana2 = banana
var banana3 = banana

class타입인 Fruit를 banana라는 변수에 할당해주었습니다. 그리고 banana2,3은 banana변수를 참조하고 있습니다.

이런식으로 현재 참조가 되있는거죠. 여기서 banana를 nil로 한다고 해봅시다. 그러면 Fruit는 사라질까요?

아닙니다. banana2,3이 아직 참조하고 있기에 사라지지 않습니다. banana2,3도 nil로 하면 Fruit는 메모리상에서 사라집니다. 그렇다면 어떻게 사라져야 된다는것을 알 수 있을까요? 여기서 ARC가 자동으로 관리를 해줍니다.

 

ARC는 컴파일 단계에서 해당 객체가 얼마나 참조를 하는지 세서 만약 참조한 개수가 0이면 메모리에서 지웁니다. 

class Fruit{
	//var refCount = 0
    var name : String?
    var price : Int?
    init(name : String , price : Int){
        self.name = name
        self.price = price
    }
}
var banana : Fruit?
banana = Fruit(name: "banana", price: 1000)
//refCount += 1
var banana2 = banana
//refCount += 1
var banana3 = banana
//refCount += 1

banana3 = nil
//refCount -= 1
banana = nil
//refCount -= 1
banana2 = nil
//refCount -= 1
//Fruit는 메모리에서 삭제

위에 코드처럼 참조가 될때와 nil로 참조가 끊어질때를 계산을 해서 메모리를 자동으로 관리를 해줍니다. 이게 ARC가 하는 일입니다.

그런데 여기서 문제가 발생합니다. 서로 맞물려 있어서 메모리에서 소멸이 안될 경우입니다.

class Fruit{
	//fruitRefCount = 0
    var name : String?
    var price : Int?
    var bucket : FruitBucket?
    init(name : String , price : Int){
        self.name = name
        self.price = price
    }
}

class FruitBucket{
	//bucketRefCount = 0	
    var name : String?
    var fruit : Fruit?
    init(name : String){
        self.name = name
    }
}

var lemon = Fruit(name: "lemon", price: 200)
//fruitRefCount += 1 (1)
var fruitBucket = FruitBucket(name: "bucket")
//bucketRefCount += 1 (1)
lemon.bucket = fruitBucket
//bucketRefCount += 1 (2)
fruitBucket.fruit = lemon
//fruitRefCount += 1 (2)

 

그림처럼 서로 참조가 되어있는 상태입니다. 여기서 lemon과 fruit를 nil로 참조를 끊는다고 해봅시다.

 

nil로 참조를 끊었지만, Fruit와FruitBucket은 서로 참조를 하고 있습니다. 그래서 각각 count가 1씩 남아 있는 상태라 메모리에 아직 남아있는 상태가 되어버립니다. 이런게 계속해서 쌓이면 메모리 낭비가 심해지겠죠. 따라서 이런 순환참조를 막기위해 weak를 사용합니다.

 

weak

weak란 약한 참조를 뜻합니다. 참조를 해도 count가 올라가지 않습니다. 

class Fruit{
	//fruitRefCount = 0
    var name : String?
    var price : Int?
    var bucket : FruitBucket?
    init(name : String , price : Int){
        self.name = name
        self.price = price
    }
}

class FruitBucket{
	//bucketRefCount = 0	
    var name : String?
    weak var fruit : Fruit?
    init(name : String){
        self.name = name
    }
}

var lemon = Fruit(name: "lemon", price: 200)
//fruitRefCount += 1 (1)
var fruitBucket = FruitBucket(name: "bucket")
//bucketRefCount += 1 (1)
lemon.bucket = fruitBucket
//bucketRefCount += 1 (2)
fruitBucket.fruit = lemon
//weak이므로 count가 안올라감 fruitRefCount == 1 (1)

여기서 이제 lemon을 nil로 한다고 해보겠습니다.

lemon = nil
//fruitRefCount는 1이므로 nil이 되면 0이 되므로 메모리에서 제거됨

또 기존 Bucket을 참조 하고 있던 fruit가 nil이 되서 fruit 내부의 bucket역시 nil이 되었겠죠? bucket의 count가 1줄어 듭니다. 그러면 Bucket의 count가 1이 되고 여기서 fruitBucket을 nil로 참조를 끊으면 Bucket은 count가 0이 되면서 Bucket 객체 역시 메모리에서 제거됩니다.

fruitBucket = nil

여기서 weak와 반대되는 strong이 있는데, weak가 count를 올리지않고 참조 한다면 strong은 count를 올립니다. 변수를 선언할때 아무것도 적어주지 않으면 strong으로 선언된것으로 defualt 되어 있습니다. 그 외에서 weak와 비슷한 unowned도 있는데 이건 나중에 하기로 하고.. 다음에너느 [weak self]에 대해 알아보도록 하죠. 왜 배열 내부에다가 선언을 했는지도 궁금하고 정확히 하는게 뭔지도 궁금하네요.