ARC(Automatic Reference Counting)

728x90

ARC 메모리 개념 (Retain Cycle, strong, weak, unowned)에 대해 알아 보겠습니다.

ARC

ARC는 Automatic Reference Counting의 약자로 기존에 수동(MRC라고 함)으로 개발자가 직접 retain/release를 통해 reference counting을 관리해야 하는 부분을 자동으로 관리해줍니다.

Memory Management without ARC

1. GC (Garbage Collection)

  • 정기적으로 Garbage Collector 가 동작하여 더이상 사용되지 않는 메모리를 반환하는 방식
  • OS X 에서만 지원했었으나 버전 10.8 (Mountain Lion) 부터 deprecated ARC는 GC(Garbage Collector)와는 다르게 런타임이 아닌 컴파일 단에서 처리됩니다. GC는 런타임에 메모리를 검사하기 때문에 앱 퍼포먼스에 악영향을 준다. 그러나 ARC는 개발자가 직접 코딩하던 retain/release를 컴파일러가 자동으로 코드에 삽입시키므로, 동작시에는 MRC와 동일하여 성능 저하를 유발하지 않는다.

 

2. MRR (Manual Retain-Release) / MRC (Manual Referece Counting)

  • RC(Reference Counting) 를 통해 메모리를 수동으로 관리한는 방식
  • retain / release / autorelease 등의 메모리 관리 코드를 직접 호출
  • 개발자가 명시적으로 RC 를 증가시키고 감소시키는 작업 수행

 

즉, 개발자가 명시적으로 RC 를 증가시키고 감소시키는 작업 수행을 말합니다.


Objective-C에서는 ARC 해제 가능.

Build Setting -> Objective-C Automatic Reference Counting을 NO로 설정하면 ARC해제 가능.

 

응용 프로그램 메모리 관리는 프로그램의 런타임 중에 메모리를 할당하고 이를 사용하여 메모리를 할당하고 완료 한 후에 메모리를 확보하는 프로세스입니다.

 

int main(int argc, const char * argv[]) {
       
    Person *man = [[Person alloc] init];  // count : 1
    [man doSomething];

    [man retain];   // count : 2
    [man doSomething];
    
    [man doSomething];
    [man release];  // count : 1
    [man release];  // count : 0
    return 0;
}

 

ARC Basic

  • RC 자동 관리 방식 (WWDC 2011 발표)
  • 컴파일러가 개발자를 대신하여 메모리 관리 코드를 적절한 위치에 자동으로 삽입
  • GC 처럼 런타임이 아닌 컴파일 단에서 처리 (Heap 에 대한 스캔 불필요 / 앱 일시 정지 현상 없음)
  • 메모리 관리 이슈를 줄이고 개발자가 코딩 자체에 집중할 수 있도록 함 ARC 는 클래스의 인스턴스에만 적용 (Class - Reference 타입, Struct / Enum - Value 타입)
  • 활성화된 참조카운트가 하나라도 있을 경우 메모리에서 해제 되지 않음

 

<참조타입>

  • 강한 참조 (Strong) : 기본값. 참조될 때마다 참조 카운트 1 증가

  • 약한 참조 (Weak), 미소유 참조 (Unowned) : 참조 카운트를 증가시키지 않음
    강한 순환 참조 (Strong Reference Cycles) 에 대한 주의 필요

  • weak는 let과 Non-Optional이 안되는 이유?
    weak는 참조하던 객체가 해제될때는 nil로 바뀝니다. 즉, 값이 변해야되니까 var를 사용!, nil을 사용할 수 있어야되니까 nil로 Optional만 되는겁니다!

  • unowned는 Optional이 안되는 이유?
    unowned는 nil로 바뀔수 없고 메모리 해제시에도 항상 메모리 주소 값을 가지고 있기 때문에 Optional을 사용할 수 없다. 즉, 객체가 항상 존재한다고 가정한다고 했을때 사용합니다.

 

ARC in 클래스, 구조체 비교

ARC in 클래스(Class)

클래스에서 ARC를 확인해보겠습니다.

⇧ 객체 생성, Heap에 메모리가 로드됨.

 

⇧ Stack에는 Heap의 주소가 저장되어있어 Heap을 가리키고 있다.
⇧ 객체가 생성되었으므로 Reference Count 1 증가

 

⇧ point2에 point1을 할당. Reference Count 2 증가
⇧ point2는 point1의 주소를 가리키고있으므로 데이터는 공유된다.

 

 

 

⇧ spoint2의 x의 값을 바꿔도 같은 주소를 바라보기 때문에 point1, point2는 값이 공유된다.

 

 

