Skip to content

ARC 강한참조 순환과 메모리누수  #49

@zzangzzangguy

Description

@zzangzzangguy

강한 참조 순환과 메모리 누수

🌀 강한 참조 순환이란?

강한 참조 순환은 두 개 이상의 인스턴스가 서로를 강한 참조로 가리키고 있어서, 참조 횟수가 0이 되지 않는 상황을 말함.

이런 상황이 발생하면 ARC가 메모리에서 해제할 수 없게 되고, 메모리 누수(Memory Leak)가 발생하게 된다... 😱

🏘️ 강한 참조 순환 예시

class Member {
    let name: String
    var registeredGym: Gym?

    init(name: String) {
        self.name = name
    }

    deinit {
        print("\(name) Member deinitialized")
    }
}

class Gym {
    let name: String
    var registeredMember: Member?

    init(name: String) {
        self.name = name
    }

    deinit {
        print("\(name) Gym deinitialized")
    }
}

// 인스턴스 생성
var gilbong: Member? = Member(name: "Gilbong")
var muscleMania: Gym? = Gym(name: "MuscleMania")

// 강한 참조 순환 발생
gilbong?.registeredGym = muscleMania
muscleMania?.registeredMember = gilbong

// 강한 참조 순환으로 인해 메모리 해제되지 않음
gilbong = nil
muscleMania = nil

위 코드에서 Member 인스턴스 gilbongGym 인스턴스 muscleMania가 서로를 강한 참조로 가리키고 있음.

그래서 gilbongmuscleManianil로 설정해도 참조 횟수가 0이 되지 않아 메모리에서 해제되지 않는다. 메모리 누수가 발생하는 것!

💪 강한 참조 순환 해결하기

강한 참조 순환을 해결하려면 강한 참조 중 하나를 약한 참조나 미소유 참조로 바꿔주면 된다.

  • 약한 참조(Weak Reference): 참조 횟수를 증가시키지 않는 참조. 참조하던 인스턴스가 메모리에서 해제되면 자동으로 nil이 됨.
  • 미소유 참조(Unowned Reference): 참조 횟수를 증가시키지 않지만, 참조하던 인스턴스가 메모리에서 해제되는 시점을 관리하지 않는다.

🌿 약한 참조 사용하기

class Member {
    let name: String
    var registeredGym: Gym?

    init(name: String) {
        self.name = name
    }

    deinit {
        print("\(name) Member deinitialized")
    }
}

class Gym {
    let name: String
    weak var registeredMember: Member?

    init(name: String) {
        self.name = name
    }

    deinit {
        print("\(name) Gym deinitialized")
    }
}

Gym 클래스의 registeredMember 프로퍼티를 weak 키워드를 사용해 약한 참조로 선언.

이제 gilbongnil이 되면 Member 인스턴스는 메모리에서 해제되고, muscleManiaregisteredMember 프로퍼티도 nil이 된다.

😎 미소유 참조 사용하기

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit {
        print("\(name) 메모리에서 해제")
    }
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit {
        print("\(number) 카드 메모리에서 해제")
    }
}

CreditCard 클래스의 customer 프로퍼티를 unowned 키워드를 사용해 미소유 참조로 선언.

미소유 참조는 참조 횟수를 증가시키지 않지만,

참조하던 인스턴스가 메모리에서 해제되는 시점을 관리하지 않기 때문에 주의해서 사용해야 한다

🔄 클로저에서의 강한 참조 순환

class ExerciseMachine {
    let name: String
    let description: String?

    lazy var instructions: () -> String = { [unowned self] in
        if let description = self.description {
            return "[\(self.name)] \(description) - 이렇게 사용하세요!"
        } else {
            return "[\(self.name)] 설명 없음"
        }
    }

    init(name: String, description: String? = nil) {
        self.name = name
        self.description = description
    }

    deinit {
        print("\(name) 기구 메모리에서 해제")
    }
}

let treadmill = ExerciseMachine(name: "런닝머신", description: "달리는 운동기구")
print(treadmill.instructions())// "[런닝머신] 달리는 운동기구 - 이렇게 사용하세요!" 출력

위 코드에서는 클로저의 캡처 리스트에 [unowned self]를 사용해 self를 미소유 참조로 캡처.
이렇게 하면 클로저가 self를 강한 참조로 가리키지 않기 때문에 강한 참조 순환이 발생하지 않는다!

🏁 정리하기

  • 강한 참조 순환은 인스턴스들이 서로를 강한 참조로 가리키고 있어 메모리 누수가 발생하는 상황.
  • 강한 참조 중 하나를 약한 참조나 미소유 참조로 바꿔주면 강한 참조 순환을 해결할 수 있음.
    • 약한 참조는 참조하던 인스턴스가 메모리에서 해제되면 자동으로 nil.
    • 미소유 참조는 참조하던 인스턴스가 메모리에서 해제되는 시점을 관리하지 않는다
  • 클로저에서 self를 캡처해 사용할 때도 강한 참조 순환이 발생할 수 있다.
    • 캡처 리스트에서 self를 약한 참조나 미소유 참조로 지정해 주면 된다.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions