Encapsulate iOS & Mac Asynchronous and Dependent Tasks into Cocoa Operation Subclass
You can also read this article in my Xcoding With Alfian blog website using the link below.
Encapsulate iOS & Mac Asynchronous and Dependent Tasks into Cocoa Operation Subclass | Xcoding with…
Asynchronous tasks like fetching data from network, parsing, processing, and saving data to local cache are routine…
Asynchronous tasks like fetching data from network, parsing, processing, and saving data to local cache are routine tasks apps nowaday perform. As a developer, we have to make sure the UI/main thread runs smoothly and move the long running heavy workload tasks into background thread to maintain 60 FPS animation.
Apple provides 2 ways for developers to perform tasks in background thread:
- Grand Central Dispatch (GCD): Set of API for developers to perform tasks serially or concurrently in background thread pool using queue.
- Operation (also known as NSOperation): A Cocoa Abstract Class that represents single unit of task to perform. It is a thread safe class with built in state, priority and QoS, cancellation, and dependencies management out of the box.
In this article we are going to build an asynchronous Operation Subclass that fetch repositories from GitHub API asynchronously, and a dependent Operation subclass that parse and serialize the fetch repository data into Swift Class.
What we will build
- AsynchronousOperation: an Operation Subclass that support asynchronous operation.
- FetchRepoOperation: AsynchronousOperation Subclass that fetch the Data for latest trending GitHub repositories since last week using URLSession asynchronously.
- ParseRepoDataOperation: Operation Subclass that decode and serialize the Data from FetchRepoOperation into an array of GithubRepo object using Swift Codable and JSONDecoder.
- Playground Page: Perform the operations using OperationQueue, add dependency between operation, and passing the data between operation objects using the completion block.
Implementing Asynchronous Operation using Operation Subclass
By default, Operation Class runs the code synchronously. Apple provides a way to run code asynchronously by subclassing and overriding isAsynchronous boolean property to true.
We also need to add our own state management property using enumeration, handle the change of state from ready, executing, and finished. dispacth queue will be used to handle synchronization using dispatch barrier for the state property when the property is being read and written concurrently.
In the start function we check if the task is not cancelled, if cancelled we just call finish to change the state to finished and return. If not we set the state to executing and call the main function. The main function will be overriden by our subclass to perform the tasks inside the function.
Implementing FetchRepoOperation to Fetch Github API
The FetchRepoOperation is a subclass of Asynchronous Operation, we declare two optional properties, fetchedData which is a Data object that will be used to store the data response from the API call, and an error property that will be used to store the error from an API call if it occurs.
Inside the overriden main method from the superclass, we construct the URL and the query items, the query items will query the repositories created since last week ordered descending by stars count. After that we initialize the URLRequest and invoke asynchronous data task with URLSession.
Inside the data task completion handler, we assign the response data and error to the instance properties and call finish method to set the state of the operation to finished to mark the operation as completed.
Implementing ParseRepoOperation to decode JSON Data with Swift Codable Class
We create GithubRepoFetchResult, GithubRepo, GithubOwner Swift Class that implements codable and CodingKeys enum to map the json property name to the instance property camel case name. By using Codable, we can utilize JSONDecoder to decode the Data to the class that implements Codable automatically.
Implementing ParseRepoOperation is very straightforward, we use Operation as subclass because JSONDecoder decode function is synchronous so we don’t need to use AsynchronousOperation.
We declare 3 optional instance properties, fetchedData is Data passed from the FetchRepoOperation, error is an Error object in case an error occurs when decoding the Data into object, the repos array containing GitHubRepo that will be used to store the result of the JSONDecoding into object.
Inside the main function we use guard to unwrap the optional fetchedData, if it is nil we just return from the function. After that inside the try catch block we use JSONDecoder decode function passing the fetchedData and the GithubRepoFetchResult as the root class to decode. Then, we assign the the items property from the GithubRepoFetchResult into the repos instance property. In case of error occurs when decoding, we assign the error to our error instance property.
Performing the Operations using OperationQueue
To perform the operations we use OperationQueue that acts a priority queue that handle the execution of the operations using First In First Out mechanism. We set maxConcurrentOperationCount to 1 so our operations does not perform concurrently at the same time.
We instantiate the FetchRepoOperation and ParseRepoOperation object, then we add FetchRepoOperation object as the dependency for ParseRepoOperation object so the fetch task will be started first and the must be finished for the parse task to start.
Passing data between operations is not straightforward, there are many ways to do it such as using Data wrapper reference class containing the data and then pass it to each operation. For this implementation, we will use the Operation completion block that will be called when the operation is finished. We assign the fetch operation completion block a closure with reference to parse and fetch operation objects. Unowned is used to avoid the retain cycle, inside the block we passed the fetch response data to the parse fetchedData property.
We assign the parse operation completion block property a closure that just loop the repos and print the name of the repo to the console so we can see the result.
At last to kick off the operations, we invoke the OperationQueue addOperations passing array containing the fetch and parse operations to begin the tasks.
Cocoa Operation Class provides developer great flexibilities such as dependencies between task, adjust the queue priority and QoS, cancellation and state management when performing background tasks. It is a great tool to use as an iOS developer just like the Grand Central Dispatch(GCD). Using them effectively will provide the result of silky smooth and high performant iOS/Mac apps.