Skip to content

Instantly share code, notes, and snippets.

@curtclifton
Last active February 13, 2018 15:39
Show Gist options
  • Save curtclifton/1988cdeaf0704853dbd058b864854957 to your computer and use it in GitHub Desktop.
Save curtclifton/1988cdeaf0704853dbd058b864854957 to your computer and use it in GitHub Desktop.
//
// NetworkIndicatorManager.swift
// Indicator
//
// Created by Curt Clifton on 5/17/16.
// Copyright © 2016 curtclifton.net. All rights reserved.
//
import Foundation
import UIKit
/// The class of opaque token objects vended by `NetworkIndicatorManager`.
public class NetworkIndicatorToken: AnyObject {
}
/// A simple wrapper class so we can put weak references inside a Swift array.
private class WeakWrapper<Wrapped: AnyObject> {
weak var wrapped: Wrapped?
/// Becomes true when `wrapped` is deallocated.
var isCleared: Bool {
return wrapped == nil
}
init(wrapping: Wrapped) {
self.wrapped = wrapping
}
}
/// A singleton instance, accessible via `sharedManager`, allows clients to control the network activity indicator.
///
/// - SeeAlso:
/// - `beganNetworkTask()`
/// - `beganNetworkTask(forToken:)`
/// - `finishedNetworkTask(forToken:)`
public class NetworkIndicatorManager {
/// The singleton instance.
public static var sharedManager = NetworkIndicatorManager()
public var completionCheckInterval: NSTimeInterval = 1.0
private var keepAliveTokens: [WeakWrapper<AnyObject>] = []
private var pollingTimer: NSTimer? = nil
private var isPollingConfigured: Bool { return pollingTimer != nil }
/// Shows the network activity indicator if it isn't already showing, returning an opaque token that can be used to signal the end of the activity.
///
/// Clients can manually signal the end of the task by calling `finishedNetworkTask(forToken:)` passing the returned token. Alternatively, a client can simply hold a reference to the token. When the reference goes out of scope or is released, the task for the token will end automatically.
/// - SeeAlso:
/// - `beganNetworkTask(forToken:)`
/// - `finishedNetworkTask(forToken:)`
public func beganNetworkTask() -> NetworkIndicatorToken {
let token = NetworkIndicatorToken()
beganNetworkTask(withToken: token)
return token
}
/// Shows the network activity indicator if it isn't already showing, using `token` to signal the end of the activity.
///
/// Clients can manually signal the end of the task by calling `finishedNetworkTask(forToken:)` passing the same `token` used to begin the task. Alternatively, a client can simply hold a reference to the token. When the reference goes out of scope or is released, the task for the token will end automatically.
/// - SeeAlso:
/// - `beganNetworkTask()`
/// - `finishedNetworkTask(forToken:)`
public func beganNetworkTask(withToken token: AnyObject) {
// Redispatch async to main queue so access to `keepAliveTokens` is single threaded
guard NSThread.isMainThread() else {
NSOperationQueue.mainQueue().addOperationWithBlock {
self.beganNetworkTask(withToken: token)
}
return
}
let wrappedToken = WeakWrapper(wrapping: token)
keepAliveTokens.append(wrappedToken)
updateIndicator()
}
/// Signals that the task associated with `token` has finished, hiding the network activity indicator if no other tasks are still active.
/// - SeeAlso:
/// - `beganNetworkTask()`
/// - `beganNetworkTask(forToken:)`
public func finishedNetworkTask(forToken token: AnyObject) {
// Redispatch async to main queue so access to `keepAliveTokens` is single threaded
guard NSThread.isMainThread() else {
NSOperationQueue.mainQueue().addOperationWithBlock {
self.finishedNetworkTask(forToken: token)
}
return
}
keepAliveTokens = keepAliveTokens.filter { wrappedToken in
return !wrappedToken.isCleared && !(wrappedToken.wrapped === token)
}
}
// MARK: - Private API
private func updateIndicator() {
// Redispatch async to main queue so access to `keepAliveTokens` is single threaded
guard NSThread.isMainThread() else {
NSOperationQueue.mainQueue().addOperationWithBlock {
self.updateIndicator()
}
return
}
keepAliveTokens = keepAliveTokens.filter { wrappedToken in
!wrappedToken.isCleared
}
let shouldShowActivity = !keepAliveTokens.isEmpty
UIApplication.sharedApplication().networkActivityIndicatorVisible = shouldShowActivity
// Update polling timer
if !isPollingConfigured && shouldShowActivity {
pollingTimer = NSTimer.scheduledTimerWithTimeInterval(completionCheckInterval, target: self, selector: #selector(timerFired(_:)), userInfo: nil, repeats: true)
} else if isPollingConfigured && !shouldShowActivity {
pollingTimer?.invalidate()
pollingTimer = nil
}
}
private dynamic func timerFired(timer: NSTimer) {
updateIndicator()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment