为请求方法设置超时

时间:2022-06-01 21:23:25

I'm trying to set up a timeout for a request method that checks username availability. When the user types in a username and presses a button, the checkUsername method is called. My code is not working because the code inside Timeout(5.0){} is never executed and timeout never gets the value false. I know this is not the best way to do it but I wanted to give it a try and wonder if this can be modified in some way or do I need a different approach?

我试图为检查用户名可用性的请求方法设置超时。当用户输入用户名并按下按钮时,将调用checkUsername方法。我的代码不能工作,因为Timeout(5.0){}中的代码永远不会执行,Timeout永远不会得到值false。我知道这不是最好的方法,但我想试一试,想知道这是否可以用某种方式修改,还是我需要另一种方法?

var timeout: Bool = false

func usernameAvailable(username: String) -> String{
    let response: String!
    response = Server.checkUsername(username!)

    Timeout(5.0){
      self.timeout = true
    }

    while(!timeout){
        if(response != nil){
           return response
        }
    }
    return "Timeout"
}

The Timeout.swift class looks like this and is working

超时。swift类看起来是这样的,并且正在运行

class Timeout: NSObject{

private var timer: NSTimer?
private var callback: (Void -> Void)?

init(_ delaySeconds: Double, _ callback: Void -> Void){
    super.init()
    self.callback = callback
    self.timer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(delaySeconds),
        target: self, selector: "invoke", userInfo: nil, repeats: false)
}

func invoke(){
    self.callback?()
    // Discard callback and timer.
    self.callback = nil
    self.timer = nil
}

func cancel(){
    self.timer?.invalidate()
    self.timer = nil
}
}

2 个解决方案

#1


0  

The code in the Timeout block will never run because the timer will fire on the on the main thread, but you're blocking the main thread with your while loop.

超时块中的代码将永远不会运行,因为计时器将在主线程上启动,但您正在用while循环阻塞主线程。

You have another issue here, that you're calling Server.checkUsername(username!) and returning that result, which would suggest that this must be a synchronous call (which is not good). So, this is also likely blocking the main thread there. It won't even try to start the Timeout logic until checkUsername returns.

这里还有一个问题,就是调用Server.checkUsername(用户名!)并返回该结果,这表明这必须是一个同步调用(这是不好的)。所以,这也可能阻塞了主线程。在checkUsername返回之前,它甚至不会尝试启动超时逻辑。

There are kludgy fixes for this, but in my opinion, this begs for a very different pattern. One should never write code that has a spinning while loop that is polling some completion status. It is much better to adopt asynchronous patterns with completionHandler closures. But without more information on what checkUsername is doing, it's hard to get more specific.

这里有一些笨拙的修正,但是在我看来,这需要一个非常不同的模式。不应该编写具有旋转while循环的代码来轮询某些完成状态。最好采用带有completionHandler闭包的异步模式。但是,如果没有关于checkUsername的更多信息,就很难得到更具体的信息。

But, ideally, if your checkUsername is building a NSMutableURLRequest, just specify timeoutInterval for that and then have the NSURLSessionTask completion block check for NSError with domain of NSURLErrorDomain and a code of NSURLError.TimedOut. You also probably want to cancel the prior request if it's already running.

但是,理想情况下,如果您的checkUsername正在构建一个NSMutableURLRequest,只需为此指定timeoutInterval,然后让NSURLSessionTask completion block检查NSError,它的域为NSURLErrorDomain,代码为NSURLError.TimedOut。如果已经运行,您可能还想取消先前的请求。

func startRequestForUsername(username: String, timeout: NSTimeInterval, completionHandler: (Bool?, NSError?) -> ()) -> NSURLSessionTask {
    let request = NSMutableURLRequest(URL: ...)  // configure your request however appropriate for your web service
    request.timeoutInterval = timeout            // but make sure to specify timeout

    let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
        dispatch_async(dispatch_get_main_queue()) {
            guard data != nil && error == nil else {
                completionHandler(nil, error)
                return
            }

            let usernameAvailable = ...  // parse the boolean success/failure out of the `data` however appropriate
            completionHandler(usernameAvailable, nil)
        }
    }
    task.resume()

    return task
}

And you can then use it like so:

你可以这样使用它:

private weak var previousTask: NSURLSessionTask?

