제라스의 iOS 공부/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
반응형