1

Im trying to write an algorithm for sorting a list, and I use a network call (API request) to the google maps api for information about the distance between two points in a list.

I am using a while loop, and iterating over the list, until the size of the list is 0.

On each iteration, I make a network call, and after it responds, I remove something from the list.

I've tried using semaphores with the code below, and it does not work as expected.

let semaphore = DispatchSemaphore(value: 1)
let dispatchQueue = DispatchQueue(label: "taskQueue")

dispatchQueue.async {
  while unvistedPoints.count > 0{
    print("The size of the list is ", unvisited.count)
    self.findNextVistablePoint(visited: visitedPoints, unvisted: unvistedPoints, completion: { (pointToVisit) in
      let indexofPointToVisit = unvistedPoints.firstIndex(where: {$0 === pointToVisit})
      unvistedPoints.remove(at: indexofPointToVisit!)
      visitedPoints.append(pointToVisit)
      semaphore.signal()
    })
  semaphore.wait()
}

The print statement should print 6,5,4,3,2,1.

2 Answers 2

2

Here's some simplified playground code that demonstrates using a semaphore to ensure that your requests are executed serially:

import UIKit
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

class SomeAsyncClass {

    var unvistedPoints = [ 6,5,4,3,2,1 ]
    let dispatchQueue = DispatchQueue(label: "taskQueue") // serial queue
    let semaphore = DispatchSemaphore(value: 1)

    public func doAsyncStuff() {
        for point in self.unvistedPoints {
            print("Queuing point \(point)")
            dispatchQueue.async {
                // block before sending the network request
                self.semaphore.wait()
                self.makeFakeNetworkRequest(point, completion: {
                    // request complete
                    print("Completed \(point)")
                    self.semaphore.signal()
                })
            }
        }
    }

    func makeFakeNetworkRequest(_ point:Int, completion:()->()) {
        let interval = TimeInterval(exactly: (arc4random() % 3) + 1)!
        print("Point \(point): Sleeping for: \(interval)")
        Thread.sleep(forTimeInterval: interval)
        print("Point \(point): Awoken after: \(interval)")
        completion()
    }
}

var c = SomeAsyncClass()
c.doAsyncStuff()

Here's the output:

Queuing point 6
Queuing point 5
Queuing point 4
Point 6: Sleeping for: 3.0
Queuing point 3
Queuing point 2
Queuing point 1
Point 6: Awoken after: 3.0
Completed 6
Point 5: Sleeping for: 3.0
Point 5: Awoken after: 3.0
Completed 5
Point 4: Sleeping for: 3.0
Point 4: Awoken after: 3.0
Completed 4
Point 3: Sleeping for: 3.0
Point 3: Awoken after: 3.0
Completed 3
Point 2: Sleeping for: 3.0
Point 2: Awoken after: 3.0
Completed 2
Point 1: Sleeping for: 3.0
Point 1: Awoken after: 3.0
Completed 1

With that said, this isn't the best way to do it. You're better off using the iOS construct designed for this purpose, which is OperationQueue -- it's got granular concurrency controls (maxConcurrentOperationCount) and can be used as the basis of a URLSession (delegateQueue). I'd recommend using that construct if it fits your need.

Sign up to request clarification or add additional context in comments.

Comments

0

The wait should go before the network request. Also there really is no reason to use dispatchQueue asynch here because you loop does very little work (the network requests are already asynchronous) and I don't see the value in using a while loop here and mutating the array when you could just use a for loop instead. Here is an example:

import PlaygroundSupport
import UIKit

struct StarwarsCharacter: Codable {
    let name: String
}

enum APIResult<T> {
    case failure(Error), success(T)
}

func getCharactersSerially(completion: @escaping (APIResult<StarwarsCharacter>) -> ()) {
    var characters: [StarwarsCharacter] = []
    let semaphore = DispatchSemaphore(value: 1)
    let urls = (1...9).map {"https://swapi.co/api/people/\($0)"}.compactMap(URL.init(string:))
    urls.forEach { url in
        semaphore.wait()
        print("starting request for \(url) at \(Date())")
        URLSession.shared.dataTask(with: url) { data, response, error in
            print("completed request for \(url) at \(Date())")
            defer {
                semaphore.signal()
            }
            guard error == nil,
                let data = data,
                let character = try? JSONDecoder().decode(StarwarsCharacter.self, from: data) else {
                    completion(.failure(error ?? NSError()))
                    return
            }
            completion(.success(character))
            }.resume()
    }
}

PlaygroundPage.current.needsIndefiniteExecution = true
getCharactersSerially() { result in
    switch result {
    case .failure(let error):
        print(error.localizedDescription)
    case .success(let character):
        print(character.name)
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.