336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.


ARC(Automatic Reference Counting) 자동 참조 카운팅


ARC란?

메모리를 모니터링하고 관리해주는 자동참조카운팅(ARC)라는 기능을 제공한다. ARC의 역할은 프로그램에서 사용한 메모리를 추적해서 인스턴스가 사용한 메모리가 더 이상 필요 없는 경우 자동으로 해제 역할을 한다.


ARC 기능

클래스를 정의 한 후에 인스턴스를 생성하게 되면 ARC는 자동으로 그 인스턴스에 대한 정보를 저장할 메모리를 할당한다. 이 메모리는 해당 인스턴스가 가진 속성들의 값들을 비롯한 인스턴스의 타입에 대한 정보를 저장하고 있다. 인스턴스가 해제될 때 ARC는 자동으로 인스턴스가 사용한 메모리를 해제한다. ARC의 동작은 클래스에서만 동작한다. 클래스는 인스턴스를 생성할 때 참조 타입(reference type)으로 생성하기 때문이다. 구조체나 열거형은 값 타입(value type)이기 때문에 ARC입장에서는 메모리를 관리 할 필요가 없다.


인스턴스 생성과 해제

인스턴스의 속성, 상수, 변수들이 각 클래스의 인스턴스에서 몇번 참조되었는가를 계속 트래킹하고 있다가 참조하는 수가 0이 되면 해당 인스턴스를 해제하고 0보다 크면 참조를 계속 유지하는 방식이다.

import UIKit

class MyGrade {
    var score: Int
    init(score: Int) {
        self.score = score
        print("초기화 되었습니다.")
    }
    deinit {
        print("해제 되었습니다")
    }
}

var ref: MyGrade? //옵서녈 타입으로 nil값이 할당되어, 메모리가 할당되기 전이다.

ref = MyGrade(score: 100) //인스턴스를 생성하고 score에 대한 메모리가 할당한다.
ref = nil //다시 nil값을 할당하여, 인스턴스를 해제하는 코드입니다.

//콜솔에서의 결과
초기화 되었습니다.
해제 되었습니다

Strong reference

클래스의 인스턴스를 다른 클래스의 속성으로 만드는 경우를 스토롱참조라고 한다. 스트롱 참조의 경우는 사용자가 편하고 코드를 간결하게 만들수 있다는 장점이 있다.

import UIKit

class MyGrade {
    var score: Int
    init(score: Int) {
        self.score = score
        print("초기화 되었습니다")
    }
    deinit {
        print("해제 되었습니다")
    }
}

var ref_1: MyGrade? //옵서녈 타입으로 nil값이 할당되어, 메모리가 할당되기 전이다.
var ref_2: MyGrade? //옵서녈 타입으로 nil값이 할당되어, 메모리가 할당되기 전이다.
var ref_3: MyGrade? //옵서녈 타입으로 nil값이 할당되어, 메모리가 할당되기 전이다.

ref_1 = MyGrade(score: 100) //인스턴스를 생성하고 score에 대한 메모리가 할당한다.
ref_2 = ref_1 //ref_2에 ref_1를 Strong reference
ref_3 = ref_1 //ref_3에 ref_1를 Strong reference

//순차적으로 nil값을 대입해보면
ref_1 = nil //ref_2, ref_3 메모리가 할당되어 있기 때문에 해제되지 못한다.
ref_2 = nil //ref_3 메모리가 할당되어 있기 때문에 해제되지 못한다.
ref_3 = nil //메모리에 할당된 참조 카운트가 0이되기에 인스턴스가 해제 된다.

//콜솔에서의 결과
초기화 되었습니다.
해제 되었습니다.

하지만 여러 클래스에서 스트롱참조를 사용한다보면 예상하지 못한 문제에 부딪힐수 있다. 문제로는 Strong reference cycle이 있다.

Strong reference cycle

class Friend {
    let name: String
    var myClass: Class?
    init(name: String) {
        self.name = name
        print("\(name)이 초기화 되었습니다")
    }
    deinit {
        print("\(name)이 해제 되었습니다.")
    }
}

class Class {
    let number: Int
    var mymember: Friend?
    init(number: Int) {
        self.number = number
        print("\(number)반이 초기화 되었습니다.")
    }
    deinit {
        print("\(number)반이 해제 되었습니다.")
    }
}

var myFriend: Friend?
var myClass: Class?

myFriend = Friend(name: "명아무개") //인스턴스를 생성하고 name에 대한 메모리가 할당된다.
myClass = Class(number: 3) //인스턴스를 생성하고 number에 대한 메모리가 할당된다.

//?는 옵셔널 타입을 가르킨다면, !는 참조 타입의 속성을 가리킨다.
myFriend!.myClass = myClass //다른 클래스의 인스턴스를 참조시킨다.
myClass!.mymember = myFriend //다른 클래스의 인스턴스를 참조시킨다.

