Application Refresh in Background - ios

I'm trying to implement background refresh in a iOS App . I need to call a class call "updatedropinanddropout" based on the location.I have added timer class while the application is in foreground it works fine, When the application goes to background it's not working. I mean I cannot call the class.
Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(runCode), userInfo: nil, repeats: true)
I have gone through apple documentation func setMinimumBackgroundFetchInterval(_ minimumBackgroundFetchInterval: TimeInterval)
I don't know how to use this function

Related

Is CADisplayLink callback always runs on the main thread?

I couldn't find any direct answer online nor within the docs about this.
If I'm setting up CADisplayLink with the following:
let displayLink = CADisplayLink(target: self, selector: #selector(updateShimmer))
displayLink.add(to: .current, forMode: .common)
#objc func updateShimmer() {
print(Thread.isMainThread)
}
I'm getting true. I know that I can wrap this within a DispatchQueue.main but I was wondering, is it always dispatched on the main queue? Or should I wrap it anyway?
You do not need to manually dispatch code inside your display link handler to the main thread. Timers and display links added to a particular run loop will always run on the thread associated with that run loop. For more information, see Threading Programming Guide: Run Loops.
Bottom line, if you add(to:forMode:) to the main run loop, the main run loop always runs on the main thread.
That having been said, if you want to make sure it always runs on the main thread, I would suggest being explicit and adding it to .main, not just .current. That removes any ambiguity:
let displayLink = CADisplayLink(target: self, selector: #selector(updateShimmer(_:)))
displayLink.add(to: .main, forMode: .common)
Note, I also tweaked the signature of updateShimmer to accept a parameter. The display link will be passed to it. It’s often useful to have that reference inside the method. And, regardless, it makes one's code more self-evident: You can just glance at this method now and understand that this is a display link handler:
#objc func updateShimmer(_ displayLink: CADisplayLink) {
...
}

Run a task at fixed time everyday

I would like to run a function (make an api call) at fixed time everyday, say 10 am and 10 pm daily. What would be the cronjob equivalent in swift?
I tried implementing Timer as:
var dateComponents = DateComponents()
dateComponents.timeZone = TimeZone(abbreviation: "NPT")
dateComponents.hour = 10
dateComponents.minute = 00
let userCalendar = Calendar.current
let myTime = userCalendar.date(from: dateComponents)
let timer = Timer(fireAt: myTime!, interval: 0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: false)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
The selector function:
#objc func updateTimer() {
print("Hello")
}
Instead of being run at the specified time, the selector function gets executed everytime I run the app and not at the specified time. How can I solve this?
Edit: I’ll be needing this to be run from the background as well. I’ll be using location service in my app.
There is no way to achieve your goals without using push notifications. Timer's are using run loops and hence aren't working in the background. There's no background mode for making API calls at regular intervals either.
The only option is to send out push notifications from your server at the specified times every day and in response to receiving the push notification in your app, make the API call. Of course you'll need internet connection for push notifications to work, but since you want to make API calls, this won't make a difference as you'd need internet connection for the API calls to succeed anyways.

Sending app to background and re-launching it from recents in XCTest

I was looking for a solution to my problem where in I need to send my app to background and re-launch it from the recents after a particular time interval.
deactivateAppForDuration() was used to achieve this in Instruments UIAutomation.
Does anybody know how to achieve that in XCTest?
Not positive if this will work, as I haven't tested it yet, but it's worth a shot. If nothing else it should give you a good idea on where to look.
XCUIApplication class provides methods to both terminate and launch your app programmatically: https://developer.apple.com/reference/xctest/xcuiapplication
XCUIDevice class allows you to simulate a button press on the device: https://developer.apple.com/reference/xctest/xcuidevicebutton
You can use these along with UIControl and NSURLSessionTask to suspend your application.
An example of this process using Swift 3 might look something like this (syntax may be slightly different for Swift 2 and below):
func myXCTest() {
UIControl().sendAction(#selector(NSURLSessionTask.suspend), to: UIApplication.shared(), for: nil)
Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(launchApp), userInfo: nil, repeats: false)
}
func launchApp() {
XCUIApplication().launch()
}
Another way may be simply executing a home button press, and then relaunching the app after a timer passes:
func myXCTest {
XCUIDevice().press(XCUIDeviceButton.Home)
Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(launchApp), userInfo: nil, repeats: false)
}
Neither of these ways may do what you're asking, or work perfectly, but hopefully, it will give you a starting point. You can play with it and find a solution that works for you. Good luck!
If you can use Xcode 8.3 and iOS 10.3 with your current tests, then you might want to try this:
XCUIDevice.shared().press(XCUIDeviceButton.home)
sleep(60)
XCUIDevice.shared().siriService.activate(voiceRecognitionText: "Open {appName}")
Be sure to include #available(iOS 10.3, *) at the top of your test suite file.
This will be relatively equivalent to deactivateAppForDuration(). Just change the sleep() to your desired duration.

How can I run a method after n minutes I received a geofence event in iOS?

