티스토리 뷰

iOS개발을 하며 기본적이지만 의외로 놓치기 쉽지만, 그렇다고 모르기엔 너무나 중요한 것들이 있다.

iOS 개발을 위해 알아야할 몇 가지를 정리차원에서 포스팅한다.

 

ARC는 무엇인가요?


Java의 경우는 GC가 자동으로 처리해주며, C/C++에서는 개발자가 직접 설정해주고 해제해줘야 한다.

iOS의 경우는 변수를 참조할 경우, Reference Counting으로 처리되는데 이것을 자동(Automatic)으로 처리해준다는 것이다.

 단, 이름에서 알 수 있듯이 참조는 클래스 타입의 변수에만 적용이 되며, 값타입에는 적용되지 않는다. 값 타입을 할당할 경우, 참조(Reference)되는 것이 아니라 해당 값이 복사 된다. 값 타입은 Swift에서는 Premitive 타입 외에, Struct, Enum, Collection등의 타입이 있다.

 

 레퍼런스 카운팅은 강한(String)타입의 변수에 할당될 때 증가되며, 약한 타입(Weak)의 변수에 할당될때는 증가되지 않는다.  강한 참조가 해제되지 않는한(유지될때) 강한 참조에 할당된 인스턴스는 해제되지 않는다.

 따라서 잘못된 강한 참조는 참조 변수의 카운팅이 감소되지 않아 메모리 누수(Memory Leak)의 주요 원인이 되니 사용시 주의를 요할 수 있다.

 강한 참조와 약한 참조의 사용을 아는 것이 ARC의 적절한 방법이라고 할 수 있겠다.

 

강한 참조란?


 강한 참조란 인스턴스의 프로퍼티나 변수, 상수 등에 레퍼런스 타입을 할당하는 것을 말한다. 레퍼런스 타입은 다른 클래스의 인스턴스(UIView의 서브클래스등..), 스트링 타입의 객체, 그리고 Objective-C에서 사용하던 NS류의 콜렉션등이 있다.

 강한 참조로 할당하게 되면 할당한 객체의 카운팅은 증가된다. 반대로 해당 참조의 값을 해제(nil로 할당) 하면 카운팅은 감소된다.

import Foundation

class Student {
    var name: String
    lazy var nameLeakLength: () -> Int = {
        self.name.count
    }
    
    lazy var nameLength: () -> Int = { [weak self] in
        return self?.name.count ?? 0
    }
    
    deinit {
        print("deinit student: \(name)")
    }
    
    init(_ _name: String) {
        name = _name
    }
}

print("case1--------")
var student: Student? = Student("Jung1")
student = nil

print("case2--------")
student = Student("Jung2")
print(student?.nameLeakLength() ?? 0)
student = nil

print("case3--------")
student = Student("Jung3")
print(student?.nameLength() ?? 0)
student = nil

 위의 예제로 강한 참조와 순환 참조에 대하여 간단히 설명하고자 한다.

 case1에서는 Student를 객체화하여 student변수에 할당한 후, 해당 변수를 nil로 할당하면, 레퍼런스 카운터가 증가, 다시 감소하여 메모리에서 해제되는 것을 볼 수 있다.

 반면 case2에서는 nameLeakLength를 호출하는데, 이때는 student를 nil로 할당해도 메모리에서 해제되는 메세지를 볼 수 없다.

 분명히 case1과 유사한데 결정적인 차이는 nameLeakLength 호출여부에 달려 있다. nameLeakLength를 호출하는 순간 nameLeakLength 클로저 함수는 메모리에 할당되며 Student에서 nameLeakLength의 강한 참조가 만들어진다. 하지만 nameLeakLength또한 self를 강한 참조하여 서로 간의 강한 참조가 되어 메모리에서 해지되지 않는 문제가 발생되며 이러한 상태가 많아질수록 메모리 누수(Memory Leak)이 발생하는 것이다.

 따라서 이를 해결하려면 둘 중 하나를 약한 참조로 변경해야 한다. nameLeakLength는 향후 계속 사용할 예정이므로, nameLeakLength를 고쳐서 사용해본 것이 case3이다.

 클로져 시작부분에 self를 약한 참조로 사용한다는 뜻으로 [weak self] in 을 기입하였다.

 아래의 물음표 한개는 옵셔널을 뜻하며, 옵셔널은 해당 값이 있을 수도 있고, 없을 수도 있다는 뜻이다. 옵셔널을 설명하자면 글이 길어지므로 다음에 포스팅할 수 있으면 포스팅할 것이다. 또한 물음표 두개(??)는 값이 없을 경우, 기본 값을 설정하는 것이다. 옵셔널을 잘 사용하면 안전한 코딩을 할 수 있다. 즉 self는 기본적으로 강한 참조인데, 이 부분을 약한 참조로 사용하겠다고 하였으며, 이를 통하여 서로 강한 참조 관계를 깨서 메모리 누수 문제를 수정하였다.

  이 부분은 기본적이면서 매우 중요한 부분이며, lazy 를 이용하여 메모리를 효과적으로 사용하겠다고 자칫 메모리 누수를 발생시킬 수 있다. 또한 lazy뿐 아니라 클로져 사용시 잘못하면 상호 강한 순환 참조를 만들어 메모리 누수를 일으킬 수 있다.

 잘 모르겠으면 클로져에서는 weak 지시자를 사용하여 약한 참조를 하는 것이 좀 더 방어적인 코딩 방법이라고 생각한다.

 

 

 

