I try to make multiple searches synchronously (I mean one after the other, waiting for the previous request to complete before running the next one) and block till all the operations are complete before going ahead.
But completion handle of the local search looks like blocked and run once the semaphore gives up. I have made many attempts without success.
My code and logs are as follows (you can copy/paste to the playground):
import CoreLocation
import MapKit
func search(_ query: String, in span: MKCoordinateSpan, centered center: CLLocationCoordinate2D, id: Int) {
let semaphore = DispatchSemaphore(value: 0)
//let group = DispatchGroup(); group.enter()
// Run the request for this rect
print("\(#function): local search on the \(id)th portion ")
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = query
request.region = MKCoordinateRegion(center: center, span: span)
if #available(iOS 13, *) {
request.resultTypes = .pointOfInterest
}
let search = MKLocalSearch(request: request)
search.start { response, error in
print("\(id) got \(response?.mapItems.count) items")
semaphore.signal()
}
let s = semaphore
//let s = group
// Wait for the request ot complete
print("\(#function): waiting for the \(id)th portion to complete")
//guard _ = s.wait(wallTimeout: .distantFuture) else {
guard s.wait(timeout: .now() + 5) == .success else {
print("\(#function): ***Warning: \(id)th timeout, job incomplete")
return
}
print("\(#function): \(id)th completed")
}
let rect = CGRect(
x: 48.10,
y: 3.43,
width: 0.09,
height: 0.09
)
let n = 4
let latDelta = rect.width / CGFloat(n)
var latOffs = rect.minX
let queue = OperationQueue()
//queue.maxConcurrentOperationCount = 1
var ops = [BlockOperation]()
// -- Run all asyn loca search requests synchronuously
for i in 0..<n {
// Take the next cut of the original region
let portion = CGRect(
x: latOffs,
y: rect.minY,
width: latDelta,
height: rect.height
)
latOffs += latDelta
ops.append(BlockOperation { [portion, i] in
let center = CLLocationCoordinate2D(latitude: CLLocationDegrees(portion.midX), longitude: CLLocationDegrees(portion.midY))
let span = MKCoordinateSpan(latitudeDelta: CLLocationDegrees(portion.width), longitudeDelta: CLLocationDegrees(portion.height))
search("coffee", in: span, centered: center, id: i)
})
}
queue.addOperations(ops, waitUntilFinished: true)
print("All done")
The current bogus output:
search(_:in:centered:id:): local search on the 1th portion
search(_:in:centered:id:): local search on the 2th portion
search(_:in:centered:id:): local search on the 3th portion
search(_:in:centered:id:): local search on the 0th portion
search(_:in:centered:id:): waiting for the 1th portion to complete
search(_:in:centered:id:): waiting for the 3th portion to complete
search(_:in:centered:id:): waiting for the 2th portion to complete
search(_:in:centered:id:): waiting for the 0th portion to complete
search(_:in:centered:id:): ***Warning: 0th timeout, job incomplete
search(_:in:centered:id:): ***Warning: 2th timeout, job incomplete
search(_:in:centered:id:): ***Warning: 1th timeout, job incomplete
search(_:in:centered:id:): ***Warning: 3th timeout, job incomplete
All done
0 got Optional(10) items
2 got Optional(10) items
1 got Optional(10) items
3 got Optional(10) items
[UPDATE]
The expected output should show no ***Warning
and All done
as the last line, as follows (the exact order of the numbering depends on the network conditions):
search(_:in:centered:id:): local search on the 1th portion
search(_:in:centered:id:): local search on the 2th portion
search(_:in:centered:id:): local search on the 3th portion
search(_:in:centered:id:): local search on the 0th portion
search(_:in:centered:id:): waiting for the 1th portion to complete
search(_:in:centered:id:): waiting for the 3th portion to complete
search(_:in:centered:id:): waiting for the 2th portion to complete
search(_:in:centered:id:): waiting for the 0th portion to complete
0 got Optional(10) items
search(_:in:centered:id:): 0th completed
2 got Optional(10) items
search(_:in:centered:id:): 2th completed
1 got Optional(10) items
search(_:in:centered:id:): 1th completed
3 got Optional(10) items
search(_:in:centered:id:): 3th completed
All done
[UPDATE 2] the outputted when uncommenting the line //queue.maxConcurrentOperationCount = 1
search(:in:centered:id:): local search on the 0th portion 2020-03-28 23:49:41 +0000 search(:in:centered:id:): waiting for the 0th portion to complete 2020-03-28 23:49:41 +0000 search(:in:centered:id:): ***Warning: 0th timeout, job incomplete 2020-03-28 23:49:46 +0000 search(:in:centered:id:): local search on the 1th portion 2020-03-28 23:49:46 +0000 search(:in:centered:id:): waiting for the 1th portion to complete 2020-03-28 23:49:46 +0000 search(:in:centered:id:): ***Warning: 1th timeout, job incomplete 2020-03-28 23:49:51 +0000 search(:in:centered:id:): local search on the 2th portion 2020-03-28 23:49:51 +0000 search(:in:centered:id:): waiting for the 2th portion to complete 2020-03-28 23:49:51 +0000 search(:in:centered:id:): ***Warning: 2th timeout, job incomplete 2020-03-28 23:49:56 +0000 search(:in:centered:id:): local search on the 3th portion 2020-03-28 23:49:56 +0000 search(:in:centered:id:): waiting for the 3th portion to complete 2020-03-28 23:49:56 +0000 search(:in:centered:id:): ***Warning: 3th timeout, job incomplete 2020-03-28 23:50:01 +0000 All done 2020-03-28 23:50:01 +0000 0 got Optional(10) items 2020-03-28 23:50:02 +0000 3 got Optional(10) items 2020-03-28 23:50:02 +0000 2 got Optional(10) items 2020-03-28 23:50:02 +0000 1 got Optional(10) items 2020-03-28 23:50:02 +0000
Note: Btw, I also added \(Date())
at the end of each print
If you want these operations to behave in a serial manner, you have to specify that the queue can only run one at a time, e.g.
And, as you discovered, you want to avoid using
waitUntilFinished
option ofaddOperations
, as that blocks the current thread until the operations are done. Instead, use completion handler pattern.Here is the code that I used:
That produced:
For what it’s worth, I wouldn’t use semaphores and instead would use an asynchronous
Operation
subclass. For example, you can use theAsynchronousOperation
class defined here, and then do:And then