NSUserDefault takes a few seconds to store the value - ios

As the title says, I am trying to store coins in NSUserDefault.
This is how I register it in the AppDelegate -didFinishLaunchingWithOptions:
let defaults:UserDefaults = UserDefaults.standard
defaults.register(defaults: ["firstLaunch":true,
"name":"",
"mycoins":0,
"didRate":false,])
That works just fine.
This is currently how I store it:
extension UserDefaults {
func incrementIntegerForKey(key:String, by: Int) {
let int = integer(forKey: key)
set(int + by, forKey:key)
}
}
UserDefaults.standard.incrementIntegerForKey(key: "mycoins", by: +1)
UserDefaults.standard.synchronize()
I am using this to check for the amount:
print("Coins: \(UserDefaults.standard.integer(forKey: "mycoins"))")
So let's say I increment the score a few times, and fully closes the app 2 seconds after running the increment and sync function, the new score doesn't fully store. Maybe one or two coins get's stored, but not all five. Now - if I wait 7-10 seconds after running that same function, it get's stored perfectly.
What am I doing wrong here? I am using synchronize() so it doesn't wait for the sync. How can I make it store at the exact time?

Related

How to call API only once in 30min in swift5?

I am calling one API, but that data is not changing frequently and I am storing data in core data. I want that API should call only once in 30 min. is there any better approach of calling API only if it exceed 30 min from last API call. I can think of Timer based, but like to know if there is any other better way to do same?
If you are calling the api randomly then holding a variable in memory for the last successful call might be good enough. If you want the API to be called automatically you'll best approach would be to set up a timer.
If you want your app to prohibit to make a new call in 30 min, then this would be a quick example:
(wrote this for a playground)
var lastCheck: Date?
let minimumMinutes = 60.0
func makeNetworkCall() {
if let lastCheckDate = lastCheck, lastCheckDate.timeIntervalSinceNow < (30 * minimumMinutes) {
debugPrint("Not making call, Didn't go 30 min yet")
return
}
lastCheck = Date()
debugPrint("Making network call!")
// ... make call
}
makeNetworkCall() // Should make call
makeNetworkCall() // Should not make call
makeNetworkCall() // Should not make call
I haven't tested the code above, but it should work.
To just limit service calling for a specific time (ex 30 minutes), you can store last service called date and use it to decide to do a call or not. You can store the date in memory or persistent storage depend on you need.
UserDefaults can be an option to store last date for persistance. There is a sample implementation below;
func saveLastServiceCalledDate() {
UserDefaults.standard.set(Date(), forKey: "lastServiceCallDate")
}
func isCalledInLast30Min() -> Bool {
guard let lastDate = UserDefaults.standard.value(forKey: "lastServiceCallDate") as? Date else { return false }
let timeElapsed: Int = Int(Date().timeIntervalSince(lastDate))
return timeElapsed < 30 * 60 // 30 minutes
}
func serviceCall() {
// ignore if called in last 30 minutes
if isCalledInLast30Min() { return }
// save current date
saveLastServiceCalledDate()
// do service call
}
My suggestion is to use DispatchSourceTimer because it can be restarted at any time.
Call startTimer() in viewDidLoad and in applicationWillBecomeActive to get the most recent data when the application becomes active
var timer : DispatchSourceTimer!
func startTimer()
{
if timer == nil {
timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
timer.schedule(deadline: .now(), repeating: 30.0)
timer.setEventHandler {
self.callAPI()
}
timer.activate()
} else {
timer.schedule(deadline:.now(, repeating: 30.0)
}
}
There is no way but timer
1- Create a Timer with 1 minute schedule
2- Timer function checks current timeStamp against a stored 1 say in defaults
3- If stored value is nil or exceeded 30 minutes gap between the current call the api
4- When you call the api update the stored value with the current 1
The reason behind making it a stored value not global is freguently opening and closing the app won't cause non-new api calls
let current = Date().timeIntervalSince1970
let stored = UserDefaults.standard.double(forKey:"stored")
if stored == 0 || current - stored >= 30.0 {
// call the api && update stored value
}
You haven't mentioned whether this should happen in background or foreground? Because based on that only we need to go for the solution. In case if u are wondering about update the data in the background, you should check apples BGTaskBackground. But the problem with this is, you can't decide the time to trigger. You can only give minimumFetchingInterval, which is not guaranteed but will be decided by the system/is.
Incase if you are looking to update in the foreground, just go with the timer approach you are talking about. Use any background queues to do that job. Queues will help you out in dispatching specific task at specific time with delay method.

how to detect if a task has execute in the last 2 minutes in swift 3?

I have a project that has Json in it - when a user come back to the home page because of view didLoad method the app will start getting son again and I want this But I want the app detect that if the user has came back to the home page in the last 2 minutes the app doesn't get the json - simply I want to run a task when user go to a view controller but if user has came back to the view controller in the last 2 minutes the app doesn't execute task and for example if the user open the app and go to the another page after 3 minutes when he came back to the home page the task start - as you see here I can use timer but the timer will run the task every minutes I want to limit this as I said
weak var timer: Timer?
func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 60.0, repeats: true) { [weak self] _ in
// do something here
}
}
func stopTimer() {
timer?.invalidate()
}
// if appropriate, make sure to stop your timer in `deinit`
deinit {
stopTimer()
}
You would need to save the Date(time) object when the task gets completed in the completion handler and then next time when you are about to start the task you would need to check the time elapsed.
Set a Date in UserDefaults in the completion handler of your task.
Before proceeding to start task check whether this Date exists and if exists, then the elapsed time is greater than 120 seconds(2 minutes) or not.
func startTaskIfPossible() {
let date = UserDefaults.standard.object(forKey: "taskCompletionDate") as? Date
guard let prevCompletionDate = date else {
startTask()
return
}
guard Date().timeIntervalSince(prevCompletionDate) > 120 else {
return
}
startTask()
}
func startTask() {
//Set Date in userdefaults in completion handler of task
// UserDefaults.standard.setValue(Date(), forKey: "taskCompletionDate")
}
Save the last json request execution time in one of the keys in your NSUserdefaults.
let userDefaults = NSUserDefaults.standardUserDefaults()
// save to user defaults
userDefaults.setObject(NSDate(), forKey: "LastExecutionDate")
Everytime your user come to home page and you want to fire json request, just compare it with last execution time. If its more than 2 mins fire it otherwise don't.
// retrieve from user defaults
let lastExecutionDate = userDefaults.objectForKey("LimitReachedOnDate") as? NSDate
Then your currentDate - lastExecutionDate > 180 seconds. This is just a algo and not the exact code for date comparison but i guess you will get it. this

