DispatchQueue로 throttle/debounce 실현

11302 단어 SwiftSwift3.0

소개



UITextField등으로 문자가 입력되었을 때에, 그 캐릭터 라인을 기초로 API를 두드리는 일이 있다고 생각합니다.
그 때에 서버 부하등도 고려해, API를 두드리는 빈도를 좁히거나 한다고 생각합니다.
RxSwiftthrottle/debounce )에서 RxSwift를 도입 할 수없는 일도 있을까 생각합니다.
그러한 경우, scheduledTimer(timeInterval:target:selector:userInfo:repeats:) 등을 이용해 구현하게 된다고 생각합니다만, timer를 관리하거나 timer가 실행되었을 때에 핸들 하는 메소드를 구현하거나 하지 않으면 안됩니다.
그래서 DispatchQueue 의 extension으로서 throttle/debounce 와 같은 구현을 호출할 수 있도록 해 갑니다.

이용시 샘플



UIViewController 내에서, UITextField 가 전회의 입력시로부터 일정의 초수 이내에 입력이 없었을 때에, text 를 print 하는 구현은 이하와 같이 구현할 수 있게 됩니다.
class ViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    //0.5秒ごとに処理をglobalQueueで行うClosureをPropertyとして保持
    let debounceAction = DispatchQueue.global().debounce(delay: .milliseconds(500))

    override func viewDidLoad() {
        super.viewDidLoad()
        //UITextFieldのtextの変更通知の監視登録
        let nc = NotificationCenter.default
        nc.addObserver(self, 
              selector: #selector(ViewController.textFieldTextDidChange(_:)),
                  name: .UITextFieldTextDidChange,
                object: nil)
    }

    @objc private func textFieldTextDidChange(_ notification: Notification) {
        debounceAction { [unowned self] in
            //前回の入力から0.5秒以内に入力がなかった際にtextをprint
            print(self.textField.text)
        }
    }
}

throttle 구현



먼저 throttle을 구현합니다.
throttle은 イベント発生中であっても、一定時間は同じ処理を実行しない 라는 거동입니다.DispatchTimeInterval 를 인수로, 実行する処理を引数とした関数 를 반환합니다.
함수의 내부에서는, 마지막에 실행된 시간을 보관 유지하는 lastFireTime 를 정의해, 현재의 시각을 대입합니다.
반환 값이 되는 함수에서 lastFireTime은 참조 전달에 self와 delay를 캡처 목록으로 하고 현재 시간 +delay를 할당한 deadline을 정의합니다.DispatchQueue.asyncAfter(deadline:qos:flags:execute:) 를 사용하여 using 클로저에서 현재 시간과 마지막으로 실행된 시간 +delay 시간을 비교합니다.
현재 시각이 진행되고 있는 경우에, lastFireTime 를 갱신해, 인수로 받고 있던 action 를 실행합니다.
extension DispatchQueue {
    func throttle(delay: DispatchTimeInterval) -> (_ action: @escaping () -> ()) -> () {
        var lastFireTime: DispatchTime = .now()

        return { [weak self, delay] action in
            let deadline: DispatchTime = .now() + delay
            self?.asyncAfter(deadline: deadline) { [delay] in
                let now: DispatchTime = .now()
                let when: DispatchTime = lastFireTime + delay
                if now < when { return }
                lastFireTime = .now()
                action()
            }
        }
    }
}

위의 구현과 UITextField의 text를 결합하면 다음과 같은 동작이 발생합니다.



debounce 구현



다음으로 debounce를 구현합니다.
debounce는 前回のイベント発生後から一定時間内に同じイベントが発生するごとに処理の実行を一定時間遅延させ、一定時間イベントが発生しなければ処理を実行する 라는 거동입니다.
기본적인 구현은 throttle과 매우 유사합니다.
throttle과의 차이점은 asyncAfter를 구현하기 전에 lastFireTime에 현재 시간을 다시 할당하는 부분입니다.
extension DispatchQueue {
    func debounce(delay: DispatchTimeInterval) -> (_ action: @escaping () -> ()) -> () {
        var lastFireTime: DispatchTime = .now()

        return { [weak self, delay] action in
            let deadline: DispatchTime = .now() + delay
            lastFireTime = .now()
            self?.asyncAfter(deadline: deadline) { [delay] in
                let now: DispatchTime = .now()
                let when: DispatchTime = lastFireTime + delay
                if now < when { return }
                lastFireTime = .now()
                action()
            }
        }
    }
}

위의 구현과 UITextField의 text를 결합하면 다음과 같은 동작이 발생합니다.



마지막으로



이와 같이 throttle/debounce 와 같은 구현을 할 수 있으므로, 꼭 이용해 보세요.

좋은 웹페이지 즐겨찾기