본문 바로가기
Xcode/Swift - PlayGround

PlayGround) Auto Reference Counting(ARC) 에 대해 알아보자!!!

by 후르륵짭짭 2020. 8. 24.
728x90
반응형

안녕하세요 후르륵짭짭이 입니다.

ARC에 대해서 배우고 이를 잘 다루는 방법에 대해서 배우도록 하겠습니다.

 

** ARC란? ** 

Swift는 ARC (Automatic Reference Counting)을 통해서 메모리 사용량을 추적하고 관리 합니다. 따라서 클래스의 인스턴스가 더 이상 필요하지 않다고 판단하면 자동으로 해당 인스턴스의 메모리를 해제 해줍니다. 그래서 그냥 놔두면 아주 스스로 잘 해주지만,,,, 예외 케이스가 있습니다. 

(참고로 레퍼런스 카운팅은 오직 클래스의 인스턴스에만 적용됩니다. 구조체와 ENUM은 값 타입이기 때문에 참조reference가 저장되거나 전달되는 일이 없습니다!)

 

** 예외 케이스 - 객체 인스턴스 참조 ** 

class ParentCalss {
    
    var classConect : ParentCalss?
    
    init(){
    
        print("클래스 생성")
    }
    
    deinit{
        print("클래스 소멸")
    }
    
}

var parent : ParentCalss? = ParentCalss()
var child : ParentCalss? = ParentCalss()

parent?.classConect = child
child?.classConect = parent

parent = nil
child = nil

//결과
클래스 생성
클래스 생성

지금 위에를 보면 그림과 같은 상황 입니다. 따라서 결과가 클래스 생성 두번 나오고 소멸이 되지 않습니다!

이러한 현상을 SRC (Strong Reference Cycle) 이라고 합니다.

즉 돌고 도는 현상이 되어 사라지지 않는 것 입니다

이 문제를 해결하기 위해서는 공유하는 변수에 weak을 해주면 됩니다!

A weak reference is a reference that does not keep a strong hold on the instance it refers to, and so does not stop ARC from disposing of the referenced instance. 

이것을 보면 weak 변수는 not keep a strong hold on the instance 라고 되어 있습니다. 

꽉 안 잡고 있겠단 소립니다.

따라서 아래 처럼 파랑색 점선으로 구성하게 됩니다.

이렇게 되면 Strong 하게 인스턴스가 가지고 있는게 아니기 때문에, 사라지게 됩니다.

그래서 결국 코드를 아래 처럼 weak으로 바꿔주면 원하는 결과가 나오게 됩니다.

class ParentCalss {
    
    weak var classConect : ParentCalss?
    
    init(){
    
        print("클래스 생성")
    }
    
    deinit{
        print("클래스 소멸")
    }
    
}

var parent : ParentCalss? = ParentCalss()
var child : ParentCalss? = ParentCalss()

parent?.classConect = child
child?.classConect = parent

parent = nil
child = nil

//결과
클래스 생성
클래스 생성
클래스 소멸
클래스 소멸

 

** 예외 케이스 - Closure **

class ViewController {
    
    var completHandler : ((String)->())? = nil
    
    let printElement  = 5
    init(){
        print("클래스 생성")
        
        completHandler = { result in
            self.printHello(result)
        }
        
        callFunction(name: "ChapChap")
        
        
    }
    
    deinit {
        print("클래스 소멸")
    }
    
    private func printHello(_ name : String){
        print("Hello \(name)")
    }
    
    func callFunction(name : String){
        completHandler?(name)
    }
    
}

var viewController  : ViewController? = ViewController()
viewController = nil

//결과
클래스 생성
Hello ChapChap

이번에는 클로저에서 발생하는 SRC를 보도록 하겠습니다.

이 코드를 보면 우리가 흔히 사용하는 코드 입니다. 저도 사실 이렇게 아무 생각 없이 많이 작성 했습니다.

그런데 여기에 아주 큰 문제가 있는데, 인스턴스가 있는 클로저로 같은 객체를 불러올 경우에

SRC가 발생하는 겁니다.

모르고 있던 사실 이였는데, 클래스 내부에 있던 클로저에서 인스턴스나 함수를 호출 할 경우에, 

다시 그 함수 내부에 있는 함수를 호출하게 됩니다.

즉, 클래스를 생성하는 것은 아니지만 클래스 자체를 돌고 도는 형태의 SRC로 만들어 버린 겁니다.

class ViewController {
    
    var completHandler : ((String)->())? = nil
    
    let printElement  = 5
    init(){
        print("클래스 생성")
        
        completHandler = { [weak self]result in
            self?.printHello(result)
        }
        
        callFunction(name: "ChapChap")
        
        
    }
    
    deinit {
        print("클래스 소멸")
    }
    
    private func printHello(_ name : String){
        print("Hello \(name)")
    }
    
    func callFunction(name : String){
        completHandler?(name)
    }
    
}

var viewController  : ViewController? = ViewController()
viewController = nil

//결과
클래스 생성
Hello ChapChap
클래스 소멸

하지만 이렇게 [weak self]를 해준다면 Strong 참조를 안하게 되고 존재 안할 수도 있기 때문에 self? 를 통해서 쉽게 해결 할 수 있습니다.

 

그런데!!!

class ClosureClass {
    var name : String? = nil
    
    init() {
        print("클래스 생성")
        startClosure()
    }
    
    deinit {
        print("클래스 소멸")
    }
    
