在Swift自定义动画中正确处理/清除CADisplayLink等问题? [英] Correct handling / cleanup / etc of CADisplayLink in Swift custom animation?
问题描述
使用 CADisplayLink
,
var link:CADisplayLink?
var startTime:Double = 0.0
let animTime:Double = 0.2
let animMaxVal:CGFloat = 0.4
private func yourAnim()
{
if ( link != nil )
{
link!.paused = true
//A:
link!.removeFromRunLoop(
NSRunLoop.mainRunLoop(), forMode:NSDefaultRunLoopMode)
link = nil
}
link = CADisplayLink(target: self, selector: #selector(doorStep) )
startTime = CACurrentMediaTime()
link!.addToRunLoop(
NSRunLoop.currentRunLoop(), forMode:NSDefaultRunLoopMode)
}
func doorStep()
{
let elapsed = CACurrentMediaTime() - startTime
var ping = elapsed
if (elapsed > (animTime / 2.0)) {ping = animTime - elapsed}
let frac = ping / (animTime / 2.0)
yourAnimFunction(CGFloat(frac) * animMaxVal)
if (elapsed > animTime)
{
//B:
link!.paused = true
link!.removeFromRunLoop(
NSRunLoop.mainRunLoop(), forMode:NSDefaultRunLoopMode)
link = nil
yourAnimFunction(0.0)
}
}
func killAnimation()
{
// for example if the cell disappears or is reused
//C:
????!!!!
}
似乎有很多问题。
在(A :),即使 link
不为空,也可能无法将其从运行循环中删除。 (例如,有人可能使用 link = link:CADisplayLink()
对其进行了初始化-尝试进行崩溃。)
At (A:), even though link
is not null, it may not be possible to remove it from a run loop. (For example, someone may have initialized it with link = link:CADisplayLink()
- try it for a crash.)
第二个(B :)似乎是一团糟...肯定有更好的方法(和更多的Swift),即使时间刚到,如果它为零怎么办?
Secondly at (B:) it seems to be a mess ... surely there's a better (and more Swift) way, and what if it's nil even though the time just expired?
最后在(C :)中,如果您想破坏动画……我感到沮丧,不知道什么是最好的。
Finally in (C:) if you want to break the anim ... I got depressed and have no clue what is best.
推荐答案
这是一个一个简单的示例,展示了我将如何实现 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 usinginvalidate()
, 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 tonil
). - We have a single
stopDisplayLink()
method that both invalidates the display link (if it is non-nil) and sets it tonil
– rather than copy and pasting this logic. - We’re not setting
paused
totrue
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.gdisplayLink?.invalidate()
(which will callinvalidate()
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 typeCADisplayLink
, as required by the documentation.
这篇关于在Swift自定义动画中正确处理/清除CADisplayLink等问题?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!