myFriend = nil //다른 클래스의 인스턴스를 참조하고 있기 때문에 메로리가 해제되지 않는다
myClass = nil //다른 클래스의 인스턴스를 참조하고 있기 때문에 메로리가 해제되지 않는다

//콜솔에서의 결과
명아무개이 초기화 되었습니다
3반이 초기화 되었습니다.

서로의 인스턴스를 속성으로 연결할 경우 둘다 속성이 남아있기때문에 메모리에서 해제될수 없다. 이런 현상을 String reference cycle이라고 한다. 이문제의 해결 방법으로는 weak reference와 unknown reference가 있다.


weak reference

위크 참조는 인스턴스를 속성값으로 저장해두지 않기 때문에 ARC가 동작하는데 큰 문제가 없도록 한다.

class Friend {
    let name: String
    var myClass: Class?
    init(name: String) {
        self.name = name
        print("\(name)이 초기화 되었습니다")
    }
    deinit {
        print("\(name)이 해제 되었습니다.")
    }
}

class Class {
    let number: Int
    weak var mymember: Friend?
    init(number: Int) {
        self.number = number
        print("\(number)반이 초기화 되었습니다.")
    }
    deinit {
        print("\(number)반이 해제 되었습니다.")
    }
}

var myFriend: Friend?
var myClass: Class?

myFriend = Friend(name: "명아무개") //인스턴스를 생성하고 name에 대한 메모리가 할당된다.
myClass = Class(number: 3) //인스턴스를 생성하고 number에 대한 메모리가 할당된다.

//?는 옵셔널 타입을 가르킨다면, !는 참조 타입의 속성을 가리킨다.
myFriend!.myClass = myClass //다른 클래스의 인스턴스를 참조시킨다.
myClass!.mymember = myFriend //위크 참조이기에 다른 클래스의 인스턴스를 참조시키지 않는다.

myFriend = nil
myClass = nil

//콜솔에서의 결과
명아무개이 초기화 되었습니다
3반이 초기화 되었습니다.
명아무개이 해제 되었습니다.
3반이 해제 되었습니다.

위크 참조로 속성을 선언하면 “값이 없다”고 설정 할 수 있기 때문에 모든 위크 참조는 옵셔널 타입으로 선언되어야 한다. 위크 참조는 인스턴스의 참조를 저장하지 않기 때문에 위크 참조인 속성이 여전히 다른 인스턴스를 참조하고 있다고 하더라도 해당 인스턴스를 해제할 수 있다.


unknown reference

위크 참조하는 달리 언노운 참조는 항상 값을 갖고 있다고 가정한다.

class Friend {
    let name: String
    var myClass: Class?
    init(name: String) {
        self.name = name
        print("\(name)이 초기화 되었습니다")
    }
    deinit {
        print("\(name)이 해제 되었습니다.")
    }
}

class Class {
    let number: Int
    unowned var mymeber: Friend
    init(number: Int, friend: Friend) {
        self.number = number
        self.mymeber = friend
        print("\(number)반이 초기화 되었습니다.")
    }
    deinit {
        print("\(number)반이 해제 되었습니다.")
    }
}

var myFriend: Friend?
var myClass: Class?

myFriend = Friend(name: "명아무개")//인스턴스를 생성하고 name에 대한 메모리가 할당된다.
myClass = Class(number: 3, friend: myFriend!) //언노운 참조이기에 다른 클래스의 인스턴스를 파라미터로 받아서 초기화 한다.

myFriend = nil

//콘솔에서의 결과
명아무개이 초기화 되었습니다
3반이 초기화 되었습니다.
명아무개이 해제 되었습니다.

언노운 참조는 Friend 이니셜라이즈는 두개의 속성에 대해 파라미터로 값을 받아서 초기화를 해야 한다는 점이다.


맞치며

Swift에서는 참조 타입의 속성에 대해 다른 인스턴스를 얼마나 많이 참조하는가를 카운트해두었다가 그 카운트가 0이 되는, 즉 더는 참조하는 인스턴스가 없다고 판단되는 경우에만 인스턴스를 해제한다. 두개 이상의 클래스들이 서로 간의 참조를 속성값으로 갖고 있는 경우에는 참조 카운터가 0이 되지 못하기 때문에 발생한다. 이러한 문제를 스토롱 참조 사이클이라고 하며, 위크참조와 언노운참조를 통해 문제를 해결 할수 있다.

ARC 자체만으로도 개발자에게 메모리 누수라는 문제를 해결 할수 있는 강력한 도구이다. 하지만 잘 알고 사용을 해야 완벽한 메모리 누수를 잡을 수 있다. 이번 ARC를 공부 및 정리를 하다보니 실전도 중요하지만 이론도 중요하다는 결론이 나온다. 부족한 실력으로 정리를 했지만 도움이 되길 바라면서 틀린점이나 궁금한점이 있으면 댓글 남겨주세요. 감사합니다 :)


출처

가장 쉽게 설명하는 Swift



'iOS 프로그래밍 > Swift' 카테고리의 다른 글

스위프트란? (swift)  (0) 2015.11.19

+ Recent posts