Swift 如何在后台自动触发代码?

How to automatically trigger code in background in Swift?

初学者尝试创建一个应用程序,为用户提供一个随机的“每日词汇”。

通过测试,我意识到用户必须每天打开该应用程序才能手动触发函数isRefreshRequired()(检索新词和更新用户界面和通知内容)。但我需要自动完成此操作,无论用户是否打开应用程序。

这可能吗?我被困在使用哪种方法之间:

  1. NSCalendarDayChanged. I found this useful answer 但我不确定它会在哪里实施?如果我使用它 applicationDidFinishLaunching,函数 calendarDayDidChange() 会在哪里被调用?在 viewDidLoad()?

  2. significantTimeChangeNotification() - 可能是更好的方法?同样,不确定如何实际实施以及在何处实施。该文件还说:

If the the device is asleep when the day changes, this notification will be posted on wakeup.

wakeup是指应用重新打开的时候吗?因为我希望 isRefreshRequired() 函数即使在用户没有触摸应用程序的情况下也能执行。

  1. Background App Refresh: 这似乎没有用,因为它需要 URLSession 并且用户可能禁用了后台刷新。对于一个只想给用户一个新词的应用来说似乎有点过分了!

有问题的代码:

    override func viewDidLoad() {
            super.viewDidLoad()
            
    isRefreshRequired()
        }

    func isRefreshRequired() {
                // if it is first time opening app
                if userDefaults.bool(forKey: "First Launch") == false {
                    //run code during first launch
                    _ = updateWordOfTheDay()
                    NotificationManager.askNotificationPermission()
                    print("First time opening app")
                    userDefaults.set(true, forKey: "First Launch")
                }
                else {
                    //run code after first launch
                    print("Not first time opening app")
                    userDefaults.set(true, forKey: "First Launch")
    
                    let lastAccessDate = userDefaults.object(forKey: "lastAccessDate") as? Date ?? Date()
                    userDefaults.set(Date(), forKey: "lastAccessDate")
    
                    // if it is the same day give the vocab picked for that day
                    if calendar.isDateInToday(lastAccessDate) {
                        readFromUserDefaults()
                        print("No refresh required as it is the same day as lastAccessDate which was \(lastAccessDate)")
                    }
                    else {
                        print("new day since lastAccessDate")
                        _ = updateWordOfTheDay()
                    }
    
                }
            }

    func sendNotification(using: ChineseVocab) {
        
        let center = UNUserNotificationCenter.current()
        center.removeAllDeliveredNotifications()
        center.removeAllPendingNotificationRequests()
        
let content = UNMutableNotificationContent()
        content.title = using.Chinese + " " + using.Pinyin
        content.body = using.Definition

}

    func saveToUserDefaults(with data: ChineseVocab) {
            let encoder = JSONEncoder()
            if let encoded = try? encoder.encode(data) {
                userDefaults.set(encoded, forKey: "selectedVocab")
            }
        }
    
         func readFromUserDefaults() {
            if let savedVocab = userDefaults.object(forKey: "selectedVocab") as? Data {
                let decoder = JSONDecoder()
                if let wordOfTheDay = try? decoder.decode(ChineseVocab.self, from: savedVocab) {
                updateLabels(using: wordOfTheDay)
                 NotificationManager.sendNotification(using: wordOfTheDay)
                    print("readFromUserDefaults() is working: \(wordOfTheDay)")
                } else {
                    print("unable to load readFromUserDefaults()")
                }
            }
        }

您正在尝试做的是在应用程序关闭或在后台时调用一个函数。退后一步,用户此时实际上并未使用您的应用程序。因此,您的应用也不应该真正成为 运行 函数。我相信这是故意的,以便 phone 可以将资源分配给用户正在积极使用的 apps/functions。

所以没有办法在应用程序关闭时只调用一个函数。但是偏离你上面的观点:

  1. 我认为使用 applicationDidBecomeActive 或 applicationDidFinishLaunching 可能是您最好的选择。这些是 AppDelegate 中的函数,您可以直接调用方法中的函数。 applicationDidBecomeActive 将在每次应用程序出现在屏幕上时执行。如果你只需要在一个屏幕上使用它,你可以调用 ViewController 的 viewDidAppear / viewWillAppear 来代替。

  2. 我不知道任何关于 significantTimeChangeNotification 的事情,所以我不想告诉你它不起作用,但我猜如果应用程序关闭,通知将不会执行。

  3. 后台应用程序刷新基本上就是您想要做的,但是,即使您实现了它..它只会在应用程序处于 background[=27= 时执行].如果应用程序完全关闭,则不会调用后台提取。所以我假设大多数时候它不会被调用。