在Swift中,我正在调用Web Serice(Google Places)并成功获取Google地方信息ID。
当我正在迭代JSON回复并获取Google Place ID时,我想拨打另一个Web服务(Google Place详情)
使用下面的代码,我得到的回答是:
estPlace_ID_1
Return Number Is: Nothing
estPlace_ID
Return Number Is: Nothing
.....
Function Phone Number is: 867-5309
Function Phone Number is: 867-5309
在结果循环中的for结果完成之前,好像没有执行函数get Details。
如何更改代码以便在继续迭代之前等待getDetails执行?
class func getDetails(id: String) -> String {
<Setup the request>
let session = NSURLSession.sharedSession()
//Second Request
let task = session.dataTaskWithRequest(request) { data, response, error in
do {
//Parse Result
print("Function Phone Number is" + phoneNumber)
}
catch {
}
}
task.resume()
return phoneNumber
}
//First request
<Setup the request>
let task = session.dataTaskWithRequest(request) { data, response, error in
//a few checks with guard statements
do {
//Extract results from JSON response
results = <FROM_JSON>
for result in results {
estPlace_ID = result["value"]
print(estPlace_ID)
print("return number is" + getDetails(estPlace_ID))
}
catch {
}
}
task.resume()
}
答案 0 :(得分:1)
进行函数调用阻塞,直到异步调用的结果到达,可以通过调度信号量来实现。模式是:
create_semaphore()
someAyncCall() {
signal_semaphore()
}
wait_for_semaphore()
rest_of_the_code()
在您的情况下,您可以修改getDetails
方法,如下所示:
class func getDetails(id: String) -> String {
<Setup the request>
let session = NSURLSession.sharedSession()
let sem = dispatch_semaphore_create(0)
//Second Request
let task = session.dataTaskWithRequest(request) { data, response, error in
do {
//Parse Result
print("Function Phone Number is" + phoneNumber)
} catch {
}
// the task has completed, signal this
dispatch_semaphore_signal(sem)
}
task.resume()
// wait until the semaphore is signaled
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER)
// we won't get here until dispatch_semaphore_signal() is called
return phoneNumber
}
要记住的一件重要事情(感谢Rob指出这一点)是你需要在另一个队列上调用getDetails
,否则你将陷入僵局:
dispatch_async(dispatch_get_global_queue(0, 0) ){
for result in results {
let estPlace_ID = result["value"]
print(estPlace_ID)
print("return number is" + getDetails(estPlace_ID))
}
}
请注意,在上面的示例中,dispatch_semaphore_wait
的第二个参数是DISPATCH_TIME_FOREVER
,这意味着调用代码将无限期地等待异步调用完成。如果要设置一些超时,可以创建dispatch_time_t
值并传递它:
// want to wait at most 30 seconds
let timeout = 30
let dispatchTimeout = dispatch_time(DISPATCH_TIME_NOW, timeout * Int64(NSEC_PER_SEC))
dispatch_semaphore_wait(sem, dispatchTimeout)
答案 1 :(得分:0)
我建议你采用异步模式。例如,有一个方法可以异步检索电话号码,使用完成处理程序报告成功或失败:
let session = NSURLSession.sharedSession()
func requestPhoneNumber(id: String, completionHandler: (String?) -> Void) {
let request = ...
let task = session.dataTaskWithRequest(request) { data, response, error in
do {
let phoneNumber = ...
completionHandler(phoneNumber)
}
catch {
completionHandler(nil)
}
}
task.resume()
}
然后,您检索所有地点的第一个请求将使用此异步requestDetails
:
// I don't know what your place structure would look like, but let's imagine an `id`,
// some `info`, and a `phoneNumber` (that we'll retrieve asynchronously).
struct Place {
var id: String
var placeInfo: String
var phoneNumber: String?
init(id: String, placeInfo: String) {
self.id = id
self.placeInfo = placeInfo
}
}
func retrievePlaces(completionHandler: ([Place]?) -> Void) {
let request = ...
let task = session.dataTaskWithRequest(request) { data, response, error in
// your guard statements
do {
// Extract results from JSON response (without `phoneNumber`, though
var places: [Place] = ...
let group = dispatch_group_create()
// now let's iterate through, asynchronously updating phone numbers
for (index, place) in places.enumerate() {
dispatch_group_enter(group)
self.requestPhoneNumber(place.id) { phone in
if let phone = phone {
dispatch_async(dispatch_get_main_queue()) {
places[index].phoneNumber = phone
}
}
dispatch_group_leave(group)
}
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
completionHandler(places)
}
}
}
task.resume()
}
这也采用异步模式,这次使用调度组来识别请求何时完成。当你调用它时,你将使用完成处理程序模式:
retrievePlaces { phoneNumberDictionary in
guard phoneNumberDictionary != nil else { ... }
// update your model/UI here
}
// but not here
注意,retrievePlaces
将相互发布这些请求(出于性能原因)。如果你想限制它,你可以使用信号量来做到这一点(只需确保在后台队列上执行此操作,而不是会话的队列)。基本模式是:
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) {
let semaphore = dispatch_semaphore_create(4) // set this to however many you want to run concurrently
for request in requests {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
performAsynchronousRequest(...) {
dispatch_semaphore_signal(semaphore)
}
}
}
这可能看起来像:
func retrievePlaces(completionHandler: ([Place]?) -> Void) {
let request = ...
let task = session.dataTaskWithRequest(request) { data, response, error in
// your guard statements
do {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) {
// Extract results from JSON response
var places: [Place] = ...
let semaphore = dispatch_semaphore_create(4) // use whatever limit you want here; this does max four requests at a time
let group = dispatch_group_create()
for (index, place) in places.enumerate() {
dispatch_group_enter(group)
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
self.requestPhoneNumber(place.id) { phone in
if let phone = phone {
dispatch_async(dispatch_get_main_queue()) {
places[index].phoneNumber = phone
}
}
dispatch_semaphore_signal(semaphore)
dispatch_group_leave(group)
}
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
completionHandler(places)
}
}
}
}
task.resume()
}
坦率地说,当它变得复杂时,我经常使用异步NSOperation
子类并使用队列的maxConcurrentOperationCount
来约束并发性,但这似乎超出了这个问题的范围。但是你也可以像上面那样使用信号量来约束并发性。但最重要的是,不是试图弄清楚如何使请求同步运行,如果你遵循异步模式,你将获得最佳的用户体验和性能。