func checkUsername(username: String) {
    // update the UI to say that we're checking the availability of the user name here, e.g. 

    usernameStatus.text = "Checking username availability..."

    // now, cancel prior request (if any)

    previousTask?.cancel()

    // start new request

    let task = startRequestForUsername(username, timeout: 5) { usernameAvailable, error in
        guard usernameAvailable != nil && error == nil else {
            if error?.domain == NSURLErrorDomain && error?.code == NSURLError.TimedOut.rawValue {
                // everything is cool, the task just timed out
            } else if error?.domain == NSURLErrorDomain && error?.code != NSURLError.Cancelled.rawValue {
                // again, everything is cool, the task was cancelled
            } else {
                // some error other  happened, so handle that as you see fit
                // but the key issue that if it was `.TimedOut` or `.Cancelled`, then don't do anything
            }
            return
        }

        if usernameAvailable! {
            // update UI to say that the username is available

            self.usernameStatus.text = "Username is available"
        } else {
            // update UI to say that the username is not available

            self.usernameStatus.text = "Username is NOT available"
        }
    }

    // save reference to this task

    previousTask = task
}

By the way, if you do this sort of graceful, asynchronous processing of requests, you can also increase the timeout interval (e.g. maybe 10 or 15 seconds). We're not freezing the UI, so we can do whatever we want, and not artificially constrain the time allowed for the request.

顺便说一下,如果您对请求进行这种优雅的异步处理,您还可以增加超时间隔(例如,可能是10或15秒)。我们没有冻结UI,所以我们可以做任何我们想做的事,而不是人为地限制请求所允许的时间。

#2


1  

I see what you are trying to do and it would make more sense to use an existing framework unless you really need/want to write your own networking code.

我知道您正在尝试做什么,除非您确实需要/想编写自己的网络代码,否则使用现有的框架会更有意义。

I would suggest instead to use the timeoutInterval support in an NSURLRequest along with a completion handler on NSURLSession to achieve the solution that you are seeking.

相反,我建议在NSURLRequest中使用timeoutInterval支持,并在NSURLSession中使用一个完成处理程序来实现您正在寻找的解决方案。

A timeout of the server response can be handled in the completion handler of something like an NSURLSessionDataTask.

服务器响应的超时可以在诸如NSURLSessionDataTask之类的完成处理程序中处理。

Here is a working example to help get you started that retrieves data from the iTunes Store to illustrate how your timeout could be handled:

这里有一个工作示例,帮助您开始从iTunes商店检索数据,以说明如何处理超时:

let timeout = 5 as NSTimeInterval
let searchTerm = "philip+glass"
let url = NSURL(string: "https://itunes.apple.com/search?term=\(searchTerm)")
let request: NSURLRequest = NSURLRequest(URL: url!,
                                         cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringCacheData,
                                         timeoutInterval: timeout)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task: NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler: {
    (data, response, error) in
        if response == nil {
            print("Timeout")
        } else {
            print(String(data: data!, encoding: NSUTF8StringEncoding))
        }
    }
)

task.resume()

If you reduce the timeout interval to something short, you can force the timeout to happen.

如果您将超时间隔缩短到某个较短的值,则可以强制执行超时。

#1


0  

The code in the Timeout block will never run because the timer will fire on the on the main thread, but you're blocking the main thread with your while loop.

超时块中的代码将永远不会运行,因为计时器将在主线程上启动,但您正在用while循环阻塞主线程。

You have another issue here, that you're calling Server.checkUsername(username!) and returning that result, which would suggest that this must be a synchronous call (which is not good). So, this is also likely blocking the main thread there. It won't even try to start the Timeout logic until checkUsername returns.

这里还有一个问题,就是调用Server.checkUsername(用户名!)并返回该结果,这表明这必须是一个同步调用(这是不好的)。所以,这也可能阻塞了主线程。在checkUsername返回之前,它甚至不会尝试启动超时逻辑。

There are kludgy fixes for this, but in my opinion, this begs for a very different pattern. One should never write code that has a spinning while loop that is polling some completion status. It is much better to adopt asynchronous patterns with completionHandler closures. But without more information on what checkUsername is doing, it's hard to get more specific.

这里有一些笨拙的修正,但是在我看来,这需要一个非常不同的模式。不应该编写具有旋转while循环的代码来轮询某些完成状态。最好采用带有completionHandler闭包的异步模式。但是,如果没有关于checkUsername的更多信息,就很难得到更具体的信息。