⇧ ARC가 Release시켰다고 가정했을때 Reference Count 1

 

⇧ ARC가 Release시켰다고 가정했을때 Reference Count 0

 

 


 

ARC in 구조체(Struct)

⇧ Struct는 Value타입이므로 Stack의 메모리를 사용한다.

 

⇧ Struct는 클래스처럼 같은 주소를 바라는것이 아니라, 복사하여 다른 Stack에 저장된다.

 

⇧ point2의 값을 변경해도, point2와는 전혀 상관이 없다.

 

 

 


 

소스로 확인 해보기( ARC in Swift )

  • Person클래스
class Person {
  let testCase: String
  init(testCase: String) {
    self.testCase = testCase
  }
  deinit {
    print("\(testCase) is being deinitialized")
  }
}

클래스의 reference count가 0이 되면 deinit이 호출된다.

 

 

1. Allocation & Release

var obj1: Person? = Person(testCase: "case1") // reference count 1 증가
obj1 = nil      // reference count 0으로되서 메모리가 해제가 됨. Person(testCase: "case1")이 해제됨.

case1 is being deinitialized // deinit호출

 

2. 참조 카운트 증가

var obj2: Person? = Person(testCase: "case2")
var countUp = obj2 // reference count // 2 증가

obj2 = nil // [obj2 release]
countUp = nil // [countUp release]

case2 is being deinitialized // deinit호출

 

3. Collection 에 의한 참조 카운트

var obj3: Person? = Person(testCase: "case3")
var array = [obj3]

obj3 = nil
array.remove(at: 0)
// 둘다 없애줘야 deinit이 호출된다.

case3 is being deinitialized // deinit호출

 

4. 강한참조, 약한참조

  • strong : 기본값. 강한 참조. Reference Count 1 증가
  • unowned : 약한 참조. Count 증가하지 않음. 참조 객체 해제 시에도 기존 포인터 주소 유지
  • weak : 약한 참조. Count 증가하지 않음. 참조하던 객체 해제 시 nil 값으로 변경
// 객체를 만들어주긴 만들어주는데 weak라서 바로 객체가 해제되어 nil로 처리된다.
weak var weakObj4 = Person(testCase: "case4")

// 객체를 만들어주는데 바로 객체가 해제되는데, 해제된 주소값을 들고 있어서 에러가 난다.
unowned var unownedObj4 = Person(testCase: "case4")

 

5. Strong Reference Cycle

  • 객체에 접근 가능한 모든 연결을 끊었음에도 순환 참조로 인해 활성화된 참조 카운트가 남아 있어 메모리 누구가 발생하는 현상

  • 앱의 실행이 느려지거나 오동작 또는 오류를 발생시키는 원인이 됨

 

Strong Reference Cycle 이미지

 

 

 

 

Weak Reference Cycle 이미지

 

 

 

 

 

 

class Person {
  var pet: Dog?
  func doSomething() {}
  deinit {
    print("Person is being deinitialized")
  }
}

class Dog {
  var owner: Person?
//  weak var owner: Person? // weak를 해주는 방법도 있다.
  func doSomething() {}
  deinit {
    print("Dog is being deinitialized")
  }
}
var somePerson: Person? = Person() // 1증가
var someDog: Dog? = Dog() // 1 증가

somePerson.pet = someDog // 1증가해서 2가됨.
someDog.owner = somePerson // 1증가해서 2가됨.

위의 소스와 같은 상황을 다이어그램으로 표현했습니다.

 

somePerson과 someDog에 nil을 할당해서 해제를 시키면 아래와 같은 상황이 메모리가 아직 다 해제가 되지 않게됩니다.

이러한 현상을 메모리가 누수(Memory Leak)이라고 하며 강한 순환 참조되었다고 합니다. 이렇게 앱을 만들다가 강한순환참조가 발생하게되면 사용할때마다 계속해서 메모리가 증가하게 될 것이고, 오류나 앱이 Crash가 나게 될겁니다.

이렇게 강한 순환 참조를 막기 위해서 weak(약한참조)를 써주면 해결할 수 있습니다.

// Person 클래스
weak var pet: Dog?

// Dog 클래스
weak var owner: Person?

'iOS' 카테고리의 다른 글

[iOS] UIDevice  (0) 2019.07.24
[iOS] GCD(Grand Central Dispatch)  (0) 2019.07.24
[iOS] 단축키(Shortcuts)  (0) 2019.07.24
[iOS] UIImagePickerController  (0) 2019.07.24
[iOS] TabbarController  (0) 2019.07.24