약한 참조란?


 약한 참조는 프로퍼티나 변수 상수 앞에 weak이라는 지시어를 할당하면 약한 참조 타입으로 할당된다.

 약한 참조에 참조되는 레퍼런스 타입은 카운팅이 되지 않는다. 또한 약한 참조 타입은 클래스 인스턴스나 클래스에서 사용되기로 되어있는 프로토콜에서만 사용할 수 있다. 만약 Struct, enum, String등의 타입을 약한 참조 타입으로 지정하면 컴파일 오류가 발생한다.

 반대로 말하면 약한 참조는 클래스 인스턴스에서만 사용하면 된다는 말이다.

 

 약한 참조의 좋은 사용 예는 iOS에서 자주 사용되는 Delegate패턴에서 볼 수 있다.

 delegate변수는 weak 타입으로 할당된다. 이유는 콜백을 받는 인스턴스는 이미 생성되어 있으며, 레퍼런스 카운터를 증가시킬 이유가 없다. 다만 해당 인스턴스의 메모리 포인터만 참조하는 용도로 사용하며, 강한 참조 사용시 서로 순환 참조를 방지하기 위하여 약한 참조타입으로 사용한다.

 

 약한 참조에는 weak과 unowned가 있다.

 둘의 가장 큰 차이는 weak은 옵셔널로, unowned는 넌옵셔널(non-optional)로 처리가 된다는 점이다.

 따라서 메모리가 해제되었을 경우, unowned로 설정한 변수에 접근 시 nil exception이 발생하여 메모리 크래시로 앱이 강제 종료될 수 있다.

 

 

줄이며..


 여러모로 부족한 포스팅이다. 틈나는 대로 읽고 보충할 내용은 보충하고, 수정할 내용은 수정해야겠다.

 

 예전에 직접 메모리를 할당하고 해제할땐 자동으로 해준다는걸 못믿어서 꼭 ARC해제하고 빌드하던 시절이 있었다. 하지만 alloc, copy, retain, release, autorelease 등 이젠 모두 과거의 추억이 되었다.

 

 역시 사람은 적응의 동물인가보다. 이제는 ARC없는 개발은 생각할 수 없으니... :)

 

 

관련자료:

docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html

 

Automatic Reference Counting — The Swift Programming Language (Swift 5.3)

Automatic Reference Counting Swift uses Automatic Reference Counting (ARC) to track and manage your app’s memory usage. In most cases, this means that memory management “just works” in Swift, and you do not need to think about memory management yours

docs.swift.org

developer.apple.com/videos/play/wwdc2011/323/

ARC를 왜 도입해야하는가에 대한 WWDC 아티클.. 개발자 계정이 있어야 볼 수있다;;
엄청 오래된 자료지만 ARC가 왜 도입되었는지 알 수 있는 아티클이다.

 

Introducing Automatic Reference Counting - WWDC 2011 - Videos - Apple Developer

Automatic Reference Counting (ARC) dramatically simplifies memory management in Objective-C. Discover how the latest advancements in the...

developer.apple.com

댓글