본문 바로가기
Swift

Memory Leak Case When ReferenceType in Dictionary

by SeoB-P 2022. 5. 11.

알다시피 Swift에서 Dictionary는 Struct이고 Value Type이다.

그렇다면, 이 Dictionary에 ReferenceType인 Class를 Value나 Key로 넣으면 어떻게 될까?

 

예시를 통해 알아보자.

name이라는 Property를 가지고 있는 Class A와 B가 있다.

class A {
    var name: String = "Park"

    deinit {
      print("A deinit!")
    }
}

class B {
    var name: String = "Kim"

    deinit {
       print("B deinit!")
    }
}

 

Class AB는 String을 Key값으로 String, AnyObject(Class)를 Value로 넣을 수 있는 Dictionary를 가지고 있다.

class AB {
    var dic = [String: AnyObject]()

    func regist<T>(key: String , value: T) {
        let key = key
        dic[key] = value as AnyObject
    }

    func remove(key:String) {
        dic[key] = nil
    }
}

let ab = AB()
var a: A? = A()
var b: B? = B()

ab.regist(key:"A", value: a)
ab.regist(key:"B", value: b)

인스턴스 a,b와 ab dictionary안에 있는 value를 보면 같은 메모리 주소를 공유함을 알 수있고 값이 복사가 되는게 아니라 참조값이 들어감을 확인 할 수 있었다.

 

문제는 이제 더이상 a와 b의 인스턴스가 필요없을때 발생한다.

여기서 이제 a와 b가 더이상 필요가 없어져서 nil을 넣으면 a와 b는 deinit이 될까?

 

a = nil
b = nil

 

놉!.

왜냐하면, dictionary에 regist하는 순간 ARC에 의해 인스턴스 a와 b는  참조카운트가 1더 증가한다. 

따라서, a와 b의 참조카운트는 2이므로 a와 b를 nil로 바꾸어준다고 해도 참조 카운트가 1이 더 남는다.

이를 없애기 위해 dictionary안에 있는 값까지 제거를 해주어야한다.

remove함수를 이용해서 없애보았다.

 

ab.remove(key: "A")
ab.remove(key: "B")

그러자, A와 B가 성공적으로 deinit되는것을 확인 할 수 있다.

그렇기 때문에 만약, AB Class안에 dictionary 값을 지워주는 remove와 같은 함수가 없다면 Memory Leak이 발생할 가능성이 있다.

 

그렇다면, a와 b를 nil로 바꾸었을때 바로 deinit시킬 수는 없을까?

이를 가능하게 하는 방법은 여러가지가 있겠지만, 내 기준에서 가장 손쉬운 방법을 찾아보았다.

 

먼저, 새로운 클래스를 하나 선언한다.

이 클래스는 weak var로 value를 가지는 것이 특징인데 이로인해 참조 카운트가 증가하지 않는다.

class Weak {
    weak var value:AnyObject?

    init(value: AnyObject) {
        self.value = value
    }

    deinit {
        print("Weak deinit")
    }
}

 

클래스 AB의 Dictionary 안의 값을 Weak 클래스로 바꾼다.

regist함수는 매개변수로 받은 value를 Weak클래스로 한번 더 감싸서 dictionary의 value로 넣는다.

class AB {
    var dic = [String: Weak]()

    func regist<T>(key: String , value: T) {
        let key = key
        let weak = Weak(value: value as AnyObject)
        dic[key] = weak
    }

    func remove(key:String) {
        dic[key] = nil
    }
}

아까와 똑같은 코드를 작성해서 확인해보자.

let ab = AB()
var a: A? = A()
var b: B? = B()

ab.regist(key:"A", value: a)
ab.regist(key:"B", value: b)

a = nil
b = nil

이번에는 remove를 실행하지 않고도 바로 deinit이 될수 있음을 확인 할 수 있다.

'Swift' 카테고리의 다른 글

Data Testing in Swift  (0) 2022.05.12
Generic을 이용한 CompileTime에 Type 검사하기.  (0) 2022.05.12
깊은 복사 & 얕은 복사 (Swift)  (2) 2022.05.08
MetaType_iOS  (0) 2022.05.05
동시성 프로그래밍 가이드 읽어보기.  (0) 2022.01.27

댓글