【问题标题】:Cancel a timed event in Swift?取消 Swift 中的定时事件?
【发布时间】:2015-04-06 05:47:05
【问题描述】:

我想在事件发生后的 10 秒内运行一段代码,但我希望能够取消它,这样如果在这 10 秒之前发生了某些事情,则代码在 10 秒过去后将不会运行。

我一直在用这个,但是不能取消:

static func delay(delay:Double, closure:()->()) {
  dispatch_after(
    dispatch_time(
      DISPATCH_TIME_NOW,
      Int64(delay * Double(NSEC_PER_SEC))
    ),
    dispatch_get_main_queue(), closure
  )
}

我怎样才能做到这一点?

【问题讨论】:

    标签: swift events


    【解决方案1】:

    你需要这样做:

    class WorkItem {
    
    private var pendingRequestWorkItem: DispatchWorkItem?
    
    func perform(after: TimeInterval, _ block: @escaping VoidBlock) {
        // Cancel the current pending item
        pendingRequestWorkItem?.cancel()
        
        // Wrap the request in a work item
        let requestWorkItem = DispatchWorkItem(block: block)
        
        pendingRequestWorkItem = requestWorkItem
    
        DispatchQueue.main.asyncAfter(deadline: .now() + after, execute: 
        requestWorkItem)
    }
    }
    
    // Use
    
    lazy var workItem = WorkItem()
    
    private func onMapIdle() {
    
        workItem.perform(after: 1.0) {
    
           self.handlePOIListingSearch()
        }
    }
    

    参考文献

    Link swiftbysundell

    Link git

    【讨论】:

      【解决方案2】:

      出于某种原因,NSObject.cancelPreviousPerformRequests(withTarget: self) 不适合我。我想到的一个解决方法是提出我允许的最大循环数,然后使用该 Int 来控制该函数是否被调用。

      然后我可以在我的代码中的其他任何地方设置 currentLoop 值,它会停止循环。

      //loopMax = 200
      var currentLoop = 0
      
      func loop() {      
        if currentLoop == 200 {      
           //do nothing.
        } else {
           //perform loop.
      
           //keep track of current loop count.
           self.currentLoop = self.currentLoop + 1
      
           let deadline = DispatchTime.now() + .seconds(1)
           DispatchQueue.main.asyncAfter(deadline: deadline) {
      
           //enter custom loop parameters
           print("i looped")
      
           self.loop()
         }
      
      }
      

      然后你可以在代码的其他地方

      func stopLooping() {
      
          currentLoop = 199
          //setting it to 199 allows for one last loop to happen. You can adjust based on the amount of loops you want to be able to do before it just stops. For instance you can set currentLoop to 195 and then implement a fade animation while loop is still happening a bit.
      }
      

      它实际上非常动态。例如,您可以查看 currentLoop == 123456789,它会无限运行(几乎),直到您在代码中的其他位置将其设置为该值。或者,即使您的需求不像我的那样基于时间,您也可以将其设置为 String() 或 Bool()。

      【讨论】:

        【解决方案3】:

        Swift 3.0 更新

        设置执行选择器

        perform(#selector(foo), with: nil, afterDelay: 2)
        

        foo 方法将在 2 秒后调用

         func foo()
         {
                 //do something
         }
        

        取消挂起的方法调用

         NSObject.cancelPreviousPerformRequests(withTarget: self)
        

        【讨论】:

        • 这依赖于 ObjC 运行时,所以不是很 Swifty。
        • 技术上,是的,你是对的。幸运的是,目前 99% 的 Swift 开发都是针对 macOS/iOS 的,所以它现在可以工作。
        • 很好的答案!
        • 我使用这种方法来合并多个观察者调用。我不希望观察者代码不断重复触发。使用 cancelPreviousPerformRequests 对我来说效果很好。
        • 它可以工作,使用cancelPreviousPerformRequests(withTarget:selector:object:),我们可以指定我们要取消的执行请求。并记住使用相同的线程,即主线程。
        【解决方案4】:

        试试这个(Swift 2.x,见下面大卫对 Swift 3 的回答):

        typealias dispatch_cancelable_closure = (cancel : Bool) -> ()
        
        func delay(time:NSTimeInterval, closure:()->()) ->  dispatch_cancelable_closure? {
        
            func dispatch_later(clsr:()->()) {
                dispatch_after(
                    dispatch_time(
                        DISPATCH_TIME_NOW,
                        Int64(time * Double(NSEC_PER_SEC))
                    ),
                    dispatch_get_main_queue(), clsr)
            }
        
            var closure:dispatch_block_t? = closure
            var cancelableClosure:dispatch_cancelable_closure?
        
            let delayedClosure:dispatch_cancelable_closure = { cancel in
                if let clsr = closure {
                    if (cancel == false) {
                        dispatch_async(dispatch_get_main_queue(), clsr);
                    }
                }
                closure = nil
                cancelableClosure = nil
            }
        
            cancelableClosure = delayedClosure
        
            dispatch_later {
                if let delayedClosure = cancelableClosure {
                    delayedClosure(cancel: false)
                }
            }
        
            return cancelableClosure;
        }
        
        func cancel_delay(closure:dispatch_cancelable_closure?) {
            if closure != nil {
                closure!(cancel: true)
            }
        }
        
        // usage
        let retVal = delay(2.0) {
            println("Later")
        }
        delay(1.0) {
            cancel_delay(retVal)
        }
        

        来自 Waam 的评论:dispatch_after - GCD in swift?

        【讨论】:

        • 有人能把它更新到 Swift 3.0,好吗?我一直在使用它直到 2.3,但是在更新到 Swift 3.0 之后,编译器会抱怨将闭包设置为 nil。
        • 见上文,但请注意它未经测试,可能不是使用 Swift 3.0 的最佳方法。让我知道它是否有效,我会更新它。
        • 对我来说,swift 3 代码不起作用。我必须将闭包声明为@escaping ()->Void
        • 你试过我上周添加的 Swift 3 版本了吗?我将对其进行编辑以使其更清楚。
        • 在 Swift 3 上对我来说效果不佳。我注意到 asyncAfter:execute: 有时不遵守最后期限:clouse 的执行时间比预期的要晚。 @DavidLawson 的回答虽然效果很好。
        【解决方案5】:

        Swift 3 有 DispatchWorkItem:

        let task = DispatchWorkItem { print("do something") }
        
        // execute task in 2 seconds
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2, execute: task)
        
        // optional: cancel task
        task.cancel()
        

        【讨论】:

        • 这似乎是在 Swift 3 中做到这一点的正确方法。应该接受答案。
        • 视情况而定。它“不错”而且相当干净,但是您必须跟踪一个变量以稍后取消它,如果您稍后在其他函数中取消它,可能是一个实例变量。 NSObject 函数为您处理它,因此没有簿记。 (当然,如果你说的是纯 Swift,那么NSObject 方式不是一个选项。)
        • 请注意 task.cancel() 将取消所有挂起的 DispatchWorkItem 事件并阻止任何未来的 DispatchWorkItem 事件运行。如果您尝试创建类似于打开和关闭的重复轮询计时器之类的东西,那么如果您“取消”了任务,则每次想要“重新启动”计时器时都需要创建一个 NEW DispatchWorkItem。本质上, task.cancel() 使 DispatchWorkItem “无效”,它不能再次使用。我怀疑 Apple 在内部为 DispatchWorkItem 设置了 isCancelled 标志,但我没有看到“清除”该标志以供重用的方法。我认为这是一个“功能”。
        • @Tejas 您可以继承 DispatchWorkItem 以覆盖其构造函数,因此您可以在每次创建新对象时设置可执行代码。这样,在您调用 .cancel() 之后,您只需将旧对象替换为新对象,该对象将继续工作。
        • 这是我使用的,我还没有遇到任何问题。简短,甜美,直截了当!
        【解决方案6】:

        我在一些项目中使用@sas 的方法,不知何故这不再起作用,也许在 Swift 2.1.1 之后发生了一些变化。值拷贝而不是指针?

        对我来说最简单的解决方法是:

        var canceled = false
        delay(0.25) {
          if !canceled {
            doSomething()
          }
        }
        

        【讨论】:

        • 我刚刚尝试在最新版本的 Xcode 中使用它,它似乎工作得很好。
        【解决方案7】:

        这应该可行:

        var doIt = true
        var timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector: Selector("doSomething"), userInfo: nil, repeats: false)
        
        //you have now 10 seconds to change the doIt variable to false, to not run THE CODE
        
        func doSomething()
        {
            if(doIt)
            {
                //THE CODE
            }
            timer.invalidate()
        }
        

        【讨论】:

        • 这实际上并没有取消计时器。它只是停止计时器中的代码运行。
        • 这不是你想要的吗?您可以使用变量“doIt”取消它,因此如果您不想执行代码,只需将其设置为 false..
        • 没有。无论如何,函数 doSomething 仍然运行。这不像我刚刚接受的答案那么有用。例如,您启动了一个计时器,将变量设置为 false 以“取消”它,然后启动另一个计时器,将变量设置回 true。代码将在第一个计时器结束时执行。
        猜你喜欢
        • 2017-08-27
        • 1970-01-01
        • 2012-02-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-20
        • 1970-01-01
        相关资源
        最近更新 更多