Track the time it takes a user to navigate through an iOS app's UI

I want to measure how long (in seconds) it takes users to do certain things in my app. Some examples are logging in, pressing a button on a certain page, etc.
I am using an NSTimer for that. I am starting it in the viewDidLoad of a specific page, and stopping it at the point that I want to measure.
I also want to measure cumulative time for certain things. I would like to start the timer on the log-in screen, and then continue the timer until the user gets to the next view controller and clicks on a certain button.
I'm not sure how to do this. Should create a global variable in my app delegate? Or is there some other better way?
No need for an NSTimer, you just need to record the start times and compare them to the stop times. Try using a little helper class such as:
class MyTimer {
static let shared = MyTimer()
var startTimes = [String : Date]()
func start(withKey key: String) {
startTimes[key] = Date()
}
func measure(key: String) -> TimeInterval? {
if let start = startTimes[key] {
return Date().timeIntervalSince(start)
}
return nil
}
}
To use this, just call start(withKey:) right before you start a long-running task.
MyTimer.shared.start(withKey: "login")
Do something that takes a while and then call measure(key:) when you're done. Because MyTimer is a singleton, it can be called from anywhere in your code.
if let interval = MyTimer.shared.measure("login") {
print("Logging in time: \(interval)")
}
If you're using multiple threads, you may to to add some thread safety to this, but it should work as is in simple scenarios.

Listen For Incoming Value Using Firebase

I'm using firebase to manage the server for my game. The game needs input from users on either end.
The app allows players to play a small game, where either player picks a random card and then the server compares the two. However, when one player picks a card before the other, I need the app to wait until the value is received. So, I need the app to 'wait' until a value is entered, then the app will continue. That way I can compare either value (the value from player 1 and the value from player 2)
This is how I'm attempting it right now:
#IBAction func aceOfClubs(_ sender: Any) {
cardSelected(imageString: "aceOfClubs", cardValue: aceOfClubsValue)
}
When a player presses a button it runs this function:
private func cardSelected(imageString: String, cardValue: Int) {
print("card selected")
self.currentPlayerChosenCard.image = UIImage(named: imageString)
setCardValueForPlayer(player: 1, cardValue: cardValue)
var playerOneCardValue = checkCardValue(player: 2)
while playerOneCardValue == 0 {
print("while loop performed")
playerOneCardValue = checkCardValue(player: 2)
}
if playerOneCardValue > 0 {
print("if statement performed")
compareCardValuesForWinner()
commitToNewPhase()
}
}
Now, let me be clear, the issue isn't the functions I'm using inside this function, its how to listen for the other players value. Currently right now I have a while loop that keeps going until the value is changed, however this eventually causes the app to crash is it's generating so much junk.
How can I do this in a better and more efficient way that will allow me to 'listen' and wait for the other players input, and for the app to receive it? In other words, how can I wait for the other players data with out continuing with the app?

Swift: Hide a View Controller after first time use

In swift I am making a app where it asks for a code before going to the actual app. I only want this to be showed once. How do I have this page only showed the first time then they enter the code and they only have to do this once then when they use the app again they don't need to enter a code. Thanks.
Your best shot is NSUserDefaults. Save a flag (a boolean) in NSUserDefaults to indicate whether it is the first time the user has opened the app.
let userDefaults = NSUserDefaults.standardUserDefaults()
if userDefaults.valueForKey("IS_FIRST_TIME") as? Bool != nil {
// First time, do something...
// ...
// ...
// Now, save a flag to indicate that this was the first time.
userDefaults.setValue(true, forKey: "IS_FIRST_TIME")
userDefaults.synchronize() // Needed to save the new value.
}

Resources