인프런 커뮤니티 질문&답변

mminy62님의 프로필 이미지
mminy62

작성한 질문수

앨런 iOS 앱 개발 (15개의 앱을 만들면서 근본원리부터 배우는 UIKit) - MVVM까지

활용-1 앱 / 코드 설정하기 (활용 앱10강)

타이머 앱 강한 참조 사이클 해결 방법에 있습니다!

해결된 질문

작성

·

65

0

안녕하세요!

타이머 앱 코드 설명중(11강 15:27)에서 강한 참조 사이클 부분에 궁금증이 생겨 질문 드립니다!

 

참조 사이클을 방지하기 위해서

  • weak var timer: Timer?

  • weak self

위 2개 방식 중에서 [self]로 코드 사용 편리함을 위해
weak var timer로 택하셨다고 했는데

Q: timer 변수는 의미상 직접적으로 Timer를 참조해야 맞을 것 같은데... 어떤 방법이 좋은 것인지 헷갈려서요!

둘다 해결 방법인데,
어떤 상황에 어떤 방법을 채택해야할지 궁금해서 질문 남겼습니다!

답변 2

1

앨런(Allen)님의 프로필 이미지
앨런(Allen)
지식공유자

아, 네 저도 그림을 그려서 다시 생각해보니까..
제가 좀 잘 못 설명을 드린 것 같네요!

결론부터 말씀드리면,
이 경우에, 클로저 내부에서 반드시 [weak self]로 사용하셔야
(어떠한 경우에도) 메모리 누수가 발생하지 않습니다.


일단 중요한 포인트가 한개 있는데, Timer객체의 경우,
Timer를 생성하고 스케줄링하면, Timer 객체는 런루프(RunLoop)에 추가되기 때문에, 런루프가 Timer에 대한 강한 참조를 유지하게 됩니다. (런루프가 강하게 가리키게 됩니다.)
(런루프 관련된 내용은 뒤쪽 강의에서 설명해 드려요.)

(쉽게 말씀드리면, Timer라는 객체는 그 특성이 자체가.. 내부적인 메커니즘에 의해 그 객체가 계속 반복적으로 동작하게 하기 위해서 관리되는 내부적인 동작구조가 있어서, 그것이 Timer를 관리하게 됩니다.)


그림으로 대략 생각해보자면, 아래 그림과 같다고 보시면 됩니다.

스크린샷 2024-08-27 오후 6.01.53.png

런루프라는 내부 메커니즘에 의해 Timer가 관리 된다는 것이죠.


따라서, Timer의 경우 (반복적으로 사용하기로 했다면)
속성을 var로 선언하던, weak var로 선언하던 지 간에 상관없이 timer를 invalidate시키기 전까지는 timer객체가 메모리에서 살아 있을 수 밖에 없습니다.
(invalidate()를 호출해야만 Timer객체가 해제가 됩니다.)

아래의 그림과 같은 방식으로 런루프에 의해서 관리가 되니, timer속성을 var로 선언하던, weak var로 선언하던 일단 Timer객체는 메모리에 있을 수 밖에 없습니다.

스크린샷 2024-08-27 오후 6.07.01.png



그리고 Timer에서 클로저를 사용하기 때문에... 또 Timer가 내부에서 클로저를 소유하게 됩니다.

스크린샷 2024-08-27 오후 6.10.08.png



따라서, 이 경우에 클로저 내부에서 self를 사용할때 weak self로 사용하지 않으면 메모리 누수가 발생할 수 있는 가능성이 높아집니다.

스크린샷 2024-08-27 오후 6.02.10.png


위의 그림을 보시면 아시겠지만,
Timer의 invalidate( ) 메서드를 호출하면 타이머가 해제가 될 수 있지만, invalidate( ) 을 호출하지 않는 경우엔 무조건 메모리 누수가 발생할 수 밖에 없으니, 가능성을 완전히 배제하시려면.. [weak self]로 선언해 주는 것이 맞습니다.
(invalidate을 호출하지 않으면, Timer도 살아있고, 클로저도 살아 있고, 클로저가 강하게 가리키는 뷰컨트롤러도 해제가 되지 않기 때문에, 이 경우 무조건 [weak self]로 선언하는게 더 맞다고 보시면 됩니다.)


