ios Magical Record save on UIApplicationWillTerminateNotification - ios

I have app that stores system events to Core Data Database. To perform saving I use MagicalRecord.
So, in my logger class in init I have:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleDidFinishLaunchingNotification) name:UIApplicationDidFinishLaunchingNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleWillTerminateNotification) name:UIApplicationWillTerminateNotification object:nil];
In handle functions I store simple entity with text and timestamp property:
- (void)handleDidFinishLaunchingNotification
{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
DBMessage *dbMessage = [DBMessage createEntityInContext:localContext];
dbMessage.text = #"Did finish launching";
dbMessage.timestamp = [NSDate date];
}];
}
- (void)handleWillTerminateNotification
{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
DBMessage *dbMessage = [DBMessage createEntityInContext:localContext];
dbMessage.text = #"Will terminate";
dbMessage.timestamp = [NSDate date];
}];
}
When I open and close (without crash) app few times, in my DB I can see "Did finish launching" entries (more that one, so I'm sure app was closed, not only moved to BG), but none of "Will terminate".
I would be less surprised if the launching event were missed, because I could expect that init method will be called after notification is posted.
What I can do to store terminate events?

I have an answer:
It looks like when application gets terminated, wont't let me to save in new background thread. But saving in current thread works fine. So all I had to do was change saving method to save in current thread, by calling saveWithBlockAndWait: instead of saveWithBlock:
- (void)handleWillTerminateNotification
{
[MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) {
DBMessage *dbMessage = [DBMessage createEntityInContext:localContext];
dbMessage.text = #"Will terminate";
dbMessage.timestamp = [NSDate date];
}];
}
Now events are succesfully saved on DB.

Related

Using applicationDidBecomeActive To Check If User Logged In