I'm working on a Geofence based iOS application and I would to know when the user stays more than 5 minutes inside a place.
Now, the geofence part is already done and working, I get the "enter" and "exit" events, but I want to execute some methods 5 minutes after I entered in a geofenced area, if I don't left it.
The main problem here is that NSTimers will not work with the app closed and I don't know how to focus this.
Any ideas?
Thank you for your time!
P.S: CLVisit class is not valid on this case because the events for this class are not "in real time" and we cannot set a time-inside.
You can use NSTimer when your app is Active like this :
let timer = NSTimer(timeInterval: 1.0, target: self, selector: #selector(self.printStr), userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
and also if your app did receive UIApplicationWillTerminateNotification event you can save the current date to e.g userDefaults and use it after the user run the application again. Another way is to use UILocalNotification after you receive AppWillTerminate event, you should schedule the UILocalNotificaiton and trigger it after 5 minutes.
Hope it helps you

UIScrollView pauses NSTimer until scrolling finishes

While a UIScrollView (or a derived class thereof) is scrolling, it seems like all the NSTimers that are running get paused until the scroll is finished.
Is there a way to get around this? Threads? A priority setting? Anything?
An easy & simple to implement solution is to do:
NSTimer *timer = [NSTimer timerWithTimeInterval:...
target:...
selector:....
userInfo:...
repeats:...];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
For anyone using Swift 3
timer = Timer.scheduledTimer(timeInterval: 0.1,
target: self,
selector: aSelector,
userInfo: nil,
repeats: true)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
tl;dr the runloop is handing scroll related events. It can't handle any more events — unless you manually change the timer's config so the timer can be processed while runloop is handling touch events. OR try an alternate solution and use GCD
A must read for any iOS developer. Lots of things are ultimately executed through RunLoop.
Derived from Apple's docs.
What is a Run Loop?
A run loop is very much like its name sounds. It is a loop your thread
enters and uses to run event handlers in response to incoming events
How delivery of events are disrupted?
Because timers and other periodic events are delivered when you run
the run loop, circumventing that loop disrupts the delivery of those
events. The typical example of this behavior occurs whenever you
implement a mouse-tracking routine by entering a loop and repeatedly
requesting events from the application. Because your code is grabbing
events directly, rather than letting the application dispatch those
events normally, active timers would be unable to fire until after
your mouse-tracking routine exited and returned control to the
application.
What happens if timer is fired when run loop is in the middle of execution?
This happens A LOT OF TIMES, without us ever noticing. I mean we set the timer to fire at 10:10:10:00, but the runloop is executing an event which takes till 10:10:10:05, hence the timer is fired 10:10:10:06
Similarly, if a timer fires when the run loop is in the middle of
executing a handler routine, the timer waits until the next time
through the run loop to invoke its handler routine. If the run loop is
not running at all, the timer never fires.
Would scrolling or anything that keeps the runloop busy shift all the times my timer is going to fire?
You can configure timers to generate events only once or repeatedly. A
repeating timer reschedules itself automatically based on the
scheduled firing time, not the actual firing time. For example, if a
timer is scheduled to fire at a particular time and every 5 seconds
after that, the scheduled firing time will always fall on the original
5 second time intervals, even if the actual firing time gets delayed.
If the firing time is delayed so much that it misses one or more of
the scheduled firing times, the timer is fired only once for the
missed time period. After firing for the missed period, the timer is
rescheduled for the next scheduled firing time.
How can I change the RunLoops's mode?
You can't. The OS just changes itself for you. e.g. when user taps, then the mode switches to eventTracking. When the user taps are finished, the mode goes back to default. If you want something to be run in a specific mode, then it's up to you make sure that happens.
Solution:
When user is scrolling the the Run Loop Mode becomes tracking. The RunLoop is designed to shifts gears. Once the mode is set to eventTracking, then it gives priority (remember we have limited CPU cores) to touch events. This is an architectural design by the OS designers.
By default timers are NOT scheduled on the tracking mode. They are scheduled on:
Creates a timer and schedules it on the current run loop in the
default mode.
The scheduledTimer underneath does this:
RunLoop.main.add(timer, forMode: .default)
If you want your timer to work when scrolling then you must do either:
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self,
selector: #selector(fireTimer), userInfo: nil, repeats: true) // sets it on `.default` mode
RunLoop.main.add(timer, forMode: .tracking) // AND Do this
Or just do:
RunLoop.main.add(timer, forMode: .common)
Ultimately doing one of the above means your thread is not blocked by touch events.
which is equivalent to:
RunLoop.main.add(timer, forMode: .default)
RunLoop.main.add(timer, forMode: .eventTracking)
RunLoop.main.add(timer, forMode: .modal) // This is more of a macOS thing for when you have a modal panel showing.
Alternative solution:
You may consider using GCD for your timer which will help you to "shield" your code from run loop management issues.
For non-repeating just use:
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
// your code here
}
For repeating timers use:
See how to use DispatchSourceTimer
Digging deeper from a discussion I had with Daniel Jalkut:
Question: how does GCD (background threads) e.g. a asyncAfter on a background thread get executed outside of the RunLoop? My understanding from this is that everything is to be executed within a RunLoop
Not necessarily - every thread has at most one run loop, but can have zero if there's no reason to coordinate execution "ownership" of the thread.
Threads are an OS level affordance that gives your process the ability to split up its functionality across multiple parallel execution contexts. Run loops are a framework-level affordance that allows you to further split up a single thread so it can be shared efficiently by multiple code paths.
Typically if you dispatch something that gets run on a thread, it probably won't have a runloop unless something calls [NSRunLoop currentRunLoop] which would implicitly create one.
In a nutshell, modes are basically a filter mechanism for inputs and timers
Yes, Paul is right, this is a run loop issue. Specifically, you need to make use of the NSRunLoop method:
- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
You have to run another thread and another run loop if you want timers to fire while scrolling; since timers are processed as part of the event loop, if you're busy processing scrolling your view, you never get around to the timers. Though the perf/battery penalty of running timers on other threads might not be worth handling this case.
This is the swift version.
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
for anyone use Swift 4:
timer = Timer(timeInterval: 1, target: self, selector: #selector(timerUpdated), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: .common)
Tested in swift 5
var myTimer: Timer?
self.myTimer= Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
//your code
}
RunLoop.main.add(self.myTimer!, forMode: .common)

Resources