Backgroundable 1.4.1

Backgroundable 1.4.1

TestsTested
LangLanguage SwiftSwift
License MIT
ReleasedLast Release Aug 2020
SPMSupports SPM

Maintained by Bell App Lab.




Backgroundable Version License

Platforms Swift support CocoaPods Compatible Carthage compatible Swift Package Manager compatible Twitter

Backgroundable

Backgroundable is a collection of handy classes, extensions and global functions to handle being in the background using Swift.

It's main focus is to add functionalities to existing Operations and OperationQueues, without adding overheads to the runtime (aka it's fast) nor to the developer (aka there's very little to learn).

It's powerful because it's simple.

Specs

  • iOS 10+
  • tvOS 10+
  • macOS 10.12+
  • Swift 4.2+
  • Objective-C ready

Executing Code in the Background

Transform this:

let queue = OperationQueue()
var bgTaskId = UIBackgroundTaskInvalid
bgTaskId = UIApplication.shared.beginBackgroundTask { () -> Void in
    bgTaskId = UIBackgroundTaskInvalid
}
queue.addOperation(BlockOperation(block: { () -> Void in
    //do something in the background
    UIApplication.shared.endBackgroundTask(bgTaskId)
}))

Into this:

inTheBackground {
    //move to the background and get on with your life
}

Operation Queues

Backgroundable exposes a nifty way to enqueue several operations that should be executed sequentially:

var sequentialOperations = [Operation]()
sequentialOperations.append(AsyncOperation({ (op) in
    print("Executing sequential operation 1")
    op.finish()
}))
sequentialOperations.append(BlockOperation({ 
    print("Executing sequential operation 2")
    //The sequential operations work with any Operation objects, not just AsyncOperations
}))
sequentialOperations.append(BlockOperation({ (op) in
    print("Executing sequential operation 3")
    op.finish()
}))
OperationQueue.background.addSequentialOperations(sequentialOperations, waitUntilFinished: false)

Background Queue

Backgroundable also provides a global background operation queue (similar to the existing OperationQueue.main):

OperationQueue.background.addOperation {
    //do something
}

This background queue is an instance of the BackgroundQueue class, which automatically handles background task identifiers. Whenever an operation is enqueued, a background task identifier is generated and whenever the queue is empty, the queue automatically invalidates it.

Sequential operations are guaranteed to be executed one after the other.

Background Queue Delegate

The BackgroundQueue class accepts a BackgroundQueueDelegate, which is notified whenever the queue backgroundQueueWillStartOperations(_:) and when backgroundQueueDidFinishOperations(_:).

This is quite handy if you want to show the network activity indicator or save a database or anything else really. The sky is the limit!

Asyncronous Operations

An AsyncOperation is an easy way to perform asynchronous tasks in an OperationQueue. It's designed to make it easy to perform long-running tasks on an operation queue regardless of how many times its task needs to jump between threads. Only once everything is done, the AsyncOperation is removed from the queue.

Say we have an asynchronous function we'd like to execute in the background:

self.loadThingsFromTheInternet(callback: { (result, error) in
    //process the result
})

If we wrapped this in an Operation object, we would have one small problem:

operationQueue.addOperation(BlockOperation({ [weak self] in
    //We're on a background thread now; NICE!
    self?.loadThingsFromTheInternet(callback: { (result, error) in
        //process the result
        //who knows in which thread this function returns... 
    })
    //Aaaand... As soon as we call the load function, the operation will already be finished and removed from the queue
    //But we haven't finished what we wanted to do!
    //And the queue will now start executing its next operation!
    //Sigh...
}))

The AsyncOperation class solves this issue by exposing the operation object itself to its execution block and only changing its isFinished property once everything is done:

operationQueue.addOperation(AsyncOperation({ [weak self] (op) in
    //We're on a background thread now; NICE!
    self?.loadThingsFromTheInternet(callback: { (result, error) in
        //process the result
        //then move to the main thread
        onTheMainThread {
            //go to the background
            inTheBackground {
                //do more stuff 
                //once everything is done, finish
                op.finish()
                //only now the queue will start working on the next thing
            }
        }
    })
}))

Nice, huh?

Timeouts

There's no way for an AsyncOperation to know when it's done (hence, we need to call op.finish() when its work is done). But sometimes, we developers - ahem - forget things.

Thus, in order to cover for the case where op.finish() may never be called (consequently blocking the OperationQueue), AsyncOperations come with a timeout (defaulting to 10 seconds). After the timeout elapses, the operation is automaticallt finished and removed from the queue.

It may be the case that your AsyncOperation's workload takes longer than the default timeout. If that's the case, you can define a new timeout like this:

AsyncOperation(timeout: 20, { (op) in
    //perform very long task
    op.finish()
})

Optionally, you can set the onTimeoutCallback: when instantiating a new AsyncOperation to be notified when your operations times out.

Cancelations

As per Apple's documentation, it's always a good idea to check if your operation has been cancelled during the execution of its closure and shortcircuit it prematurely if needed. For example:

AsyncOperation({ (op) in 
    //do some work
    
    guard !op.isCancelled else { return } //No need to call finish() in this case
    
    //do some more work
})

Uniqueness Policy

The uniqueness policy dictates whether AsyncOperations with the same name should co-exist in a BackgroundQueue. This is great for deduplicating operations, for example:

@IBAction func refresh(_ sender: UIRefreshControl) {
    let op = AsyncOperation(name: "Call to API endpoint /xyz", uniquenessPolicy: .drop) { op in
        //make the call to the API
        op.finish()
    }
    OperationQueue.background.addOperation(op)
}

This first time the user activates the refresh control, the operation will be added to the queue as normal, because there are no other operations with the name "Call to API endpoint /xyz" there yet. But if the user activates the control again before the first call to the API returns, then the .drop policy will make sure that a second operation is not added to the queue, since there's one operation with that name in there already. If .replace is set, then the previous operation is cancelled and the new one replaces it.

Neat!

Installation

Cocoapods

pod 'Backgroundable', '~> 1.4'

Then import Backgroundable where needed.

Carthage

github "BellAppLab/Backgroundable" ~> 1.4

Then import Backgroundable where needed.

Swift Package Manager

dependencies: [
    .package(url: "https://github.com/BellAppLab/Backgroundable", from: "1.4")
]

Then import Backgroundable where needed.

Git Submodules

cd toYourProjectsFolder
git submodule add -b submodule --name Backgroundable https://github.com/BellAppLab/Backgroundable.git

Then drag the Backgroundable folder into your Xcode project.

Author

Bell App Lab, [email protected]

Credits

Logo image by Becris from The Noun Project

License

Backgroundable is available under the MIT license. See the LICENSE file for more info.