When to call activateSession() on WCSession object - swift

I wonder at what point one would call activateSession() on a WCSession object on the watch and on the iOS device.
In the documentation it says:
Always assign a delegate and activate your session before calling any session-related methods. The session must be configured and activated before sending messages or obtaining information about the state of the connection.
At first thought I put my code to initialise the session:
if (WCSession.isSupported()) {
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
in viewDidLoad on the iOS device and in willActivate on the watch side.
It works... but I don't think it's a good solution.
I'm not too familiar with the app-lifecycle yet but as far as I understand those are called every time the app's are opened.
Does that result in a "reconnect" every time one of the apps is opened?
Where would be a good place to place that code?

When you put the WCSession code in viewDidLoad and willActivate it is not only called when the app is opened but every time the view controller that contains the code is shown. So that is not an ideal place.
The best place to put this code is in application:didFinishLaunchingWithOptions in your app's AppDelegate and in applicationDidFinishLaunching in your watch extensions's ExtensionDelegate
You can put all the session handling into a singleton class, as suggested in this great tutorial by #NatashaTheRobot.
That way the session is only created once for the time the app in being held in memory.
EDIT
As ccjensen pointed out in his comment, if you are using the connection for a Complication, Notification or Glance update you have to activate the session in the ExtensionDelegate's init method. applicationDidFinishLaunching will not be called in those cases.

Related

Using deinit to make a save to database

Very junior dev, so excuse the question but I'm just curious. I have a local database and I'm considering using a ViewModel to store the information which will only be persisted if the user hits the save button.
But if they send the app to background for example and forgot about the app, can I use the deinit to make the saves the database?
Now not considering the user experience in this case, I just would like to know if this method is ever used.
The app lifecycle is unpredictable enough that you can't trust deinit will be called.
When your app is sent to the background it has a few seconds to run before it's frozen, if the user doesn't use it in a while it will be killed silently, and deinit won't be called.
If the user kills the app, I don't think deinit is called either.
Like it was suggested in the comments, you should listen to life cycle events and react to those. It will change depending on how your app is setup:
UIKit + UIApplication Delegate: implement applicationDidEnterBackground(_:)
UIKit + Scene Delegate: implement sceneDidEnterBackground(_:)
Full SwiftUI: Observe changes of Scene Phase

Where to register for applicationWillResignActive using UIStoryboard

Looking at the example in Beginning iOS 5 for persisting your application state, in the first viewController that is shown for the app, they register for applicationWillResignActive in viewDidLoad:. So that makes sense to me in that you register for that notification when your first view is shown.
I'm confused on whether you always do this, or where you typically register for this notification. Q1) Like do they register for this notification in this viewController so they can recreate this view? Q2) If so, do I do this for each viewController?
Q3) I'm using UIStoryboard and my first viewController is a UITabBarController. So do I register for the notification in the first tab's viewController?
I also have a singleton DataManager object that holds the data for the app if that helps anyone guide me in the right direction of where I should save my data. Thanks!
These methods are in the AppDelegate.m
- (void)applicationWillResignActive:(UIApplication *)application
{
/*
Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
*/
}
You can perform your actions here. You can however register to "listen" to that event in another view controller (like your example) but that is just to make it easier to send that event notification into that viewcontroller.
1) no, only to ensure that whenver this VC is loaded it will be able to listen to this event.
2) no, only for the ones you want to pass the even easily like this. However using the appdelegate.m and each's vc view did appear is better.
3) depends on the kind of data you want to save, but typically you create your own file and just save it to disk like in any other OS. ios gives you access to the "documents" folder of your app and each folder is used for something specific, read the documentation.

applicationDidBecomeActive getting called twice

