Swift:同步Web服务调用

时间:2016-08-25 01:17:29

标签: ios swift

在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()
}

2 个答案:

答案 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来约束并发性,但这似乎超出了这个问题的范围。但是你也可以像上面那样使用信号量来约束并发性。但最重要的是,不是试图弄清楚如何使请求同步运行,如果你遵循异步模式,你将获得最佳的用户体验和性能。