iOS Run Code Once a Day

iOS Run Code Once a Day

Here's the situation regarding background execution and notifications and timers etc. in relation to an app scheduling some activity to happen periodically.

  1. An app cannot execute in the background unless:

    1. It requests extra time from the OS to do so. This is done using beginBackgroundTaskWithExpirationHandler. It is not specified (intentionally) by Apple how long this extra time is, however in practice it is around 10 minutes.

    2. An app has a background mode, the modes are: voip, audio, location, newstand. Even if it has one of these types an app cannot execute without some restrictions. The rest of this discussion assumes the app does not have a background mode.

  2. When an app is suspended it cannot do ANYTHING to rouse itself directly. It cannot previously have scheduled an NSTimer, it cannot make use of something like performSelector:afterDelay. etc.

    The ONLY way the app can become active again is if the USER does something to make it active. The user can do this from via of the following:

    1. Launch the app directly from its icon

    2. Launch the app in response to a local notification that was previously scheduled by the app while it was active.

    3. Launch the app in response to a remote notification sent by a server.

    4. A few others: such as URL launching if the app is registered to deal with launching via a url; or if its registered to be capable of dealing with a certain type of content.

If an app is in the foreground when a local/remote notification fires then the app receives it directly.

If the app is not currently in the foreground when a local/remote notification fires then the app DOES NOT receive it. There is no code that is executed when the notification fires!

Only IF the user selects the notification will the app become active and it can execute.

Note that the user can disable notifications, either for the entire device, or just for a specific application, in which case the user will never see them. If the device is turned off when a notification is due to fire then it is lost.

iOS: execute code AFTER a particular date

Your best option is to store it as local data Even though you only want the code to run once, the overhead is so low, the "check" will not impact the speed or feel of the application. Also this will allow you to run additional checks .. If someone deletes the app, for instance, and leaves the local storage behind. If they re-install you could theoretically "remember" that the application has been installed, and said code has already run (until the user clears application data)

Something like:

//Globally set key
struct defaultsKeys {
static let keyDate = "dateKey"
}


// Set the date in local storage
let defaults = UserDefaults.standard
defaults.set("Your Date String", forKey: defaultsKeys.dateKey)


// Get the date from local storage
let defaults = UserDefaults.standard
if let stringDate = defaults.string(forKey: defaultsKeys.dateKey) {
print(stringDate)
// Do your date comparison here
}

Very few lines of code, and even though the check happens every time the application starts .. The overhead is negligible.

How can I run a piece of code only once in swift?

What you need is persistence between app launches. Fortunately, this exists and is provided with NSUserDefaults.

I would do something like the following in your app delegate didFinishLaunching method:

let hasLaunchedKey = "HasLaunched"
let defaults = UserDefaults.standard
let hasLaunched = defaults.bool(forKey: hasLaunchedKey)

if !hasLaunched {
defaults.set(true, forKey: hasLaunchedKey)
}

// Use hasLaunched

The first time you run the app, the key will not be in the defaults database and will return false. Since it is false, we save the value of true in the defaults database, and each subsequent run you will get a value of true.

Call a function one time per day at a specific time Swift

You cannot guarantee that your app is running at that time, each day. the phone could of lost all of its battery power, app might not be running... etc etc.

But this is just a logical problem... If you want to refresh the data after a certain time each day, you should just store a last refresh time in UserDefaults, and if that time was more than your required expiry time (in this case 24h) then refresh the data and update the last updated time.

So if you have an active user, as soon as the 24 hour time difference expires it would refresh, or if a users phone was off for a few days (for whatever reason) your app would still know it needs to refresh the data once the user comes back to the app.

EDIT: Added code example

