multithreading – Transforms an asynchronous delegate pattern into a blocking method

I to have to use an integrated API that works like this

class MyAPIWrapper {
func callAPI () {
apiObj.delegate = self
apiObj.doWork () // return immediately, finally call self.delegate.workDone with the result on another thread
}

func workDone (result) {
// do something with the result
}
}

You interact with the API by calling different methods on different objects in the API. Each method returns immediately and reports its success / failure by calling a method on the delegate of the object once it is done.

The problem with this API is the delegate. Imagine if someone else is calling do some work for that same object right after you do. They "steal" the delegate, and they will have both made work callbacks (yours and theirs). Or even apart from that, it's very difficult to call a sequence of methods on these API objects because you have to implement manual synchronization mechanisms to make sure you get a made work call and the result was what you wanted before going on to the next call.

Since I can not modify this API, I want to encapsulate it to eliminate these problems. My idea was essentially to make the do some work call blocking. Then, no delegate will fly and call a sequence of methods is very easy. Something like that

WrappedAPIObject class

init (apiObject) {
self.apiObject = apiObject
self.delegateMutex = Mutex ()
self.workCV = ConditionVariable ()
}

func doWork () -> Result {
delegateMutex.lock ()
apiObj.delegate = self
apiObj.doWork ()
workCV.wait ()
retVal = self.result
delegateMutex.unlock ()
back back
}

func workDone (result) {
self.result = result
workCV.notify ()
}
}

Then, as long as you create encapsulated objects for each API object and always use them instead of non-encapsulated objects, you do not have to worry about delegates or the asynchronous nature. If you want to call it asynchronously, you can still generate a thread like this.

APIWrapper class
func doLotsOfWork (wrappedObj) {
Thread.new {
result1 = wrappedObj.doWork ()
result2 = wrappedObj.doWork ()
}
}
}

I have already tested this and it works very well. But that makes me a little uncomfortable:

  1. The variable mutex / condition combo seems awkward. I have the impression that I miss a more elegant model to simplify this API.
  2. By forcing an asynchronous API to be synchronous, I force the consumer to generate multiple threads to make it asynchronous. In our case, we have a lot of methods like doLotsOfWork this batch together the API calls. If the user of our APIWrapper calls a whole lot of these people on the even encapsulated object, it would generate tons of threads that could only be run in serial, wasting system resources.

2 is my main concern. I think there should be a design in which you can make several asynchronous calls to the same encapsulated object and put them all in the queue. a wire for this object.

Am I worried about anything? Or is it possible to solve the threading problem? Or is there a better way to avoid the problem of delegates I do not see?

* If you care, the current language is Swift, the lousy API is Apple's CoreBluetooth framework, the API objects are CBPeripherals, the "mutex" is a DispatchQueue and the "condition variable" is a DispatchGroup.