当前位置: 首页 > 面试题库 >

在后台快速NSTimer

公羊兴文
2023-03-14
问题内容

我在堆栈或其他地方在后台处理NSTimer时遇到了很多问题。我尝试了所有实际上有意义的选项之一,以便在应用程序进入后台时停止计时器

    NSNotificationCenter.defaultCenter().addObserver(self, selector: "appDidEnterBackground", name: UIApplicationDidEnterBackgroundNotification, object: nil)

    NSNotificationCenter.defaultCenter().addObserver(self, selector: "appDidBecomeActive", name: UIApplicationWillEnterForegroundNotification, object: nil)

起初我以为我的问题解决了,我只是保存了应用程序进入后台的时间,并计算了应用程序进入前景时的时间..但后来我注意到时间实际上延迟了3、4、5秒。
..实际上不一样..我已将其与另一台设备上的秒表进行了比较。

在后台运行NSTimer真的有任何SOLID解决方案吗?


问题答案:

您不应该根据进入后台或恢复的时间来进行任何调整,而只需节省您要数的时间(取决于您是在递增还是递减)。然后,当应用再次启动时,您只需在重构计时器时使用该时间即可。

同样,请确保您的计时器处理程序不依赖于调用处理选择器的确切时间(例如, 不要
执行seconds++任何类似操作,因为您可能希望调用时不会准确地调用它),但请务必返回从/到时间。

这是一个倒数计时器的示例,它说明我们不对任何东西进行“计数”。我们也不在乎介于appDidEnterBackground和之间的时间appDidBecomeActive。只需保存停止时间,然后计时器处理程序就可以将目标stopTime时间与当前时间进行比较,并根据需要显示经过的时间。

例如:

import UIKit
import UserNotifications

private let stopTimeKey = "stopTimeKey"

class ViewController: UIViewController {

    @IBOutlet weak var datePicker: UIDatePicker!
    @IBOutlet weak var timerLabel: UILabel!

    private weak var timer: Timer?
    private var stopTime: Date?

    let dateComponentsFormatter: DateComponentsFormatter = {
        let formatter = DateComponentsFormatter()
        formatter.allowedUnits = [.hour, .minute, .second]
        formatter.unitsStyle = .positional
        formatter.zeroFormattingBehavior = .pad
        return formatter
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        registerForLocalNotifications()

        stopTime = UserDefaults.standard.object(forKey: stopTimeKey) as? Date
        if let time = stopTime {
            if time > Date() {
                startTimer(time, includeNotification: false)
            } else {
                notifyTimerCompleted()
            }
        }
    }

    @IBAction func didTapStartButton(_ sender: Any) {
        let time = datePicker.date
        if time > Date() {
            startTimer(time)
        } else {
            timerLabel.text = "timer date must be in future"
        }
    }
}

// MARK: Timer stuff