I have an app that has a "Home Screen" with login and register options. When a user completes one of the above the data is stored in a shared instance. Now for security reasons I was looking at using the applicationDidBecomeActive to periodically check to make sure the user is still active on the server or not blocked by calling a method in the shared instance. If the user is not active the app kicks them to the home screen with a prompt.
My issue is however that when the app loads for the first time `applicationDidBecomeActive is called and because the user is not logged in you end up with a loop.
What is the correct approach for dealing with this issue? Ideally I want to use applicationDidBecomeActive but I only want to perform the check whilst in the account section of the app.
Any help would be great.
Thanks.
There are several ways of implementing this.
Way 1: Fire a Notification using NotificationCenter.default and implement a listener in your Accounts-ViewController.
Way 2: In your AppDelegate, get active ViewController (depends on which rootViewController you are using) and if that ViewController has the type AccountVC trigger a public function.
Way 3: Combine or use Way1 and Way2 in a different object and notify your ViewController in your preferred way.
And many more.
Post some code for more specific help :)
You can use either of the following methods to deal with the issue.
Approach 1
- (void)checkWhetherAppIsActive {
UIApplicationState appState = [[UIApplication sharedApplication] applicationState];
if (appState == UIApplicationStateActive) {
}else if (appState == UIApplicationStateInactive) {
}else if (appState == UIApplicationStateBackground) {
}
}
Approach 2
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
}];
When the user logs in , generate an expiryTimeStamp. And whenever the app is active, you can check the currentTimestamp to expiryTimeStamp . If the currentTimestamp is before the expiryTimeStamp, then consider it as active session!
- (void)applicationDidBecomeActive:(UIApplication *)application {
//compare current NSDate with expiryDate
if(current date is before expiry date){
//active session
}else{
//log out the user
}
}
You can generate expiry date as:
-(void)generateExpiryTimeStamp{
[[NSUserDefaults standardUserDefaults]setObject:[NSDate dateWithTimeIntervalSinceNow:900] forKey:#"tokenExpiry"]; //Expiry date Set to 15mins
[[NSUserDefaults standardUserDefaults]synchronize];
}

how to update value after NSNotificationCenter event happened?

Hi i know this question maybe silly but anyway i ask it now.
i have this function :
- (int)getToday {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:#"fa_IR"];
[dateFormatter setDateFormat:#"dd"];
int day = [[dateFormatter stringFromDate:[NSDate date]] intValue];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(handleSysTimeChanged:)
name:NSSystemClockDidChangeNotification
object:nil];
return day;
}
and after that i handle notification like this :
-(void) handleSysTimeChanged: (NSNotification*) notification
{
if (NSSystemClockDidChangeNotification) {
NSLog(#"%i", [self getToday]);
}
}
and i get exactly the changes i want in my NSLOG. BUT after that how can i update my getToday to show new number after notification happened.
I'm new to objective-c and NSNotificationCenter. So don't be mad.
Firstly, as #rmaddy said, you should not observe for notifications inside of getToday. This code here is the problem:
[nc addObserver:self
selector:#selector(handleSysTimeChanged:)
name:NSSystemClockDidChangeNotification
object:nil];
You only want to set up your observer once and if you call getToday on each notification, it's going to be called a ton of times. Repetitive addObserver calls CAN break things. Set this up inside of applicationDidFinishLaunchingWithOptions or your class's initialization method. Also, don't forget to call removeObserver before your application terminates or you'll get some really weird errors.
Secondly, I don't think what you're doing is broken. Based on your provided code, you're getting the current date with [NSDate date] each time that the notification is received. This should provide you with the correct day as desired.

NSMetadataQuery not working

I am trying to use iCloud in my app. In the simulator, I notice that it takes a short amount of time after launch for new files in iCloud to be added to the ubiquity container. So to refresh my UI to reflect these changes, I set up an NSMetadataQuery as shown:
_query = [[NSMetadataQuery alloc] init];
_query.searchScopes = #[ubiquityContainer];
_query.predicate = [NSPredicate predicateWithFormat:#"%K == '*'", NSMetadataItemFSNameKey];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(refreshFiles) name:NSMetadataQueryDidUpdateNotification object:nil];
[_query startQuery];
refreshFiles is never called...
My app saves its files directly in ubiquityContainer (an NSURL) - it doesn't create any subdirectories...
What am I doing wrong?
I found out what the issue was! I needed to use NSMetadataQueryUbiquitousDataScope as the search scope. That fixed the problem!

How to update a UILabel once per day

I have a UILabel that randomly will show text from a list I have provided.
I want the UILabel to show one item per day.
What's the best way to handle this?
Should I use an NSTimer or is there a different method?
I'm not worried about a specific time of the day, just that the UILabel updates once per day.
Thank you!
One option would be to save the current date into NSUserDefaults when you show the label.
When your view controller is loaded you get that date from NSUserDefaults. If the difference between the saved date and "now" is more than 24 hours you update the label (and save the new date), otherwise show the current label.
You probably also want the view controller to listen for the "will enter foreground" notification. Each time your app returns to the foreground you will want to do the same check.
Store the date in preferences, and compare when the app comes into the foreground. Your appDelegate would look something like this:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setObject:[NSDate date] forKey:#"savedDate"];
[prefs synchronize];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSDate *savedDate = [[NSUserDefaults standardUserDefaults] objectForKey:#"savedDate"];
NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSCalendarUnitDay fromDate:savedDate toDate:[NSDate date] options:0];
if ([dateComponents day] >= 1) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"updateLabel" object:nil];
}
}
Then in your view controller, listen for the notification:
-(void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateLabel) name:#"updateLabel" object:nil];
}
-(void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
-(void) updateLabel {
//update your label here
}
For updating at midnight, check out UIApplicationSignificantTimeChangeNotification. There is a relevant answer here: https://stackoverflow.com/a/15537806/1144632

Correct management of addObserverForName:object:queue:usingBlock:

I'm still new to blocks in objective-c and wondering if I have this psuedo code correct. I'm not sure if it's enough to just remove the observer or if i have to call removeObserver:name:object:
-(void) scan {
Scanner *scanner = [[Scanner alloc] init];
id scanComplete = [[NSNotificationCenter defaultCenter] addObserverForName:#"ScanComplete"
object:scanner
queue:nil
usingBlock:^(NSNotification *notification){
/*
do something
*/
[[NSNotificationCenter defaultCenter] removeObserver:scanComplete];
[scanner release];
}];
[scanner startScan];
}
Update: I'm receiving intermittent EXC_BAD_ACCESS from this block, so this can't be right.
Declare the scanComplete variable before defining the block itself.
The reason why you need to do this is because you're trying to access a variable that doesn't exist within the block at the time of definition since the variable itself has not been assigned yet.
What is EXC_BAD_ACCESS? Well, it's an exception that is thrown when you try to access a reference that doesn't exist. So that is exactly the case in your example.
So if you declare the variable before the block itself, then it should work:
-(void) scan {
Scanner *scanner = [[Scanner alloc] init];
__block id scanComplete;
scanComplete = [[NSNotificationCenter defaultCenter] addObserverForName:#"ScanComplete"
object:scanner
queue:nil
usingBlock:^(NSNotification *notification){
/*
do something
*/
[[NSNotificationCenter defaultCenter] removeObserver:scanComplete];
[scanner release];
}];
[scanner startScan];
}
You should not unregister in the register block. Instead, store the token returned from addObserverForName (in this case, your scanComplete) as an instance variable or in a collection that is an instance variable, and unregister later when you're about to go out of existence (e.g. in dealloc). What I do is keep an NSMutableSet called observers. So:
id ob = [[NSNotificationCenter defaultCenter]
addObserverForName:#"whatever" object:nil queue:nil
usingBlock:^(NSNotification *note) {
// ... whatever ...
}];
[self->observers addObject:ob];
And then later:
for (id ob in self->observers)
[[NSNotificationCenter defaultCenter] removeObserver:ob];
self->observers = nil;
Apple Document about this method:
The following example shows how you can register to receive locale change notifications.
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
self.localeChangeObserver = [center addObserverForName:NSCurrentLocaleDidChangeNotification object:nil
queue:mainQueue usingBlock:^(NSNotification *note) {
NSLog(#"The user's locale changed to: %#", [[NSLocale currentLocale] localeIdentifier]);
}];
To unregister observations, you pass the object returned by this method to removeObserver:. You must invoke removeObserver: or removeObserver:name:object: before any object specified by addObserverForName:object:queue:usingBlock: is deallocated.
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center removeObserver:self.localeChangeObserver];
The scope of the block doesn't have permission to release the scanner object. If you're not using garbage collection, removing the release and making the scanner autorelease ([[[Scanner alloc] init] autorelease]) should do the trick.
You should also be able to safely move the call to removeObserver outside of the block.
For the case of EXC_BAD_ACCESS: Entering bt in the console window after the application crashes will give you a backtrace, and should inform you where the error occurred.

Resources