You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:36:32