private extension ViewController {
    func registerForLocalNotifications() {
        if #available(iOS 10, *) {
            UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
                guard granted, error == nil else {
                    // display error
                    print(error ?? "Unknown error")
                    return
                }
            }
        } else {
            let types: UIUserNotificationType = [.alert, .sound, .badge]
            let settings = UIUserNotificationSettings(types: types, categories: nil)
            UIApplication.shared.registerUserNotificationSettings(settings)
        }
    }

    func startTimer(_ stopTime: Date, includeNotification: Bool = true) {
        // save `stopTime` in case app is terminated

        UserDefaults.standard.set(stopTime, forKey: stopTimeKey)
        self.stopTime = stopTime

        // start Timer

        timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(handleTimer(_:)), userInfo: nil, repeats: true)

        guard includeNotification else { return }

        // start local notification (so we're notified if timer expires while app is not running)

        if #available(iOS 10, *) {
            let content = UNMutableNotificationContent()
            content.title = "Timer expired"
            content.body = "Whoo, hoo!"
            let trigger = UNTimeIntervalNotificationTrigger(timeInterval: stopTime.timeIntervalSinceNow, repeats: false)
            let notification = UNNotificationRequest(identifier: "timer", content: content, trigger: trigger)
            UNUserNotificationCenter.current().add(notification)
        } else {
            let notification = UILocalNotification()
            notification.fireDate = stopTime
            notification.alertBody = "Timer finished!"
            UIApplication.shared.scheduleLocalNotification(notification)
        }
    }

    func stopTimer() {
        timer?.invalidate()
    }

    // I'm going to use `DateComponentsFormatter` to update the
    // label. Update it any way you want, but the key is that
    // we're just using the scheduled stop time and the current
    // time, but we're not counting anything. If you don't want to
    // use `DateComponentsFormatter`, I'd suggest considering
    // `Calendar` method `dateComponents(_:from:to:)` to
    // get the number of hours, minutes, seconds, etc. between two
    // dates.

    @objc func handleTimer(_ timer: Timer) {
        let now = Date()

        if stopTime! > now {
            timerLabel.text = dateComponentsFormatter.string(from: now, to: stopTime!)
        } else {
            stopTimer()
            notifyTimerCompleted()
        }
    }

    func notifyTimerCompleted() {
        timerLabel.text = "Timer done!"
    }
}

顺便说一句,以上内容还说明了本地通知的使用(以防定时器在应用当前未运行时到期)。



 类似资料:
  • 这是在它自己的函数中,在同一个文件中: 你知道如何使用YouTube SDK在后台继续播放声音吗?还有,是不是可以只播放背景中的声音,不播放视频? 感谢任何帮助。

  • 本文向大家介绍bootstrap快速制作后台界面,包括了bootstrap快速制作后台界面的使用技巧和注意事项,需要的朋友参考一下 最近看了bootstrap的一个小的视频,快速的做出一个后台界面;介绍了一些典型的用法; 里面涉及了: 下拉菜单、胶囊菜单、胶囊菜单垂直显示、栅格排列、导航栏、字体图标、 图片样式、输入组、折叠菜单panel、面包屑、表格样式、分页组件样式; 下面将跟着项目做出的小例

  • 问题内容: 有什么方法可以知道我的应用程序的状态是后台模式还是前台模式。谢谢 问题答案: 将返回应用程序的当前状态,例如: UIApplicationStateActive UIApplicationStateInactive UIApplicationStateBackground 迅捷3+ 目标C

  • 移动互联快速开发平台 采用Mongodb为底层数据库:数据设计随需而变; 采用Mongodb集群,支撑大数据量,大并发实时查询,便于扩展; 采用SpringMongodb简化开发,简单得令人发指; 采用SpringRest提供JSON的输出,支持各种转换; 提供程序整合、兼容中文、跨域JSONP的支持; 进行了大数据量的压力测试,参数的最优配置; 各种最佳实践。 HTML5 快速开发的前端架构,专

  • 问题内容: 我希望iOS11版本能够解决无提示推送问题,该问题已在最新的Beta和GM版本的iOS中出现。 目前,我一直在努力理解为什么我没有收到任何静默的推送消息,这实际上应该唤醒我的应用程序以在后台执行一些所需的任务。 在iOS 10中,我仅使用后台获取功能,并在AppDelegate中实现了“唤醒代码”,如以下代码所示。 在iOS 11中,注册代码仍然可以正常工作,我的后端还向Apple D

  • 快Star,没有笔试 没有八股,更多的是聊了聊实习经历 面试官感觉蛮aggressive的 说的是给30分钟做几道题,结果第一题反转链表就寄了(感觉快手很爱考这道题),太久没写,犯了个愚蠢的小错误,调了半天,最后只做出来这一道题 估计要挂了 最后问了问部门情况,感觉这个JD也蛮奇怪的,既有toC的大模型业务开发(这和其他后台开发有啥不一样的?),也有给算法爷的AI Infra。