class RefreshManager: NSObject {

static let shared = RefreshManager()
private let defaults = UserDefaults.standard
private let defaultsKey = "lastRefresh"
private let calender = Calendar.current

func loadDataIfNeeded(completion: (Bool) -> Void) {

if isRefreshRequired() {
// load the data
defaults.set(Date(), forKey: defaultsKey)
completion(true)
} else {
completion(false)
}
}

private func isRefreshRequired() -> Bool {

guard let lastRefreshDate = defaults.object(forKey: defaultsKey) as? Date else {
return true
}

if let diff = calender.dateComponents([.hour], from: lastRefreshDate, to: Date()).hour, diff > 24 {
return true
} else {
return false
}
}
}

You don't really have to make this a new class or a singleton like I have. It was just easier to keep everything nicely contained for the sake of the answer.

So this will work that if there is no refresh date, or if the number of hours since the last one is greater than 24 then it will update the data and update the last refresh date/time. using the completion it will also return whether it updated data or not.

You could also add a check in here that the current hour of the day is >= 16 if required.

EDIT 2: User selectable hour

private func isRefreshRequired(userPickedHour: Int = 16) -> Bool {

guard let lastRefreshDate = defaults.object(forKey: defaultsKey) as? Date else {
return true
}

if let diff = calender.dateComponents([.hour], from: lastRefreshDate, to: Date()).hour,
let currentHour = calender.dateComponents([.hour], from: Date()).hour,
diff >= 24, userPickedHour <= currentHour {
return true
} else {
return false
}
}

In this modified isRefreshRequired function you can pass a value for the hour and see whether it has been at least 24 hours since the last refresh and that the current hour is or is greater than the users selected hour.

This doesn't mean it will run at say for example exactly 16:00 by the way. it will run when the user loads that screen and the rules pass (min 24 hours passed, currentHour is >= userSelected hour)

EDIT 3: how to call

class ViewController: UIViewController {

let refreshManager = RefreshManager.shared

override func viewDidLoad() {
super.viewDidLoad()

refreshManager.loadDataIfNeeded() { success in
print(success)
}
}
}

EDIT: Changed logic

if let diff = calender.dateComponents([.day], from: lastRefreshDate, to: Date()).day,
let currentHour = calender.dateComponents([.hour], from: Date()).hour,
diff >= 1, userPickedHour <= currentHour {
return true
} else {
return false
}

Run code at midnight everyday

No timer needed. Observe the notification

static let NSCalendarDayChanged: NSNotification.Name

Posted whenever the calendar day of the system changes, as determined by the system calendar, locale, and time zone. This notification does not provide an object.

If the the device is asleep when the day changes, this notification will be posted on wakeup. Only one notification will be posted on wakeup if the device has been asleep for multiple days.

NotificationCenter.default.addObserver(self, selector:#selector(calendarDayDidChange), name:.NSCalendarDayChanged, object:nil)

...

func calendarDayDidChange()
{
print("day did change")
}

Or with the closure based API

NotificationCenter.default.addObserver(forName: .NSCalendarDayChanged, object: nil, queue: .main) { _ in
print("day did change")
}

Or with Combine

var subscription : AnyCancellable?

subscription = NotificationCenter.default.publisher(for: .NSCalendarDayChanged)
.sink { _ in print("day did change") }

Or with async/await (Swift 5.5+)

Task {
for await _ in NotificationCenter.default.notifications(named: .NSCalendarDayChanged) {
print("day did change")
}
}

Perform task once a day

You can achieve this by using NSUserDefaults on your AppDelegate.

An easy representation for saving;

[[NSUserDefaults standardUserDefaults] setObject:@"YourDateHere" forKey:@"lastAmazonCheckDate"];
[[NSUserDefaults standardUserDefaults] syncronize];

And retrieving;

[[NSUserDefaults standardUserDefaults] objectForKey:@"lastAmazonCheckDate"];

With NSUserDefaults you can save the last time you checked for your list.

Swift Execute function once a day with Firebase

Just an idea: you can use Timer/DateTime check & UserDefaults. For example you can set some setting in UserDefaults "Performed Calculation" which will contain date of last call of the function. Every run your app should check if setting contains today's date or not. If yes - skip, if not - call calc. + you can use Timer to run it every XXX seconds.



Related Topics



Leave a reply



Submit