Swift 4中:需后台持续运行的rocketsGo函数应置于何处?
让火箭动画函数在Swift 4后台持续运行的实现方案
首先得先提一句,你的原代码里有个小bug:启动动画后立刻隐藏火箭并重置位置,这会导致动画根本看不到——因为UI动画是异步执行的,必须等动画完成后再做后续操作。先贴出你的原代码方便参考:
import UIKit class ViewController: UIViewController { @IBOutlet weak var TheCharacter: UIImageView! @IBOutlet weak var rocket1: UIImageView! func rocketsGo(){ let randomNumber = Int(arc4random_uniform(8)) self.rocket1.isHidden = true if randomNumber % 2 == 0 { if 0 == 0{ // 这个判断完全多余,建议直接删除 self.rocket1.isHidden = false let xPosR = rocket1.frame.origin.x + 500 let yPosR = rocket1.frame.origin.y let heightCharacterR = rocket1.frame.size.height let widthCharacterR = rocket1.frame.size.width UIView.animate(withDuration: 1.75, animations: { self.rocket1.frame = CGRect(x: xPosR, y: yPosR , width: widthCharacterR, height: heightCharacterR) }) { (finished) in } self.rocket1.isHidden = true self.rocket1.frame = CGRect(x: xPosR - 500, y: yPosR, width: widthCharacterR, height: heightCharacterR) } } } }
最佳实现方案:使用Timer重复触发
因为你的逻辑涉及UI动画,必须在主线程执行,所以用Timer是最稳妥的选择——它默认在主运行循环中工作,不会有线程安全问题。具体实现步骤如下:
1. 添加定时器管理变量
在视图控制器中定义一个Timer属性,用来管理定时器的生命周期,避免被提前释放。
2. 在视图加载时启动定时器
在viewDidLoad()里初始化定时器,设置重复调用的时间间隔(比如每2秒触发一次,你可以根据游戏节奏调整),并把它加入主运行循环的.common模式,确保在滚动UI等场景下也能正常触发。
3. 在视图消失时销毁定时器
为了避免内存泄漏,一定要在视图即将消失时停止并销毁定时器。
4. 修复动画逻辑
把隐藏和重置位置的操作放到动画的完成回调中,保证动画能完整展示。
修改后的完整代码:
import UIKit class ViewController: UIViewController { @IBOutlet weak var TheCharacter: UIImageView! @IBOutlet weak var rocket1: UIImageView! private var rocketTimer: Timer? // 定时器变量 override func viewDidLoad() { super.viewDidLoad() // 启动定时器:每2秒调用一次rocketsGo,重复执行 rocketTimer = Timer.scheduledTimer( timeInterval: 2.0, target: self, selector: #selector(rocketsGo), userInfo: nil, repeats: true ) // 加入主运行循环的common模式,避免被UI交互阻塞 RunLoop.current.add(rocketTimer!, forMode: .common) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // 停止并销毁定时器,防止内存泄漏 rocketTimer?.invalidate() rocketTimer = nil } @objc func rocketsGo(){ // Timer的selector要求方法带有@objc标记 let randomNumber = Int(arc4random_uniform(8)) rocket1.isHidden = true if randomNumber % 2 == 0 { rocket1.isHidden = false let originalFrame = rocket1.frame // 保存火箭原始位置 let targetX = originalFrame.origin.x + 500 UIView.animate(withDuration: 1.75, animations: { // 更新火箭位置到目标点 self.rocket1.frame = CGRect( x: targetX, y: originalFrame.origin.y, width: originalFrame.size.width, height: originalFrame.size.height ) }) { [weak self] finished in // 动画完成后重置位置并隐藏,用weak self避免循环引用 guard let self = self else { return } self.rocket1.isHidden = true self.rocket1.frame = originalFrame } } } }
额外说明
如果你的需求是让纯计算逻辑在后台线程持续运行(不涉及UI),可以用DispatchSourceTimer,但因为你这里是UI动画,必须在主线程执行,所以Timer是最优解。
内容的提问来源于stack exchange,提问作者user9313040
相关产品推荐
相关产品推荐