    func getName(name : String , completeHandler : (String?)->()){
        completeHandler(name)
    }
    
    func startClosure(){
        getName(name: "ChapChap", completeHandler: { result in
            self.name = result
        })
    }
}

var closureClass : ClosureClass? = ClosureClass()
closureClass = nil

//결과
클래스 생성
Hello ChapChap
클래스 소멸

이것도 클로저를 이용해서 self를 하는데, 결과가 올바르게 나오는 것을 확인 할 수 있습니다.

둘의 차이는 인스턴스로 하냐 아니면 함수 내부에서 호출하냐 차이가 전부 입니다.

함수에서 호출한다면 그 함수가 끝날 때 동시에 메모리에서 반환이 됩니다.

따라서 강한 참조를 하지 않게 되는 것 입니다!

 

** 예외 케이스 - Delegate **

protocol SomeDelegate {
    func funcPrint()
}

class MainViewController : SomeDelegate {
    
    let vc : ChildViewController?
    
    init(){
        print("MainViewController 생성")
        vc = ChildViewController()
        vc?.delegate = self
        vc?.anyFunc()
        
        //ChildeViewController의 delgate가 SomeDelegate를 받고 있고
        //그 deleagate에 값을 넣어주기 위해서는 현재 클래스도 SomeDelegate를 받고 있어야한다.
    }

    func funcPrint() {
        
    }
    
    deinit {
        print("MainViewController 소멸")
    }
    
    
}

class ChildViewController {
   var delegate : SomeDelegate?
    
    init(){
        print("ChildViewController 생성")
    }
    
    deinit {
        print("ChildViewController 소멸")
    }
    
    func anyFunc(){
        print("프린트 ChildViewController")
    }
    
    
}

var mainViewController : MainViewController? = MainViewController()
mainViewController = nil

//결과
MainViewController 생성
ChildViewController 생성
프린트 ChildViewController

우리가 흔하게 접하는 delegate 입니다.

delegate 패턴은 프로토콜로 규칙이라고 예전에 말했습니다.

이렇게 delegate를 했는데, SRC가 발생하다니,,,,

원인은 let vc : ChildViewController? 에  vc = ChildViewController() 를 넣어주면서 

ChildViewControllerdptj MainViewController가 강한 참조를 하게 됩니다.

그리고 vc?.delegate = self 로 이제 다시 MainViewController에서 ChildViewController로 강한 참조를 하게 됩니다.

결국 아래 이미지 처럼 되어 버린 겁니다.

그래서 SRC가 발생 한 겁니다.

그러면 이제 참조하고 있는 변수인 delegate를 weak으로 해주면 되겠지 싶었는데,

'weak' must not be applied to non-class-bound 'SomeDelegate'; consider adding a protocol conformance that has a class bound"

라는 오류가 발생합니다.

이 오류는 weak은 반드시 Class로 지정된 것에만 해당해야한다. 라는 것 입니다.

우리가 Protocol을 지정 했지만 그 Protocol이 참조형이 아닌 Struct Enum 모두에게 적용 될 수 있다면 안되기 때문에

Protocol을 class 타입으로 지정해줍니다.

protocol SomeDelegate : class{
    func funcPrint()
}

class MainViewController : SomeDelegate {
    
    let vc : ChildViewController?
    
    init(){
        print("MainViewController 생성")
        vc = ChildViewController()
        vc?.delegate = self
        vc?.anyFunc()
        
        //ChildeViewController의 delgate가 SomeDelegate를 받고 있고
        //그 deleagate에 값을 넣어주기 위해서는 현재 클래스도 SomeDelegate를 받고 있어야한다.
    }

    func funcPrint() {
        
    }
    
    deinit {
        print("MainViewController 소멸")
    }
    
    
}

class ChildViewController {
   weak var delegate : SomeDelegate?
    
    init(){
        print("ChildViewController 생성")
    }
    
    deinit {
        print("ChildViewController 소멸")
    }
    
    func anyFunc(){
        print("프린트 ChildViewController")
    }
    
    
}

var mainViewController : MainViewController? = MainViewController()
mainViewController = nil

//결과
MainViewController 생성
ChildViewController 생성
프린트 ChildViewController
MainViewController 소멸
ChildViewController 소멸

이렇게 해준다면 오류가 해결 됩니다.

 

지금 까지 ARC가 무엇인지 SRC가 무엇인지, 그리고 언제 발생하는지 알아봤습니다.

사실 저도 완벽하게 아는 것이 아니라, 공부 한것을 처음으로 정리하는 것이기 때문에 부족할 수 있습니다.

다음에 좀더 정확하게 배워서 더욱 깊게 알려드리겠습니다.

 

참고 사이트 : 

docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID48

 

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

hyerios.tistory.com/16

 

Protocol에 class선언해주기

Q delegate는 왜 Weak로 선언할까요? A Retain cycle 을 피하기 위해서! 기본적으로 클래스의 객체를 가리키는 각각의 reference는 강함 참조(strong)입니다. 두 클래스 인스턴스가 서로 강한 참조를 하게 ��

hyerios.tistory.com

wlaxhrl.tistory.com/51

 

Automatic Reference Counting (ARC)

Apple 제공 Swift 프로그래밍 가이드(3.0.1)의 Automatic Reference Counting 부분을 공부하며 정리한 글입니다. 개인적인 생각도 조금 들어가있습니다. 들어가며 Swift는 Automatic Reference Counting (ARC)..

wlaxhrl.tistory.com

 

 

728x90
반응형

댓글