관리 메뉴

사과하는 제라스

[Swift 기능] Timer 설정 방법 2가지 - 클로져 함수 / selector 본문

제라스의 Swift 공부/Swift 기능

[Swift 기능] Timer 설정 방법 2가지 - 클로져 함수 / selector

Xerath(제라스) 2023. 9. 10. 20:37

목차

    728x90
    반응형

    Timer를 써보면 schedule을 할당해주는 방법이 여러가지가 존재한다. 그 중 2가지를 소개하고자 한다.

     

    구현을 하다보면 정해진 간격의 시간마다, 정해진 시간동안 어떤 일을 수행하는 동작을 구현해야 할 때가 생긴다.

    이러한 것을 돕는 것이 바로 timer다.

    이전에 부스트캠프를 하면서도 사용했던 것인데 이번 공부를 하면서 가볍게라도 정리를 해보자!

    1. 클로져 함수

    @IBAction func startButtonDidTapped(_ sender: UIButton) {
        self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [self] _ in
    
            if number > 0 {
                number -= 1
                self.slider.setValue(Float(number)/Float(60), animated: true)
                mainLabel.text = "\(number) 초"
            } else {
                number = 0
                mainLabel.text = "초를 선택하세요"
                timer?.invalidate()
                let systemSoundID: SystemSoundID = 1016
                AudioServicesPlaySystemSound(systemSoundID)
    
            }
        }
    }

    이 코드처럼 클로져 함수를 줄 수 있다. 이때 유의해야 할 점이 바로 순환 참조이다.

    마치 이런 형태가 될 수 있는데 timer라는 변수를 만들고 그 변수는 Timer 라이브러리로부터 만들고 Timer에서는 클로져 함수를 참조한다.

    근데, 이때 클로져 함수를 위 코드처럼 [self]를 해두면 timer를 다시 강한 참조를 한다. 이렇게 되면 순환참조가 발생하고 코드가 계속 사라지지 않는 이슈가 발생할 수 있다.

    이를 방지하기 위해서는 다음과 같이 timer를 weak로 약한 참조로 생성을 해야한다.

    weak var timer: Timer?

    이렇게 하면 timer 변수가 Timer를 약한 참조를 하기 때문에 순환 참조를 방지할 수 있다. 다음 그림처럼 볼 수 있다.

    만약 weak으로 안했다면 클로져 함수 내 [self]를 [weak self]로 주는 방법도 있다.

    2. selector

    이 방법을 쓰면 순환참조에 대한 고민을 좀 덜 수 있다. 다음과 같은 코드를 작성하는 것이다.

    @IBAction func startButtonDidTapped(_ sender: UIButton) {
            self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(doSomethingAfter1Sec), userInfo: nil, repeats: true)
    }
    
    @objc func doSomethingAfter1Sec() {
        if number > 0 {
            number -= 1
            self.slider.setValue(Float(number)/Float(60), animated: true)
            mainLabel.text = "\(number) 초"
        } else {
            number = 0
            mainLabel.text = "초를 선택하세요"
            timer?.invalidate()
            let systemSoundID: SystemSoundID = 1016
            AudioServicesPlaySystemSound(systemSoundID)
    
        }
    }

    이렇게 쓰는 방법인데 selector로 objc 함수를 주고(selector에는 항상 objc 함수를 할당해야 한다고 함, #selector를 쓰고 그 안에 실행할 함수이름을 적어넣으면 됨.) 해당 함수를 주어진 interval마다 반복하는 방식이다.

     

    이와 같은 방식으로 짜면 objc함수인 doSomethingAfterSec에서 timer라는 멤버 변수를 통해 접근을 할 수 있기에 따로 timer를 매개변수로 넘겨주지 않아도 되지만 만약 timer를 매개변수로 넘겨준다면 서로 순환 참조를 할 수 있기에 문제가 생길 수도 있다. 

    다음은 순환 참조가 생길 가능성이 있는 방식의 구현이다.

     

    let timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(doSomethingAfter1Sec(_:)), userInfo: nil, repeats: true)
        
    @objc func doSomethingAfter1Sec(_ timer: Timer) {
        if number > 0 {
            number -= 1
            self.slider.setValue(Float(number)/Float(60), animated: true)
            mainLabel.text = "\(number) 초"
        } else {
            number = 0
            mainLabel.text = "초를 선택하세요"
            timer?.invalidate()
            let systemSoundID: SystemSoundID = 1016
            AudioServicesPlaySystemSound(systemSoundID)
    
        }
    }

    이렇게 하면 userInfo를 통해 timer의 요소들에 접근을 해서 사용할 수 있다는 장점이 있기는 하다. 대신 순환 참조를 방지하도록 타이머 무효화를 잘해주면 된다.

     

     

     

    일단 클로져 함수를 순환 참조 고민까지 해가면서 하기에는 부담이 된다면 selector 함수를 쓰는 것도 좋은 것 같다. 이 부분은 어떤 것을 쓰는게 코드적으로, 성능적으로 좋을지는 차차 알아가보도록 하자.

    728x90
    반응형