[Swift 기능] Timer 설정 방법 2가지 - 클로져 함수 / selector
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 함수를 쓰는 것도 좋은 것 같다. 이 부분은 어떤 것을 쓰는게 코드적으로, 성능적으로 좋을지는 차차 알아가보도록 하자.