为啥 NSTimer 在 swift 中只工作一次

Posted

技术标签:

【中文标题】为啥 NSTimer 在 swift 中只工作一次【英文标题】:why NSTimer works just once in swift为什么 NSTimer 在 swift 中只工作一次 【发布时间】:2016-01-17 12:40:25 【问题描述】:

我有一个 http 请求,如果我正确接收到响应,那么我想启动一个每秒触发一个函数的计时器。该函数也是一个http请求。

这是我触发计时器的代码

    if let data = data 
        do
            let resultJSON = try NSJSONSerialization.JSONObjectWithData(data, options: [])
            requestClient.id = resultJSON["id"] as! Double
            self.timerResponse = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "checkIfThereAreResponeses", userInfo: nil, repeats: true)
            self.timerResponse!.fire()
        catch
        
    

如你所见,我正在调用一个名为checkIfThereAreResponses的函数

在该函数中,我有一个打印语句,并且该打印语句只打印一次,虽然我的计时器应该每 1 秒工作一次

我缺少什么?

这就是函数

 func checkIfThereAreResponeses()
    if (requestClient!.id != nil) 
        let url = NSURL(string: "http://localhost:blablabla")
        let request = NSMutableURLRequest(URL: url)
        request.HTTPMethod = "POST"

        let session = NSURLSession.sharedSession()

        task = session.dataTaskWithRequest(request, completionHandler: (data, response, error) in
            if let error = error 
                print("error = \(error)")
            



            if let data = data 
                do 
                    let resultJSONArray = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! NSArray
        bla bla bla

                catch 
                    print("no responses yet = \(error)")
                
            
        )
        task!.resume()
    else 
        print("not yet ")
    

我收到的 JUST ONCEno response yet

【问题讨论】:

我无法重现该问题,printcheckIfThereAreResponeses 中的第一条语句吗? @luk2302 但它在里面。 但它是第一个吗?查看实际的checkIfThereAreResponeses 可能会有所帮助! @luk2302 好的,我给了你代码 @luk2302 我现在在函数的开头打印了一个,它仍然只工作一次,但是当我将计时器放在主线程中时它可以工作 【参考方案1】:

如果您在完成处理程序中有此代码,建议 NSTimer 在主线程中运行,因此这可能是原因。你可能需要这样的东西:

dispatch_async(dispatch_get_main_queue(), 
    // Your timer logic here
)

Apple 文档说:

定时器与运行循环一起工作。为了有效地使用计时器, 你应该知道运行循环是如何运作的——参见 NSRunLoop。

(NSTimer)

NSRunLoop 类通常不被认为是线程安全的,并且 它的方法只能在当前的上下文中调用 线。你永远不应该尝试调用 NSRunLoop 的方法 对象在不同的​​线程中运行,因为这样做可能会导致 意想不到的结果。

(NSRunLoop)

【讨论】:

您是否打算将我的代码更改为`dispatch_async(dispatch_get_main_queue(), self.timerResponse = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "checkIfThereAreResponeses", userInfo: nil, 重复: true) self.timerResponse!.fire() )` ? @sarah 是的,类似的就是答案的含义。 我可以有一个链接来增加我在这个问题上的知识吗? 不建议在主线程中运行 NSTimer,只是为了方便。 NSTimer 可以在任何线程的 runloop 上运行,您只需要了解该线程的生命周期。主线程很方便,因为除非您的应用程序死亡,否则它永远不会死亡。重要的是要意识到,当计时器触发时,选择器会在具有运行计时器的 runloop 的线程上调用。【参考方案2】:

NSTimer 的工作原理是被安排在一个运行循环上。如果在 session 的 completionHandler 中调用这个方法,它会被调度到当前正在运行该 completionHandler 的线程的 runloop。由于这个线程不属于你,而是由系统拥有,它会被系统处理掉。这可能是在完成处理程序完成执行之后立即发生的,也可能是很久以后。有了线程,runloop 也消失了,因此,您的计时器可能永远不会触发,几次或仅触发一次;没有确切的说法,因为这取决于系统何时删除线程。

因此,您应该在您拥有的线程上创建计时器,或者更简单地将计时器的创建分派给主线程(dispatch_get_main_queue() 是您的朋友)。另一种选择是使用+timerWithTimeInterval... 方法之一创建一个NSTimer,然后使用[NSRunloop mainRunloop] addTimer: yourTimer forMode NSRunLoopCommonModes] 将其添加到主运行循环中。请记住,计时器将在与其运行的 runloop 相同的线程上调用您的选择器。

附带说明,轮询服务器可能不是在移动设备上运行的最佳方式。如果您控制服务器,最好在有新数据时向设备发送推送通知。这将节省设备的电池电量,并减少服务器的负载。但是,如果你不控制服务器,实现这一点会更复杂,然后轮询可能是一个很好的折衷方案。

【讨论】:

以上是关于为啥 NSTimer 在 swift 中只工作一次的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Swift 中只执行一次代码?

cellForItemAt 在 Swift collectionView 中只调用一次

在 Swift 中使用 NSTimer

NSTimer - 如何在 Swift 中延迟

Swift Playground 中的 NSTimer.scheduledTimerWithTimeInterval

NSTimer 中的错误(Swift:Xcode)