Swift문법 - (25)메모리 관리ARC

Youth·2022년 10월 19일
0

swift문법공부

목록 보기
25/27
post-thumbnail

스위프트의 메모리 관리 모델

  • MRC(수동 RC관리)와 ARC(자동 RC)
class Dog {
    var name: String
    var weight: Double
    
    init(name: String, weight: Double) {
        self.name = name
        self.weight = weight
    }
    
    deinit {
        print("\(name) 메모리 해제")
    }
}

인스턴스를 하나씩 찍어내면 heap영역에 choco인스턴스 bori인스턴스가 올라감

→ 옵셔널 타입으로 설정한 이유는 nil을 할당할 수 있어야하기 때문

var choco: Dog? = Dog(name: "초코", weight: 15.0)
var bori: Dog? = Dog(name: "보리", weight: 10.0)

choco의 RC(레퍼런스 카운트)는 1이 되고 bori의 RC도 1이 된다

choco와 bori변수는 메모리 주소를 가지고 있는데 nil로 할당해주면 RC가 0이 된다

choco = nil   // RC: 0
//release(choco)
bori = nil    // RC: 0
//release(bori)

→ deinit이 호출된다

Memory Leak(메모리 누수) 현상

class Dog {
    var name: String
    var owner: Person?
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("\(name) 메모리 해제")
    }
}

class Person {
    var name: String
    var pet: Dog?
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("\(name) 메모리 해제")
    }
}

인스턴스를 각자만들면

  1. bori변수는 bori의 RC를 1증가시킴
  2. gildong변수는 gildong의 RC를 1증가시킴
  3. bori의 owner가 gildong의 RC를 1증가시킴(gildong RC = 2)
  4. gildong의 pet이 bori의 RC를 1증가시킴(bori RC = 2)

이런 상황에서 bori변수와 gildong변수에 nil을 할당하더라도 RC가 1씩 줄어들어도

서로가 서로를 가리켜 메모리에서 해제되지 않는다

💡 객체가 서로를 참조하는 강한 참조 사이클로 인해

변수의 참조에 nil을 할당해도 메모리 해제가 되지 않는

메모리 누수(Memory Leak)의 상황이 발생

Memory Leak의 해결방안

  • RC를 고려하여, 참조 해제 순서를 주의해서 코드 작성 → 실수할 가능성이 높음 비추
  • Weak Reference (약한 참조)
    • 가리키지만 RC를 증가시키지 않는 방법

메모리 누수(Memory Leak)현상의 해결

  1. 약한참조
weak var owner: Person?
weak var pet: Dog?

→ Person을 가리키지만 RC를 증가시키지 않음

→ 약한 참조의 경우, 참조하고 있던 인스턴스가 사라지면, nil로 초기화 되어있음

gildong = nil
bori?.owner   // gildong만 메모리 해제시켰음에도 ===> nil

밸류(Value) 타입 캡처와 캡처리스트

  • 클로저는 자신이 사용할 외부의 변수를 캡처함
var num = 1

// 클로저를 변수에 담는 순간 클로저는 heap영역에 저장된다
// num이라는 변수를 캡처한다 -> 변수의 주소값을 가리키고 있다
let valueCaptureClosure = {
    print("밸류값 출력(캡처): \(num)")
}
num = 7
valueCaptureClosure()  // result : 밸류값 출력(캡처): 7

num = 1
valueCaptureClosure()  // result : 밸류값 출력(캡처): 1 

→ 만약에 캡쳐 리스트를 사용하게 되면 주소값이 아닌 값 자체를 복사해서 가지고 있다

// 이미 위에서 1이라는 num의 값을 복사해서 가지고 와 있는 상태
let valueCaptureListClosure = { [num] in
    print("밸류값 출력(캡처리스트): \(num)")
}

// num에 7을 할당하더라도 num 1을 복사해서 가지고 있기 때문에 1을 출력
num = 7
valueCaptureListClosure()      // 몇을 출력할까요? = 1

참조(Reference) 타입 캡처와 캡처리스트

class SomeClass {
    var num = 0
}

var x = SomeClass()
var y = SomeClass()

// x라는 변수를 가리킨다(RC를 증가시키지 않음)
print("참조 초기값(시작값):", x.num, y.num)

// 참조타입을 캡처리스트에 담는 순간 x자체를 가리키게된다(RC증가)
let refTypeCapture = { [x] in
    print("참조 출력값(캡처리스트):", x.num, y.num)
}

강한 참조 사이클 문제의 해결

  • weak의 사용
  • weak를 사용하게 되면 메모리가 해제가 되더라도 그 인스턴스를 가리키고 있기때문에 nil을 할당할 수 있어야한다
  • [weak z]라면 z라는 인스턴스가 nil일 가능성이 있기때문에 옵셔널 체이닝을 적용해서 z?.name이라고 해야한다(이때 name은 옵셔널의 가능성이 있어도 ?안붙임)
var z = SomeClass()

let refTypeCapture1 = { [weak z] in
    print("참조 출력값(캡처리스트):", z?.num)
}

⭐️객체 내에서 클로저의 사용⭐️

  • 일반적인 클로저의 사용(객체 내에서의 사용, self키워드)
class Dog {
    var name = "초코"
    func doSomething() {
        DispatchQueue.global().async {
            print("나의 이름은 \(self.name)입니다.")
        }
    }
}

✅클로저 내에서 객체의 속성 및 메서드에 접근 시에는 "self키워드"를 ⭐️반드시⭐️ 사용해야함✅

💡 무조건 self를 붙여야하는데 방식이 두가지가 있다 1. self.name 2. [self] in name

→ 비동기 처리를 진행할때 변수의 주소를 캡쳐하고 있으면 2번 쓰레드에서 독립적으로 작업을 할 수가 없게된다 그렇기 때문에 객체를 가리키고 있어야한다(강한참조가 필요)

캡처리스트 실제 사용 예시

class ViewController: UIViewController {
    
    var name: String = "뷰컨"
    
    func doSomething() {
        DispatchQueue.global().async {
            sleep(3)
            print("글로벌큐에서 출력하기: \(self.name)")
        }
    }
    
    deinit {
        print("\(name) 메모리 해제")
    }
}
func localScopeFunction() {
    let vc = ViewController()
    vc.doSomething()
} 
localScopeFunction()

함수 실행의 결과 → 글로벌큐에서 출력하기, 뷰컨, 뷰컨 메모리 해제

weak self라면 → 뷰컨 메모리 해제, 글로벌큐에서 출력하기: nil

profile
AppleDeveloperAcademy@POSTECH 1기 수료, SOPT 32기 iOS파트 수료

0개의 댓글