weak 사용법

  • ARC 공식문서weak에 대해서만 발췌하고 살을 붙인 포스팅입니다

  • ARC 자체 번역본은 여기 (왜 티스토리에 했을까...)

  • IBOutlet과 관련한 내용은 여기

  • (약한 참조에는 unowned라는 것도 있는데 이건 추후에 별도로 다루겠다)


🐻‍❄️ ARC 메커니즘

weak에 대해 알아보기 전에 ARC에 대해 간단하게 상기시키고 갑시다

일반적으로 값타입은 Stack에 저장되며(어쩌면 100%..?) 인스턴스가 생성된 scope를 벗어나면 자연히 해제됩니다

하지만, 참조타입은 이와 다르게 인스턴스가 생성된 scope 밖으로 나갈 수 있기 때문에 코드를 떠돌며 어쩌면 영영 해제되지 않는(?) 삶에 처할지도 모릅니다

참조타입 인스턴스의 해제를 위해, Swift에서는 ARC(Automatic Reference Counting)라는 메커니즘을 사용합니다
ARC가 판단하기에 어떤 참조타입 인스턴스가 더 이상 필요없는 것 같으면 자동으로 해제시킵니다

더 이상 필요없다고 판단?
해당 인스턴스의 강한참조의 개수를 tracking하여 개수가 0이 되는지 감시합니다
Swift에는 강한참조와 약한참조가 존재하는데, 일반적으로 우리가 class타입 인스턴스를 생성하고 그 주소값을 별도의 표시없이 어딘가에 저장하는 행위가 강한참조에 해당됩니다

아래 코드에서는 class 인스턴스를 생성하고 강한참조하다가
참조를 해제하여 인스턴스가 ARC에 의해 자동해제되게 만드는 예제입니다

class ABC {
	let a = 1
}

let temp: ABC? = ABC() // temp는 ABC인스턴스를 강한참조한다
temp = nil // 강한참조가 모두 사라져 ARC에 의해 인스턴스 해제

그럼 약한참조는 무엇인가?

🐧 weak란?

약한참조는 weak라는 키워드를 사용합니다
weak를 사용한 약한참조는 ARC가 tracking하지 않으므로 해당 참조타입 인스턴스로 약한참조만 존재한다면 더 이상 필요없는 것으로 판단하여 해제시킵니다

또한, weak로 선언했던 참조 변수들의 값을 전부 nil로 바꿔버립니다

아래 예제를 위와 비교해봅시다

class ABC {
    var a = 1
    
    deinit {
        print("goodbye ABC")
    }
}

weak var temp: ABC? = ABC() // 바로 해제. "goodbye ABC" 출력

아니 바로 해제될거면 왜 쓰는거지?
이제, 어떤 경우에 weak가 필요한지 알아보자

🐯 weak는 언제 사용되는가

결론적으로, 해당 인스턴스의 메모리 해제에 관여하고 싶지 않을 때이다

우선, 순환참조가 발생할 때

어떤 참조타입 인스턴스의 값을 읽거나 변경하려면(=접근하려면) 해당 인스턴스를 참조해야 한다
그러다보면, 두 인스턴스가 서로의 값에 접근하고 싶은 경우가 생긴다

johnunit4A 인스턴스는 서로의 값에 접근하고 싶어하기에 참조값을 프로퍼티로 각각 저장하였다

이게 왜 대참사인지 알아보자
johnuint4A가 이제 필요가 없어져서 nil처리하였다고 치자.
하지만, 아래와 같이 참조를 해제하여도 서로를 참조하며 참조 개수가 0이 아니라서 ARC가 해제시키지 않는다

이렇게 서로를 참조하는 것을 순환참조라고 하며
순환참조가 발생하거나 발생할 가능성이 있는 경우, 두 참조 중 적어도 하나를 약한참조로 설정해야 한다

순환참조가 아니더라도

하나의 참조타입 인스턴스를 여러 곳에서 참조하는 경우를 생각해봅시다
이 인스턴스가 해제되려면 모든 참조가 해제되어야 합니다

우선, 어떤 Button을 ViewViewController에서 참조하는 예시를 살펴봅시다

//해결이 필요한 코드 : 어떤 인스턴스의 생사여탈권을 여러 곳에서 가짐
class View {	
    var button: UIButton? = UIButton()  //View -> button 참조
}
var view: View? = View()

class ViewController {
    var button: UIButton?  //ViewController -> button 참조
}
var viewController = ViewController()
viewController.button = view.button

view = nil // view는 사라졌으나 ViewController에 의해 button은 살아있는 상태

하지만, 우리는 View가 사라지면 Button 또한 사라지도록 하고 싶습니다.
이를 위해, ViewController는 Button의 해제에 관여하지 않도록 weak로 설정해줍니다

// 해결
class ViewController {
    weak var button: UIButton? 
}

view = nil 
// view와 함께 button도 해제됨. 
// ViewController의 button은 자동으로 nil로 변경

@IBOutlet

@IBOutletweak를 사용하는 이유는
View와 같은 상위 객체가 이미 강한참조하고 있는 상태이며, 보통은 상위 객체와 함께 사라지기를 원하기 때문이다

이를 도식화하면 아래와 같다

ViewController에서 버튼을 강한참조하는 경우
이와 같이 View와 함께 버튼이 사라지지 않는다

버튼을 약한참조하는 경우
상위 객체인 View와 함께 해제된다

사실, 예시처럼 하나의 scene에서 ViewController를 두고 View가 먼저 해제되는 경우는 잘 없을 것 같지만 어디까지나 개념 이해를 돕는 예시니😂
실제로는, 서로 다른 scene을 참조하는 경우 navigating을 하며 해제가 빈번하니 고려해야 할 것 같다

🦊 weak 사용법

아래 두 가지를 지켜주지 않으면 컴파일 에러가 발생합니다

1. Optional타입으로 정의할 것.
2. 변수(var)로 정의할 것

위에서 언급한대로, 약한참조는 자신이 가리키던 인스턴스가 해제되면 자동으로 값이 nil로 바뀝니다

그러므로 Optional 타입이어야 하고, 런타임에서 변경되므로 변수여야 합니다

Optional Binding이 번거로우면 unowned라는 약한참조도 있는데 이건 특수한 상황에서만 사용가능하므로 지금은 패스...

좋은 웹페이지 즐겨찾기