且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

在Swift自定义动画中正确处理/清除CADisplayLink等问题?

更新时间:2022-04-09 21:48:17

这是一个一个简单的示例,展示了我将如何实现 CADisplayLink (在Swift 3中):

Here’s a simple example showing how I’d go about implementing a CADisplayLink (in Swift 3):

class C { // your view class or whatever

  private var displayLink: CADisplayLink?
  private var startTime = 0.0
  private let animLength = 5.0

  func startDisplayLink() {

    stopDisplayLink() // make sure to stop a previous running display link
    startTime = CACurrentMediaTime() // reset start time

    // create displayLink & add it to the run-loop
    let displayLink = CADisplayLink(
      target: self, selector: #selector(displayLinkDidFire)
    )
    displayLink.add(to: .main, forMode: .commonModes)
    self.displayLink = displayLink
  }

  @objc func displayLinkDidFire(_ displayLink: CADisplayLink) {

    var elapsed = CACurrentMediaTime() - startTime

    if elapsed > animLength {
      stopDisplayLink()
      elapsed = animLength // clamp the elapsed time to the anim length
    }

    // do your animation logic here
  }

  // invalidate display link if it's non-nil, then set to nil
  func stopDisplayLink() {
    displayLink?.invalidate()
    displayLink = nil
  }
}

注意事项:


  • 我们在这里使用 nil 来表示显示链接未运行的状态–

  • 我们不是使用 removeFromRunLoop(),而是使用 invalidate(),如果尚未将显示链接添加到运行循环中,则不会崩溃。但是,这种情况永远都不会出现-因为我们总是在创建显示链接后立即将其添加到运行循环中。

  • 我们制作了 displayLink 私有,以防止外部类将其置于意外状态(例如,使其无效但不将其设置为)。 / li>
  • 我们有一个 stopDisplayLink()方法,该方法会使显示链接无效(如果它不是nil)并将其设置为 nil –而不是复制并粘贴此逻辑。

  • 我们未设置已暂停更改为 true ,然后取消显示链接,因为这是多余的。

  • 不是强制展开 displayLink 检查是否为非零后,我们使用可选的链接,例如 displayLink?.invalidate()(将称为 invalidate()(如果显示链接不是nil)。尽管在您给定的情况下(当您检查是否为零时)强制展开可能是安全的 –在将来的重构中它可能是不安全的,因为您可以在不考虑这对强制展开产生什么影响的情况下重新构造逻辑。

  • 我们将已花费的时间限制为动画持续时间,以确保以后的动画逻辑不会产生值超出预期范围。

  • 我们的更新方法 displayLinkDidFire(_:)接受单个类型为 CADisplayLink ,根据文档需要

  • We’re using nil here to represent the state in which the display link isn’t running – as there’s no easy way of getting this information from an invalidated display link.
  • Instead of using removeFromRunLoop(), we’re using invalidate(), which will not crash if the display link hasn’t already been added to a run-loop. However this situation should never arise in the first place – as we’re always immediately adding the display link to the run-loop after creating it.
  • We’ve made the displayLink private in order to prevent outside classes from putting it in an unexpected state (e.g invalidating it but not setting it to nil).
  • We have a single stopDisplayLink() method that both invalidates the display link (if it is non-nil) and sets it to nil – rather than copy and pasting this logic.
  • We’re not setting paused to true before invalidating the display link, as this is redundant.
  • Instead of force unwrapping the displayLink after checking for non-nil, we’re using optional chaining e.g displayLink?.invalidate() (which will call invalidate() if the display link isn’t nil). While force unwrapping may be ‘safe’ in your given situation (as you’re checking for nil) – it’s potentially unsafe when it comes to future refactoring, as you may re-structure your logic without considering what impact this has on the force unwraps.
  • We’re clamping the elapsed time to the animation duration in order to ensure that the later animation logic doesn’t produce a value out of the expected range.
  • Our update method displayLinkDidFire(_:) takes a single argument of type CADisplayLink, as required by the documentation.