AsyncOperation
Tags: swift
Published: 2020-03-13 06:52:36

---
title: AsyncOperation
published: true
date: 2020-03-12 13:48:13 UTC
tags: swift
canonical_url: https://zeitschlag.net/asyncoperation/
---

A few days ago, I was working on an app for a client. With this app you can basically see a staff roster, that needs to be downloaded from a server. The REST-API is built that way, that the app has to talk to different endpoints to download all the data it needs. So the idea was basically:

1. Download this.
2. Download that.
3. Download those items over there, too.
4. Do something different, when you're done.
5. There's not 5.

Sure, I could've added a 3rd-party-dependency that does all this and I'd be done. But personally, I try to use as little 3rd-party-dependencies as possible. And if I use one, I'd like to make sure, that they're not the most critical parts of the app. At the same time, I wanted to avoid putting a new `URLSession.dataTask(with:completionHandler)` into another `completionHandler` in the same file.

As downloading is a pretty important thing in an app, that downloads and displays information, I decided not to use Alamofire or other fancy frameworks, no Combine, no RxSwift, just Swift and some Foundation. For downloading things I used [`URLSession`](https://developer.apple.com/documentation/foundation/urlsession) and it [`URLSessionDataTasks`](https://developer.apple.com/documentation/foundation/urlsessiondatatask), as everyone would do today, I guess. For the orchestration of the tasks, I could've rebuilt Grand Central Dispatch as John Sundell describes in his blogpost [Task-based concurreny in Swift](https://www.swiftbysundell.com/articles/task-based-concurrency-in-swift/). But actually, he summed up pretty well in half a sentence, how I was going to do it:

> subclass Operation to create a very custom solution

First, I was thinking about using [`DispatchQueue`](https://developer.apple.com/documentation/dispatch/dispatchqueue), [`DispatchGroup`](https://developer.apple.com/documentation/dispatch/dispatchgroup) and all their friends, but I didn't need all their capabilities. I wanted to use something more high-level, like [`Operation`](https://developer.apple.com/documentation/foundation/operation) and [`OperationQueue`](https://developer.apple.com/documentation/foundation/operationqueue). For this exact task, they gave me enough power to solve it. So, as John suggested, let's use `Operation` and a pretty basic very custom solution.

All I needed is described in Apple's [Concurrency Programming Guide](https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationObjects/OperationObjects.html#//apple_ref/doc/uid/TP40008091-CH101-SW8). I created a subclass with the very generic name of `AsyncOperation` with two properties `asyncOperationFinished` and `asyncOperationExecuting` and some KeyPath-observeration code, just like the guide suggested:

```swift
import Foundation

class AsyncOperation: Operation {
    
    private var asyncOperationFinished: Bool
    private var asyncOperationExecuting: Bool
    
    override init() {
        self.asyncOperationFinished = false
        self.asyncOperationExecuting = false
        
        super.init()
    }
    
    override var isFinished: Bool {
        return self.asyncOperationFinished
    }
    
    override var isExecuting: Bool {
        return self.asyncOperationExecuting
    }
    
    override func start() {
        if (self.isCancelled) {
            self.willChangeValue(for: \AsyncOperation.isFinished)
            self.asyncOperationFinished = true
            self.didChangeValue(for: \AsyncOperation.isFinished)
            return
        }
        
        self.willChangeValue(for: \AsyncOperation.isExecuting)
        self.asyncOperationExecuting = true
        Thread.detachNewThreadSelector(#selector(main), toTarget: self, with: nil)
        self.didChangeValue(for: \AsyncOperation.isExecuting)
    }
    
    override func cancel() {
        super.cancel()
        self.completeOperation()
    }
    
    func operationCompletedSuccessfully() {
        self.completeOperation()
    }
    
    private func completeOperation() {
        self.willChangeValue(for: \AsyncOperation.isFinished)
        self.willChangeValue(for: \AsyncOperation.isExecuting)
        
        self.asyncOperationExecuting = false
        self.asyncOperationFinished = true
        
        self.didChangeValue(for: \AsyncOperation.isFinished)
        self.didChangeValue(for: \AsyncOperation.isExecuting)
    }
    
}

```

For the actual asynchronous work like downloading and processing stuff, I subclassed this subclass again:

```
class DownloadFromThisEndpointOperation: AsyncOperation {
   override func main() {
       // download things from first endpoint asynchronously
       // call self.cancel() in completion-block if somethings goes wrong
       // call self.operationCompletedSuccessfully() in completion-block if you're done.
   }
}

```

So I had five different, independent operations, each calling a different endpoint, processing the downloaded data and sending a notification to update the UI. But there's one more thing missing: I needed to know, when all the operations were done. To do this, I used another subclass of `Operation`:

```swift
import Foundation

class DownloadFinishedOperation: Operation {

    var downloadFinishedCompletion: ((Bool) -> ())?

    init(downloadCompletion: ((Bool) -> ())?) {
        self.downloadFinishedCompletion = downloadCompletion
    }
    
    override func main() {
        let cancelledOperations = dependencies.filter { $0.isCancelled }
        
        if cancelledOperations.count > 0 {
            downloadFinishedCompletion?(false)
        } else {
            downloadFinishedCompletion?(true)
        }
    }
}

```

This synchrous operation gets a completion-block and if only one of the dependent operation failed earlier, the whole thing wasn't successful. Now, that I had all the pieces, the last step to do was to put them together. Thankfully, one can add dependencies to operations. So I connected every other operation to the last one, that should do something at the end, added all the operations to an instance of `OperationQueue` and started the whole thing. That's it.

```swift
let allOperations = [
    DownloadFromThisEndpointOperation()
    DownloadFromThatEndpointOperation()
    DownloadThoseItemsFromThatEndpointOperation()
]

let resultsOperation = DownloadFinishedOperation(downloadCompletion: completion)

for operation in allOperations {
    resultsOperation.addDependency(operation)
}

var allOperationsIncludingResult = Array<Operation>(allOperations)
allOperationsIncludingResult.append(resultsOperation)

let operationQueue = OperationQueue()
operationQueue.addOperations(allOperationsIncludingResult, waitUntilFinished: false)

```

## Conclusion

To be honest: I have no idea, if this is the way you do this. But for me, it worked flawlessly: I can download the data more or less asynchronously and indepedently from each other and get notified in the end. And more or less as a byproduct I got a class that I can use to chain asynchonous tasks together.

And it feels better than I thought. So you better [use this very carefully](https://gist.github.com/zeitschlag/388d820946e6c8d9e499d21987366e64)!

<!--kg-card-end: markdown-->