But, ideally, if your checkUsername is building a NSMutableURLRequest, just specify timeoutInterval for that and then have the NSURLSessionTask completion block check for NSError with domain of NSURLErrorDomain and a code of NSURLError.TimedOut. You also probably want to cancel the prior request if it's already running.

但是,理想情况下,如果您的checkUsername正在构建一个NSMutableURLRequest,只需为此指定timeoutInterval,然后让NSURLSessionTask completion block检查NSError,它的域为NSURLErrorDomain,代码为NSURLError.TimedOut。如果已经运行,您可能还想取消先前的请求。

func startRequestForUsername(username: String, timeout: NSTimeInterval, completionHandler: (Bool?, NSError?) -> ()) -> NSURLSessionTask {
    let request = NSMutableURLRequest(URL: ...)  // configure your request however appropriate for your web service
    request.timeoutInterval = timeout            // but make sure to specify timeout

    let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
        dispatch_async(dispatch_get_main_queue()) {
            guard data != nil && error == nil else {
                completionHandler(nil, error)
                return
            }

            let usernameAvailable = ...  // parse the boolean success/failure out of the `data` however appropriate
            completionHandler(usernameAvailable, nil)
        }
    }
    task.resume()

    return task
}

And you can then use it like so:

你可以这样使用它:

private weak var previousTask: NSURLSessionTask?

func checkUsername(username: String) {
    // update the UI to say that we're checking the availability of the user name here, e.g. 

    usernameStatus.text = "Checking username availability..."

    // now, cancel prior request (if any)

    previousTask?.cancel()

    // start new request

    let task = startRequestForUsername(username, timeout: 5) { usernameAvailable, error in
        guard usernameAvailable != nil && error == nil else {
            if error?.domain == NSURLErrorDomain && error?.code == NSURLError.TimedOut.rawValue {
                // everything is cool, the task just timed out
            } else if error?.domain == NSURLErrorDomain && error?.code != NSURLError.Cancelled.rawValue {
                // again, everything is cool, the task was cancelled
            } else {
                // some error other  happened, so handle that as you see fit
                // but the key issue that if it was `.TimedOut` or `.Cancelled`, then don't do anything
            }
            return
        }

        if usernameAvailable! {
            // update UI to say that the username is available

            self.usernameStatus.text = "Username is available"
        } else {
            // update UI to say that the username is not available

            self.usernameStatus.text = "Username is NOT available"
        }
    }

    // save reference to this task

    previousTask = task
}

By the way, if you do this sort of graceful, asynchronous processing of requests, you can also increase the timeout interval (e.g. maybe 10 or 15 seconds). We're not freezing the UI, so we can do whatever we want, and not artificially constrain the time allowed for the request.

顺便说一下,如果您对请求进行这种优雅的异步处理,您还可以增加超时间隔(例如,可能是10或15秒)。我们没有冻结UI,所以我们可以做任何我们想做的事,而不是人为地限制请求所允许的时间。

#2


1  

I see what you are trying to do and it would make more sense to use an existing framework unless you really need/want to write your own networking code.

我知道您正在尝试做什么,除非您确实需要/想编写自己的网络代码,否则使用现有的框架会更有意义。

I would suggest instead to use the timeoutInterval support in an NSURLRequest along with a completion handler on NSURLSession to achieve the solution that you are seeking.

相反,我建议在NSURLRequest中使用timeoutInterval支持,并在NSURLSession中使用一个完成处理程序来实现您正在寻找的解决方案。

A timeout of the server response can be handled in the completion handler of something like an NSURLSessionDataTask.

服务器响应的超时可以在诸如NSURLSessionDataTask之类的完成处理程序中处理。

Here is a working example to help get you started that retrieves data from the iTunes Store to illustrate how your timeout could be handled:

这里有一个工作示例,帮助您开始从iTunes商店检索数据,以说明如何处理超时:

let timeout = 5 as NSTimeInterval
let searchTerm = "philip+glass"
let url = NSURL(string: "https://itunes.apple.com/search?term=\(searchTerm)")
let request: NSURLRequest = NSURLRequest(URL: url!,
                                         cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringCacheData,
                                         timeoutInterval: timeout)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task: NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler: {
    (data, response, error) in
        if response == nil {
            print("Timeout")
        } else {
            print(String(data: data!, encoding: NSUTF8StringEncoding))
        }
    }
)

task.resume()

If you reduce the timeout interval to something short, you can force the timeout to happen.

如果您将超时间隔缩短到某个较短的值,则可以强制执行超时。