My app delegate method applicationDidBecomeActive: is getting called twice for the first time launch of the application. I have some portion of code which I want to execute only once & that I have put into applicationDidBecomeActive:
What should I do?
I got the issue. I am using Location Services. When launching for the first time after I tap on "OK" on the location services alert, my applicationDidBecomeActive gets called one more time which is the normal iOS behavior.
If you want to call your code only once when app becomes active, try calling it from two methods.
didFinishLaunchingWithOptions
applicationWillEnterForeground
instead of calling it only from applicationDidBecomeActive.
This is because of location or push notification alert.
After the native location/push notification has been dismissed, applicationDidBecomeActive will be called.
I don't know if this will help, but I just had the same issue with a totally simple app that doesn't use Location Services, and I found out it's an illusion. Look at the logging messages I got:
2012-12-22 10:47:45.329 Bizarro[10416:907] start applicationDidBecomeActive:
2012-12-22 10:47:45.333 Bizarro[10416:907] end applicationDidBecomeActive:
2012-12-22 10:47:45.329 Bizarro[10416:907] start applicationDidBecomeActive:
2012-12-22 10:47:45.333 Bizarro[10416:907] end applicationDidBecomeActive:
Look closely. Look at the times. The first and third messages have the same time. The second and fourth messages have the same time. They are the same messages! It's an Xcode bug; it has nothing to do with my code. Xcode is reporting the same log messages twice.
In my case, I was able to prevent this by turning off all Behaviors for Running -> Generates Output.
What about:
Increment on applicationDidBecomeActive
Decrement on callback events of permissions requests or other alerts that trigger another applicationDidBecomeActive when closed.
With Xcode 6 there's a new reason this can happen: when you launch an app in a resizable simulator, applicationDidBecomeActive: will get called twice.
It launches the app with the default size class, and then applies the size you were last using—even if you were using the defaults. Any time a change in size class is applied, applicationDidBecomeActive: gets called.
When app launches first time
it calls sequentially,
didFinishLaunchingWithOptions
applicationDidBecomeActive (Twice)
When we open the Control Center it calls only,
applicationDidBecomeActive
When app come from background to foreground it calls sequentially
applicationWillEnterForeground
applicationDidBecomeActive
I'm currently seeing double notifications.
It's happening because my AppDelegate's init code is being called twice.
It's being called once when the main() does [[NSBundle mainBundle] loadNibNamed:#"MainMenu" owner:application topLevelObjects:&tl] (ie, when the .XIB file is loaded), becaues the .XIB file is setting up FirstResponder to my custom AppDelegate, and then it's being called again when main() calls [[myAppDelegate alloc] init].
The init code is what does the addObserver calls, so two observers are being set up for each notification I care about, which is why my notifications get called twice.
I'm a newbie OS X programmer, so I'm not yet sure of the best way to resolve these two, but wanted to post it here in case it's of help to others, another place to look.
Have you possibly created an instance of your class in Interface Builder AND in your AppDelegate code, perhaps?
If you have code you want called only once when the app starts up, then use
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
instead.
Otherwise, applicationDidBecomeActive will be called whenever your app becomes active again, so that doesn't just mean twice as in your case, but every time the user switches back to your app after switching to another.
If you use RxSwift, then you can just throttle the application event, so it doesn't call twice in the same second:
NotificationCenter.default.rx.notification(Notification.Name.UIApplicationDidBecomeActive)
.throttle(1, scheduler: MainScheduler.instance)
.subscribe { (event) in
self.appEnteredFromBackground()
}.disposed(by: disposeBag)
private func appEnteredFromBackground() {
Analytics.trackPageView(withScreen: .home)
dataStore.checkIfAllowingRides()
}
I put this code in my actual view controller where the logic is supposed to happen on ApplicationDidBecomeActive.
I just check at top of applicationDidBecomeActive: if the request was really sent (I made a function for this, checking status), if so I return already.
The second time in applicationDidBecomeActive:, the function reads the status as Deny or Allowed, because this is after the User answered the Alert.

Proper appDelegate method for Flurry startsession?

Flurry docs recommend placing the startSession call in applicationDidFinishLaunching:.
Two problems with this...
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
[FlurryAnalytics startSession:#"AWESOMEAPIKEY"];
// ...
}
1) Isn't application:didFinishLaunchingWithOptions: the new approved launch point?
2) This is only called once on launch, but don't we want session info every time a user opens or switches back to the app? Or does Flurry handle all that on their own by listening to some event or NSNotification?
Wouldn't a better place to put the startSession call be in applicationDidBecomeActive: or applicationWillEnterForeground:, like so?
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// ... Flurry AppCircle setup
[FlurryAnalytics startSession:#"AWESOMEAPIKEY"];
// ... your setup
}
for your case 1)
correct place to put [FlurryAnalytics startSession:#"SOMESESSIONKEY"]; is
application:didFinishLaunchingWithOptions:
you can place it there without worries. I have done this by myself and the app is working awesome at appstore and providing the stats perfectly.
for case 2), your secession will be automatically resumed when app returns to foreground so you dont have to do any special handling here.
I was real curious about this too. I looked at my inherited code for my app and didn't see any flurry activity in didbecomeactive, foreground, etc. I only saw the startsession in didfinishlaunchingwithoptions. I saw the below on the flurry site re: startsession, but i still don't get how it works, just behind the scenes stuff the flurry library does? #samfisher, can you elaborate?
"This method serves as the entry point to Flurry Analytics collection. It must be called in the scope of applicationDidFinishLaunching. The session will continue for the period the app is in the foreground until your app is backgrounded for the time specified in setSessionContinueSeconds:. If the app is resumed in that period the session will continue, otherwise a new session will begin."
FlurryApi.h shows the default as 10 for setSessionContinueSeconds so I guess Flurry handles it, I'm just looking for more confirmation.
http://support.flurry.com/sdkdocs/iOS/interface_flurry_analytics.html#a78b0b92085b38875d51f1ca0d699849a

iPhone 4: when to save data?

I have an app (a game) which saves data: game state, high scores, achievements, etc. Currently the app delegate does this on applicationWillTerminate:. After playing around with iPhone 4 for a bit, it seems that applications pretty much never terminate: they just run in the background forever, unless the user goes out of their way to quit them, or restart the phone.
So my question is, should I find another place to save my data, and if so, when?
To minimize the amount of time spent in the delegate method call, you should find a place that makes sense to save during the game (level completion, checkpoints, etc). You can also add a new delegate method to your application delegate which will be called when your application transitions to the background where you can duplicate some of the things you may have done previously in applicationWillTerminate:. The new delegate method to implement is -applicationDidEnterBackground:.
You will also receive a notification that the user switched back to your app as applicationWillEnterForeground:.
you can do so in the views diddisappear delegate method
-(void)viewDidDisappear:(BOOL)animated
{
//CODE FOR SAVING
}
There are 2 App delegate methods you can use
applicationDidResignActive: //pausing the app, used when a msg comes up. if multitasking this wont help
applicationDidEnterBackground: // called in iOS 4 for when the app is in the background
you can see when it loads into the foreground using
applicationWillEnterForeground:
check out the reference for more info
You should save in applicationDidEnterBackground. Make sure to wrap your saving code with – beginBackgroundTaskWithExpirationHandler: and endBackgroundTask, since without that, you have less than a second (or something like that) before execution suspends.