따라서, Timer객체를 사용해서, 클로저에서 반복하는 코드를 사용하는 경우,
아래의 코드처럼 weak self와 옵셔널 바인딩 (guard let self = self else)을 같이 사용하시면 됩니다.

timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
            
     guard let self = self else { return }
            
     if number > 0 {
         number -= 1
         print(Float(number) / Float(60))
         slider.value = Float(number) / Float(60)
         mainLabel.text = "\(number) 초"
     } else {
         mainLabel.text = "초를 선택하세요"
         number = 0
         timer?.invalidate()
         AudioServicesPlaySystemSound(SystemSoundID(1000))
     }
}
스크린샷 2024-08-27 오후 7.51.56.png



그래서 결론적으로는,
(1) 타이머의 경우 런루프라고 하는 내부적인 메커니즘에 의해 관리 되므로 사실 정확하게는 var로 선언하던, weak var로 선언하던 크게 상관은 없고요. (어차피 var로 선언하더라도, invalidate 메서드 호출시 Timer는 무조건 해제가 될테니까요.)

(2) 그런데, 클로저에서는 [weak self]로 선언을 안해주면 Timer에서 invalidate를 호출 안해주면 무조건 뷰컨트롤러까지 해제가 안되어서 메모리 누수가 발생할 수 있으니, 클로저에서는 반드시 [weak self]로 선언해주셔야 합니다.



혹시나, 궁금증이 있으시면, 추가적으로 질문주셔도 됩니다.
감사합니다. :)

0

mminy62님의 프로필 이미지
mminy62
질문자

설명으로 이해한바를 정리하자면,
Timer 객체의 속성?으로 인해서 런루프에 추가되고, 강한 참조가 일어나기 때문에 확실하게 메모리 누수를 방지하려면 "[weak self] 가 정확하다"로 이해했습니다!

(그림 설명까지 감사합니다 :))

혹시 아래처럼, Timer 객체가 아닌 제가 만든 Person 객체가 있다면, 여기서는 어떤 방식을 주로 사용하게 될까요?
weak var person / [weak self] 방식 중 여기서는 둘다 해결방법이 될 것 같은데,
코드 의미상으로는 person의 강한 참조 & weak self가 조금 더 맞지 않을까 해서 질문 드립니다!

class ViewController: UIViewController {     
  weak var person: Person?      
  ......     
  person = Pereson.someTask(....) {
   [self] _ in    ......     
  } 
}

 

앨런(Allen)님의 프로필 이미지
앨런(Allen)
지식공유자

예시로 쓰신 것과 일반적인 상황에서, weak var person은 아예 해결책이 될 수가 없습니다 !
Person인스턴스가 생성되자마자 사라지니까요!

 

이때는 무조건 [weak self] 만 가능합니다.

일반적으로 weak var를 사용하는 경우는, 객체와 객체가 서로가 서로를 가리키는 경우, 한쪽 객체에서 이미 강하게 가리키는 경우엔 사용 가능합니다.

예를 들어서 코드 아래처럼요.

class Person {
    var vc: ViewController
    
    init(vc: ViewController) {
        self.vc = vc
    }
}


class ViewController {
    weak var person: Person?
}


let vc = ViewController()
let person = Person(vc: vc)

이런 코드 같은 경우엔 Person객체가 이미 ViewController객체를 강하게 가리키고 있으니, 강한 순환참조를 방지하기 위해 weak var 를 사용하긴 하지만,

객체와 클로저를 사용하는 경우엔, 클로저 내부에선 객체를 가리킬 때만 [weak self]를 쓴다고 보셔야 할 것 같아요. :)

mminy62님의 프로필 이미지
mminy62
질문자

아 바로 사라져버리겠네요 ㅜㅜ

예시까지 답변 감사합니다!!

mminy62님의 프로필 이미지
mminy62

작성한 질문수

질문하기