      - parameter data: Information package used to display UI blocker.
      */
     public final func startAnimating(_ data: ActivityData) {
-        guard state == .hidden else { return }
-
-        state = .waitingToShow
-        startAnimatingGroup.enter()
-        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(data.displayTimeThreshold)) {
-            guard self.state == .waitingToShow else {
-                self.startAnimatingGroup.leave()
-
-                return
-            }
-
-            self.show(with: data)
-            self.startAnimatingGroup.leave()
-        }
+        self.data = data
+        state.startAnimating(presenter: self)
     }
 
     /**
      Remove UI blocker.
      */
     public final func stopAnimating() {
-        _hide()
+        state.stopAnimating(presenter: self)
     }
 
     /// Set message displayed under activity indicator view.
     ///
     /// - Parameter message: message displayed under activity indicator view.
     public final func setMessage(_ message: String?) {
-        guard state == .showed else {
-            startAnimatingGroup.notify(queue: DispatchQueue.main) {
-                self.messageLabel.text = message
-            }
-
-            return
-        }
-
-        messageLabel.text = message
+        waitingToStartGroup.notify(queue: DispatchQueue.main) {
+            self.messageLabel.text = message
+        }
     }
 
     // MARK: - Helpers
 
-    private func show(with activityData: ActivityData) {
+    fileprivate func show(with activityData: ActivityData) {
         let containerView = UIView(frame: UIScreen.main.bounds)
 
         containerView.backgroundColor = activityData.backgroundColor

        return label
    }()

    private var state: State = .hidden
    private let startAnimatingGroup = DispatchGroup()

    /// Shared instance of `NVActivityIndicatorPresenter`.
    public static let sharedInstance = NVActivityIndicatorPresenter()

    }
}

private protocol NVActivityIndicatorPresenterState {
    func startAnimating(presenter: NVActivityIndicatorPresenter)
    func stopAnimating(presenter: NVActivityIndicatorPresenter)
}

private struct NVActivityIndicatorPresenterStateWaitingToStart: NVActivityIndicatorPresenterState {
    func startAnimating(presenter: NVActivityIndicatorPresenter) {
        guard let activityData = presenter.data else { return }

        presenter.show(with: activityData)
        presenter.state = .animating
        presenter.waitingToStartGroup.leave()
    }

    func stopAnimating(presenter: NVActivityIndicatorPresenter) {
        presenter.state = .stopped
        presenter.waitingToStartGroup.leave()
    }
}

private struct NVActivityIndicatorPresenterStateAnimating: NVActivityIndicatorPresenterState {
    func startAnimating(presenter: NVActivityIndicatorPresenter) {
        // Do nothing
    }

    func stopAnimating(presenter: NVActivityIndicatorPresenter) {
        guard let activityData = presenter.data else { return }

        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(activityData.minimumDisplayTime)) {
            presenter.stopAnimating()
        }
        presenter.state = .waitingToStop
    }
}

private struct NVActivityIndicatorPresenterStateWaitingToStop: NVActivityIndicatorPresenterState {
    func startAnimating(presenter: NVActivityIndicatorPresenter) {
        // Do nothing
    }

    func stopAnimating(presenter: NVActivityIndicatorPresenter) {
        presenter.hide()
        presenter.state = .stopped
    }
}

private struct NVActivityIndicatorPresenterStateStopped: NVActivityIndicatorPresenterState {
    func startAnimating(presenter: NVActivityIndicatorPresenter) {
        guard let activityData = presenter.data else { return }

        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(activityData.displayTimeThreshold)) {
            presenter.startAnimating(activityData)
        }
        presenter.state = .waitingToStart
        presenter.waitingToStartGroup.enter()
    }

    func stopAnimating(presenter: NVActivityIndicatorPresenter) {
        // Do nothing
    }
}

/// Presenter that displays NVActivityIndicatorView as UI blocker.
public final class NVActivityIndicatorPresenter {
    fileprivate enum State: NVActivityIndicatorPresenterState {
        case waitingToStart
        case animating
        case waitingToStop
        case stopped

        var performer: NVActivityIndicatorPresenterState {
            switch self {
            case .waitingToStart: return NVActivityIndicatorPresenterStateWaitingToStart()
            case .animating: return NVActivityIndicatorPresenterStateAnimating()
            case .waitingToStop: return NVActivityIndicatorPresenterStateWaitingToStop()
            case .stopped: return NVActivityIndicatorPresenterStateStopped()
            }
        }

        func startAnimating(presenter: NVActivityIndicatorPresenter) {
            performer.startAnimating(presenter: presenter)
        }

        func stopAnimating(presenter: NVActivityIndicatorPresenter) {
            performer.stopAnimating(presenter: presenter)
        }
    }

    private let restorationIdentifier = "NVActivityIndicatorViewContainer"
