同步进行多个地图本地搜索



我尝试同步进行多个搜索(我的意思是一个接一个,等待上一个请求完成后再运行下一个请求(并阻止,直到所有操作完成后再继续。

但本地搜索的完成句柄看起来像是被阻塞了,一旦信号量放弃就会运行。我多次尝试都没有成功。

我的代码和日志如下(你可以复制/粘贴到操场上(:

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")

当前伪输出:

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

[更新]

预期输出应不显示***WarningAll done作为最后一行,如下所示(编号的确切顺序取决于网络条件(:

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]取消对//queue.maxConcurrentOperationCount = 1行的注释时输出

search(:in:centered:(:在第0部分进行本地搜索2020-03-28 23:49:41+0000搜索(:in:centered:(:等待第0部分完成2020-03-28 23:49:41+0000search(:in:centered:(:***警告:第0次超时,作业未完成2020-03-28 23:49:46+0000搜索(:in:centered:(:在第1部分进行本地搜索2020-03-28 23:49:46+0000搜索(:in:centered:(:等待第1部分完成2020-03-28 23:49:46+0000搜索(:in:centered:(:***警告:第1次超时,作业未完成2020-03-28 23:49:51+0000搜索(:in:centered:(:在第2部分进行本地搜索2020-03-28 23:49:51+0000搜索(:in:centered:(:等待第2部分完成2020-03-28 23:49:51+0000搜索(:in:centered:(:***警告:第2次超时,作业未完成2020-03-28 23:49:56+0000搜索(:in:centered:(:在第三部分进行本地搜索2020-03-28 23:49:56+0000搜索(:in:centered:(:等待第三部分完成2020-03-28 23:49:56+0000search(:in:centered:(:***警告:第3次超时,作业未完成2020-03-28 23:50:01+0000全部完成2020-03-28 23:50:01+00000获得可选(10(项2020-03-28 23:50:02+00003获得可选(10(项2020-03-28 23:50:02+00002获得可选(10(项2020-03-28 23:50:02+00001获得可选(10(项2020-03-28 23:50:02+0000

注:顺便说一句,我还在每次打印的末尾添加了(Date())

如果您希望这些操作以串行方式进行,则必须指定队列一次只能运行一个,例如

queue.maxConcurrentOperationCount = 1

而且,正如您所发现的,您希望避免使用addOperationswaitUntilFinished选项,因为这会阻塞当前线程,直到操作完成。相反,使用完成处理程序模式。


这是我使用的代码:

func performMultipleSearches(completion: @escaping () -> Void) {
let searches = ["restaurant", "coffee", "hospital", "natural history museum"]
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
for (i, searchText) in searches.enumerated() {
queue.addOperation {
self.search(searchText, in: self.mapView.region, id: i)
}
}
queue.addOperation {
completion()
}
}
func search(_ query: String, in region: MKCoordinateRegion, id: Int) {
let semaphore = DispatchSemaphore(value: 0)
os_log("%d starting", id)
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = query
request.region = region
if #available(iOS 13, *) {
request.resultTypes = .pointOfInterest
}
let search = MKLocalSearch(request: request)
search.start { response, error in
defer { semaphore.signal() }
guard let mapItems = response?.mapItems else {
os_log("  %d failed", id)
return
}
os_log("  %d succeeded, found %d:", id, mapItems.count)
}
os_log("  %d waiting", id)
guard semaphore.wait(timeout: .now() + 5) == .success else {
os_log("  %d timedout", id)
return
}
os_log("  %d done", id)
}

产生:

2020-03-28 16:16:25.219565-0700 MyApp[46601:2107182] 0 starting
2020-03-28 16:16:25.220018-0700 MyApp[46601:2107182]   0 waiting
2020-03-28 16:16:25.438121-0700 MyApp[46601:2107033]   0 succeeded, found 10:
2020-03-28 16:16:25.438269-0700 MyApp[46601:2107182]   0 done
2020-03-28 16:16:25.438436-0700 MyApp[46601:2107182] 1 starting
2020-03-28 16:16:25.438566-0700 MyApp[46601:2107182]   1 waiting
2020-03-28 16:16:25.639198-0700 MyApp[46601:2107033]   1 succeeded, found 10:
2020-03-28 16:16:25.639357-0700 MyApp[46601:2107182]   1 done
2020-03-28 16:16:25.639490-0700 MyApp[46601:2107182] 2 starting
2020-03-28 16:16:25.639598-0700 MyApp[46601:2107182]   2 waiting
2020-03-28 16:16:25.822085-0700 MyApp[46601:2107033]   2 succeeded, found 10:
2020-03-28 16:16:25.822274-0700 MyApp[46601:2107182]   2 done
2020-03-28 16:16:25.822422-0700 MyApp[46601:2107162] 3 starting
2020-03-28 16:16:25.822567-0700 MyApp[46601:2107162]   3 waiting
2020-03-28 16:16:26.015566-0700 MyApp[46601:2107033]   3 succeeded, found 1:
2020-03-28 16:16:26.015696-0700 MyApp[46601:2107162]   3 done
2020-03-28 16:16:26.015840-0700 MyApp[46601:2107162] all done

值得一提的是,我不会使用信号量,而是使用异步Operation子类。例如,您可以使用此处定义的AsynchronousOperation类,然后执行:

class SearchOperation: AsynchronousOperation {
let identifier: Int
let searchText: String
let region: MKCoordinateRegion
init(identifier: Int, searchText: String, region: MKCoordinateRegion) {
self.identifier = identifier
self.searchText = searchText
self.region = region
super.init()
}
override func main() {
os_log("%d started", identifier)
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = searchText
request.region = region
if #available(iOS 13, *) {
request.resultTypes = .pointOfInterest
}
let search = MKLocalSearch(request: request)
search.start { response, error in
defer { self.finish() }
guard let mapItems = response?.mapItems else {
os_log("  %d failed", self.identifier)
return
}
os_log("  %d succeeded, found %d:", self.identifier, mapItems.count)
}
}
}

然后

let searches = ["restaurant", "coffee", "hospital", "natural history museum"]
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
for (i, searchText) in searches.enumerated() {
queue.addOperation(SearchOperation(identifier: i, searchText: searchText, region: mapView.region))
}
queue.addOperation {
completion()
}

最新更新