CountDownLatch for Swift

By | August 5, 2016

In Java, CountDownLatch is a class in the JDK that is often used together with asynchronous tasks, and in tests.

An example use case is when you need two HTTP requests to complete before going to the next step, like in a login form where two HTTP requests must complete before the user can start using the app.

Here is how I did it in Swift 3 on iOS

import Foundation

final class CountDownLock {
    private var remainingJobs: Int32
    private let isDownloading = DispatchSemaphore(value: 0) // initially locked

    init(count: Int32) {
        remainingJobs = count
    }

    func countDown() {
        OSAtomicDecrement32(&remainingJobs)

        if (remainingJobs == 0) {
            self.isDownloading.signal() // unlock
        }
    }

    func waitUntilZero() {
        self.isDownloading.wait(timeout: DispatchTime.distantFuture)
    }

}

An example of use, with the non-essential parts left out

class LoginViewController: UIViewController {

    @IBOutlet weak var loginButton: UIButton?

    private let counter = CountDownLock(count: 2)

    private func downloadCertificate() {
        self.doDownLoadCertificate({ (success, errorMessage, errorDetailObject) -> Void in
            if (success) {
                self.counter.countDown()
            } else {
                self.reportErrorToUser(errorMessage, detail: errorDetailObject)
            }

        })
    }

    private func login(username:String, password:String) {
        self.loginToPortal(username, password:String, onCompletion: { (success, errorMessage, errorDetailObject) -> Void in
            if (success) {
                self.counter.countDown()
            } else {
                self.reportErrorToUser(errorMessage, detail: errorDetailObject)
            }
        }))
    }

    override func viewDidLoad() {
        self.downloadCertificate();

        super.viewDidLoad()
    }

    @IBAction func loginButtonWasClicked(sender: UIButton) {
        print("Button was clicked")

        login(username, password)

        // Wait until all background jobs are done
        self.counter.waitForZero() // non-busy wait, UI is not blocked
    }

}

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.