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 |