[iOS / Swift] iOS 메모리 구조와 앱 메모리
1. iOS 메모리 구조
iOS에서 앱이 실행되면 메모리는 4가지 영역으로 나뉨
메모리 영역 | 하는 일 | 예시 |
Stack (스택) | 지역 변수 저장(자동 관리됨) | 함수 안에서 만든 변수 (`let`, `var`) |
Heap (힙) | 동적으로 할당되는 데이터 저장 | `class`인스턴스, `UIImage`등 |
Date (데이터) | 전역 변수, static변수 저장 | `static var count = 0` |
Code (코드) | 실행할 코드 저장 | 컴파일된 앱 코드 |
예제
class Person {
var name: String // Heap 영역에 저장됨 (동적 할당됨)
init(name: String) {
self.name = name
}
}
func sayHello() {
let greeting = "Hello" // Stack 영역에 저장됨 (함수 끝나면 사라짐)
let user = Person(name: "John") // Heap에 저장됨 (직접 해제 필요)
print("\(greeting), \(user.name)!")
}
- `greeting` 변수는 Stack에 저장됨 => 함수 종료 시 자동 해제
- `greeting`은 String 타입이지만, Swift에서 `String`은 값 타입이기 때문에 Stack에 저장됨
- 함수 `sayHello()`가 끝나면 자동으로 사라짐
- `Person` 인스턴스는 Heap에 저장됨 => 직접 해제해줘야 함(ARC 사용)
- `Person`은 class(참조 타입)이기 때문에, 객체가 Heap에 저장됨
- `user` 변수는 Stack에 저장되지만, Heap에 있는 `Person` 객체를 가리키는 포인터(주소)만 저장됨
- 함수 `sayHello()`가 끝나도 ARC에 의해 해제될 때까지 메모리에 남아 있음
그러면 메모리를 어느정도 관리해주니까 조금 덜 해도 되는거 아닐까? 하는 생각이 들었음.
2. iOS의 메모리 관리 방법
iOS는 자동으로 메모리를 관리하지만, 몇 가지 개념을 알아야 함.
2-1 ARC (Automatic Reference Counting)
iOS에서는 ARC(자동 참조 카운트)를 사용해서 객체가 필요 없으면 자동으로 메모리를 해제해줌.
쉽게 말하면, 객체를 몇개의 변수가 참조하고 있는지 체크해서 아무도 안 쓰면 삭제하는 방식임.
class Animal {
var name: String
init(name: String) {
self.name = name
print("\(name) 생성됨!") // 객체 생성 시 메시지 출력
}
deinit {
print("\(name) 해제됨!") // 객체 해제될 때 메시지 출력
}
}
func createAnimal() {
let dog = Animal(name: "Buddy") // ARC 카운트: 1
} // 함수 종료 시 dog 참조가 사라지므로 ARC 카운트 0 -> 메모리 해제됨
// 출력 결과
Buddy 생성됨!
Buddy 해제됨!
=> 함수가 끝나면 `dog`를 참조하는 곳이 없기 때문에, 자동으로 메모리에서 삭제됨.
그런데 이정도면 메모리를 어느정도 관리해준다는 소리 같은데, 메모리가 부족해지기가 쉽지 않을 것 같은데..?
3. Memory Warning이 발생하는 이유
앱이 메모리를 너무 많이 사용하면 Memory Warning이 발생함
iOS는 메모리가 부족하면 앱을 강제로 종료할 수도 있음
Memory Warning 발생 원인
- 이미지를 한꺼번에 너무 많이 로드할 때
- 불필요한 객체를 계속 만들고 지우지 않을 때(메모리 누수 = Memory leak)
- 백그라운드에서 앱이 너무 많이 실행 중일 때
Memory Warning 대처 방법
iOS는 `didReceiveMemoryWarning()`을 호출해서 앱이 불필요한 메모리를 정리하도록 요청함
이때, 캐시 데이터를 삭제하거나 사용하지 않는 객체를 해제해야 함
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
imageCache.removeAllObjects() // 이미지 캐시 삭제 (메모리 절약)
}
이렇게 하면 앱 내에서 임시 저장한 이미지 데이터를 삭제해서 메모리를 확보 가능, 또는
`UIImage(contentsOfFile:)`을 사용해서 필요할 떄만 이미지를 로드하는 것도 좋은 방법임
4. 메모리 누수 방지 방법(Strong Reference Cycle 해결하는 방법)
ARC가 자동으로 메모리를 관리하지만, 순환 참조가 생기면 메모리 해제가 안됨
그 말인 즉슨, 객체들이 서로 강하게 참조하면 ARC가 해제하지 못해서 메모리 누수가 발생한다는 뜻임.
메모리 누수가 발생하는 코드를 보겠음
class A {
var b: B? // 강한 참조
init() { print("A 생성됨") }
deinit { print("A 해제됨") }
}
class B {
var a: A? // 강한 참조
init() { print("B 생성됨") }
deinit { print("B 해제됨") }
}
var objA: A? = A()
var objB: B? = B()
objA?.b = objB
objB?.a = objA
objA = nil
objB = nil // 메모리 해제 안 됨! (순환 참조 발생)
위 코드에서
class A {
weak var b: B? // 약한 참조 (ARC가 해제 가능!)
init() { print("A 생성됨") }
deinit { print("A 해제됨") }
}
class B {
var a: A?
init() { print("B 생성됨") }
deinit { print("B 해제됨") }
}
이렇게 `weak` 혹은 `unowned` 를 사용하면 됨
`weak`을 사용하면 ARC가 자동으로 메모리를 해제할 수 있음
즉, `weak`, `unowned`는 순환참조가 발생할 수 있는 경우 사용하면 좋음
weak | unowned | |
ARC참조 카운트 증가 여부 | 증가 안함 | 증가 안함 |
nil 허용 여부 | 가능 (Optional) | 불가능 (Non-Optional) |
객체가 해제되면? | 자동으로 nil로 변경됨 | 해제된 메모리를 참조하면 Crash 발생 |
언제 사용함? | 객체가 nil이 될 수 있는 경우 | 객체가 nil이 될 수 없는 경우 |
공부는 하는데 기초지식이 없으니 메모리 겁나 어렵네.. 더 파봐야겠음.