카테고리 없음

swift 캡처 참조 타입에 대해 배워보자(강한참조, 약한참조, 순환참조)

kingarthur 2024. 7. 16. 12:19

캡쳐 리스트

캡쳐링을 하는 값의 참조 규칙을 캡쳐리스트를 통해서 정해줄 수 있습니다.

클로저의 캡쳐리스트 내에 정의하면 캡쳐링할 때 본래의 타입에 맞게 캡쳐링되도록 사용되게 할 수 있습니다.

즉, 값 타입은 클로저가 생성될 시점의 값이 copy되어지고,

참조 타입은 클로저가 호출되는 시점에 참조되어 사용되도록 사용되어지게 합니다.

캡쳐리스트는 클로저 in 앞에 [ ] 내에 넣는 형식으로 작성합니다.

 

값타입 참조타입 예시

var x = 0
var y = 0

let someClosure = { [x] in
    print("in closure.x: \(x)")
    print("in closure.y: \(y)")
}

x = 20
y = 20

someClosure()
//in closure.x: 0
//in closure.y: 20

 

위 예시처럼,

y는 값 타입이지만, 캡쳐링되면서 참조하게 되어 외부에서 변동된 값이 반영됩니다.

x도 값 타입이지만, 캡쳐링할 때 캡쳐리스트 [ ]에 넣게 되면서 값이 0으로 복사되어 캡쳐링된 것을 확인할 수 있습니다.

 

참조타입 참조규칙 

class someClass {
    var value = 0
}

var x = someClass()
var y = someClass()

let someClosure = { [x] in
    print("in clousre x.value: \(x.value)")
    print("in clousre y.value: \(y.value)")
}

x.value = 10
y.value = 10

someClosure()
//in clousre x.value: 10
//in clousre y.value: 10

 

x, y 모두 참조타입이며,

 

캡쳐리스트에 넣은 x도, 넣지않은 y도 모두 참조되어 사용되는 것이 확인됩니다.

 


예시2:

캡쳐하려는 값이 참조 타입일 때, 메모리 참조 규칙을 정할 수 있습니다.

예시1처럼 아무것도 적지 않으면 기본적으로 strong이 되고 weak, unowned를 설정할 수 있습니다.

strong참조가 되었을 때 강한순환참조가 발생하여 메모리누수가 되는 상황이 있는데, 이를 막기 위해 weak unowned를 사용합니다.

weak참조와 unowned참조 모두 인스턴스의 참조카운트를 증가시키지 않고 참조하지만,

weak는 사용시점에 메모리카운트가 0일 때 nil을 반환시키는 optional로 사용(?로 unwrapping)하는 반면에

unowned는 메모리카운트가 0이더라도 optional로 사용하지 않아 댕글링포인터를 참조해 충돌이 일어날 가능성이 생겨납니다.

그래서 unowned로 사용을 하려면 사용시점에 절대 해제되지 않을거라고 판단이 되는 곳에서만 사용해야 합니다.

 

class someClass {
    var value = 0
}

var x: someClass? = someClass()
var y = someClass()

let someClosure = { [weak x, unowned y] in
    print("in clousre x.value: \(String(describing: x?.value))")
    print("in clousre y.value: \(y.value)")
}

x = nil
y.value = 10

someClosure()
//in clousre x.value: nil
//in clousre y.value: 10

 

 

강한 참조 (Strong Reference)

  • 해당 인스턴스의 소유권을 가진다.
  • 자신이 참조하는 인스턴스의 retain count를 증가시킨다.
  • 값 지정 시점에 retain이 되고 참조가 종료되는 시점에 release가 된다.
  • 선언할 때 아무것도 적어주지 않으면 default로 strong이 된다.
var test = Test() // retain count 1 증가
test = nil // retain count가 1 감소되어 0이 되면서 메모리 해제됨

약한 참조 (Weak reference)

  • 해당 인스턴스의 소유권을 가지지 않고, 주소값만을 가지고 있는 포인터 개념이다.
  • 자신이 참조하는 인스턴스의 retain count를 증가시키지 않는다. release도 발생시키지 않는다.
  • 자신이 참조는 하지만 weak메모리를 해제시킬 수 있는 권한은 다른 클래스에 있다.
  • 메모리가 해제될 경우 자동으로 레퍼런스가 nil로 초기화를 해준다.
  • weak 속성을 사용하는 객체는 항상 optional타입 이어야 한다.(해당 객체가 nil일수 있기 때문) 
weak var test = Test() // 객체가 생성 되지만 weak이기 때문에 객체가 해제되어 nil이 됨

미소유 참조 (Unowned reference)

  • 해당 인스턴스의 소유권을 가지지 않는다.
  • 자신이 참조하는 인스턴스의 retain count를 증가시키지 않는다.
  • nil이 될 수 없다. optional로 선언 되어서는 안된다.

 

 

  • 댕글링 포인터(Dangling pointer)? 원래 바라보던 객체가 해제되면서 할당되지 않은 공간을 바라보는 포인터
unowned var test = Test() // 객체 생성과 동시에 해제되고 댕글링 포인트만 가지고 있음. 에러남.

weak 와 unowned의 차이

weak는 객체를 계속 추적하면서 객체가 사라지게 되면 nil로 바꾼다.
하지만, unowned는 객체가 사라지게 되면 댕글링 포인터가 남는다.
이 댕글링 포인터를 참조하게 되면 crash가 나는데, 이 때문에 unowned는 사라지지 않을거라고 보장되는 객체에만 설정 하여야 한다.

어느 상황에 쓰이는가?

  • strong : 레퍼런스 카운트를 증가시켜 ARC로 인한 메모리 해제를 피하고, 객체를 안전하게 사용하고자 할 때 쓰인다.
  • weak : retain cycle에 의해 메모리가 누수되는 문제를 막기 위해 사용되며, 대표적으로 delegate패턴이 있다.
  • unowned : 객체의 라이프사이클이 명확하고 개발자에 의해 제어 가능이 명확한 경우, weak optional 타입 대신 사용하여 좀 더 간결한 코딩이 가능하다.

약한 참조가 필요한 경우 weak키워드만을 사용하고, guard let(또는 if let) 구문을 통해 안전하게 옵셔널을 추출하는 것을 권장한다.

순환 참조

  • 서로가 서로를 소유하고 있어 절대 메모리 해제가 되지 않는다는 것을 말한다
  • ARC가 편하게 메모리 관리를 해주지만 자칫 잘못하면 순환참조가 발생할 수 있다.
    예시 - delegate 패턴
    delegate를 하기 위해서는 일을 시키는 객체와 일을 하는 객체 두 개가 무조건 있어야 하는데,
    아래의 코드로 객체를 연결 시켜줌으로 인해 FirstViewController와 SecondViewController는 서로를 소유하는 상황이 된다.
vc.delegate = self
  • 즉, FirstViewController에서 SecondViewController객체를 만듦으로 SecondViewController를 소유하고,
    SecondViewController의 delegat를 FirstViewController로 연결 해줌으로써 FirstViewController를 소유하는 순환 참조가 된다.

이 문제를 해결하기 위해서는 SecondViewController의 delegate에 weak를 붙여주면 된다.

weak var delegate: FirstViewProtocol?

그렇게 되면 FirstViewController만 SecondViewController를 소유하기 때문에 순환참조가 발생하지 않는다.

reference

srkim Develop Blog