Communicating Asynchronous Data Retrieval status between UIViewControllers - ios

I'm doing some prefetching of data to reduce waiting time experience. In viewDidLoad of UIViewController A, I initiate an asynchronous function from a downloader class to call to the server to grab some small pieces of data. The downloader class stores this information in some Core Data tables. Then, when UIViewController B comes along, it uses that data to populate a table.
When I was running this on a simulator, it worked without exception. But as soon as I put it on a real phone and untethered the phone from the computer, the app crashed. When I got to VC B, I click on a button to open up that table. That button click would just hang, and the app would never recover. I checked the crash log and got the 0x8badf00d error. I'm doing my testing on an old iPhone 4, which could be part of my problem, but the app needs to run on all iPhone versions, not just newer ones.
OK, so I suspected why this was happening - the data wasn't back yet. As a quick and dirty way to test this, I put in a boolean user default that I set to NO from VC A. Then, when the downloader class got the data, it set that same variable to YES. In VC B in viewDidLoad, I put in a busy loop to make it wait until the value had been set to YES. When I ran the app, it instantly cleared the hanging problem, and the wait time in VC B was instant. The user would never know that this was present.
Despite this momentary success, I think that this approach of mine is terrible! Is there a better way than using user defaults? I don't know how to employ a delegate pattern here, and notifications might not work either. I realize that I am blocking the UI thread, but this is intentional. I can't let the user open that table before there is data available. Is there a more elegant approach that is more consistent with Objective C patterns?
I can post code if it will help. Thank you!

I would probably go with notifications for something like this and do your downloading in the background (I couldn't tell if you were doing it in the background or not).
To run the download code in the background do something like
[[DownloadManager instance] performSelectorInBackground:#selector(doDownload) withObject:nil];
In the doDownload class post a notification when it is done
//Downloading code
...
[[NSNotificationCenter defaultCenter] postNotificationName:#"DownloadsDidFinish" object:self];
In your VC B listen for the notification.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(downloadDidFinish:) name:#"DownloadsDidFinish" object:nil];
In downloadDidFinish: you may want to do a performSelectorOnMainThread depending on if you care if you are on a background thread or not at this. You can also post the notification on the main thread by doing the performSelectorOnMainThread at the time of posting the notification.

You can have ViewController B as a delegate with downloadedStarted and downloadedStopped methods. And you can start and stop spinning wheel animation in these methods:
[busyIndicator startAnimating];

Related

Managing local notifications for iOS framework with beacons

I have been working on iOS framework (in Swift) which contains beacon functionality. I made it work except that I'm not sure how to handle scenario where I'm in foreground and I encounter multiple beacons in short duration.
If I want didReceive delegate method to show Alert for beacon while in foreground, and if I encounter many beacons it will not work nicely (alerts will display one over another). Is there some solution to queue notifications somehow?
Also I would like to know, if there is a way to make all that logic for receiving local notifications inside my framework?
I have to be able to support iOS-8.0 so I can't use Notification Center which is available from iOS-10.0
Can I create some class which would act like appdelegate (probably some class which would implement UIApplicationDelegate inside framework), is something like that possible?
I want to put as much code as I can inside framework itself so that it won't be too messy job for someone to include that framework with all functionality.
After some time I figured out a way to make this. I'm beginner in iOS with few months experience so I can't say if this solution is the best but it works for me.
I found a way to implement all push and local notification related delegate methods from framework. Basically if main application wants framework to take care of notifications without having to implement anything yourself, on runtime framework will dynamically implement certain UIApplicationDelegate methods for AppDelegate.swift class (or whatever is your AppDelegate class called).
I used object_getClass(UIApplication.shared.delegate!) to get the main class.
Then I used func class_addMethod(_ cls: AnyClass!, _ name: Selector!, _ imp: IMP!, _ types: UnsafePointer!) -> Bool
to implement delegate methods for push and local notifications from inside framework so now it comes down to write one or two lines to use framework entirely with working notifications and beacon location services instead of having to write a lots of code outside framework.
As for handling notifications in foreground mode I made that work by adding them to queue so that if more than one notification comes, and wants to be displayed in foreground regime, only one will be displayed by UIAlertController and the rest will be put in queue and sent again but with some small delay (I set fire date to be some value which I thought was appropriate in my case) after user makes an action regarding that first notification which was the only one presented.
These are just my ideas for the problems I had, if someone shows interest for these solutions I will write more details if needed. I will also gladly accept any criticism.

Network lost on screen lock unlock

Encountered a very weird problem, using simple AFNetworking downloading operation, even tried with simple NSURLConnection operation, connection fails if you keep your app running, and lock screen and then unlock. Works absolutely fine in background though.
Any one encountered similar problem with NSURLConnection want to share some solution?
Thanks.
It looks like an iOS bug. Weird, but lock screen action affects NSURLSession somehow, so that it stops working and returns NSURLErrorNetworkConnectionLost. So in my app I gave up using shared session. I either use a new session object for every request or (if I need to maintain one session constantly) recreate it every time the screen gets unlocked. And it works. For users of AFNetworking or any other third party library working on top of NSURLSession the situation is harder, of course. You'll need to correct the code of the library, which is definitely not a good thing, but I think there's no other choice
Very helpful Andrey Chernukha,
In my case, figured out that you don't necessary need to recreate new session every time.
I ended up using array to save running NSURLSessionDataTasks and after phone is unlocked resume them.
Steps:
I created array NSMutableArray *dataTasksToResume
In - (void)applicationWillResignActive:(UIApplication *)application I saved all tasks to dataTasksToResume array
Cancel all running NSURLSessionDataTasks
In - (void)applicationDidBecomeActive:(UIApplication *)application get all tasks from array and resuming them (re-creating them)
Enjoy!
Hope it helps.

Programmatically Terminate an iOS App Extension [duplicate]

At some point in my application I have done this exit(0) which crashes my app. But I haven't figured out what method gets called when this executes.
I've put messages in:
(void)applicationWillTerminate:(UIApplication *)application
(void)applicationDidEnterBackground:(UIApplication *)application
But none of this seem to get called! Any idea about what method is called when exit(0) is done?
From Apple's Human User Guidelines...
Don’t Quit Programmatically
Never quit an iOS application programmatically because people tend to
interpret this as a crash. However, if external circumstances prevent
your application from functioning as intended, you need to tell your
users about the situation and explain what they can do about it.
Depending on how severe the application malfunction is, you have two
choices.
Display an attractive screen that describes the problem and suggests a
correction. A screen provides feedback that reassures users that
there’s nothing wrong with your application. It puts users in control,
letting them decide whether they want to take corrective action and
continue using your application or press the Home button and open a
different application
If only some of your application's features are not working, display
either a screen or an alert when people activate the feature. Display
the alert only when people try to access the feature that isn’t
functioning.
If you've decided that you are going to quit programmatically anyway...
In C, exit(0) will halt execution of the application. This means that no delegate methods or exception handlers will be called. So, if the goal is to make sure that some code gets called when the closes, even on a forced close, there may be another option. In your AppDelegate implement a custom method called something like -(void)applicaitonIsgoingAway. Call this method from within anywhere you want your exiting code to be called:
applicationWillTerminate
applicationDidEnterBackground
onUncaughtException
The first two are ones that you already mentioned in your question. The third can be a catch-all of sorts. It's a global exception handler. This next bit comes from a question on that very topic.
This exception handler will get called for any unhanded exceptions (which would otherwise crash your app). From within this handler, you can call applicaitonIsgoingAway, just like in the other 2 cases. From the other question that I mentioned above, you can find an answer similar to this.
void onUncaughtException(NSException* exception)
{
[[AppDelegate sharedInstance] applicationIsgoingAway];
}
But in order for this to work, you need to set this method up as the exception handler like so...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSSetUncaughtExceptionHandler(&onUncaughtException);
//There may already be more code in this method.
}
Now, you can quit the app programmatically by calling NSAssert(FALSE, #"Quitting the app programmatically."); As long as there is no other exception handler in place to catch this, your app will begin to crash, and your exception handler code will be called. in-turn calling applicationIsGoingAway.
When you call exit(0) you immediately terminate your application. 0 is a status code which means successful termination.
No other method is called, you application just dies. As a result end user may think app is just crashed.
Apple discourages you to call exit anywhere.
exit(0) is a C function that terminates your app's process therefore none of the application delegates methods will be called, the app will be killed immediately. Apple recommends strongly against your app quitting because it appears broken to the user.
There is no Apple-supported method to terminate your application programmatically. Calling exit is certainly out of the question. This causes all sorts of bugs (for example the multitasking switcher will break badly) as well as simply being wrong.
If you are trying to disable multitasking, you can do this with the UIApplicationExitsOnSuspend key in your Info.plist file (the title for the key is "Application does not run in background").
Other than that, it's up to your users to press the home button to close your application.
these methods will be called but you cannot use exit(0) you will need to press the back button to close your app then these methods will be called

iOS Dropbox Sync API - detecting newer status

I'm integrating the Dropbox Sync SDK into my iOS app. I want to set it up so that when the user is in a UIDocument, and then the app becomes inactive (home button, lock, etc.), and then the file is changed by someone else in Dropbox, and then the user returns to the app, they will be notified that changes were made elsewhere. Here's what I have now:
In my viewDidLoad I have:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(checkForNewerStatus) name:UIApplicationDidBecomeActiveNotification object:nil];
Then I have this method:
- (void)checkForNewerStatus
{
if (self.dropboxFile.newerStatus)
{
//alert user of changes
}
}
This works semi-desirably. When the app first comes back, self.dropboxFile.newerStatus returns NO. If I leave the app and come back a second time, then it returns YES. But I need it to return YES the first time the app comes back. This isn't time related - I can wait several minutes before coming back, and it still fails the first time and succeeds the second. Any ideas?
Many thanks!
Note: this problem only occurs if the app becomes inactive and then the file is changed. If the file is changed while the app is still active, and then the app leaves and comes back, it alerts as expected.
#rmaddy's comment above is correct. Just moving it into an answer.
You need to add an observer on your DBFile to get notified when it changes. What's happening is that your app is being restarted and your immediately checking the status, but since the app just started, it hasn't had time yet to talk to the server and learn about the newer file content. An observer on the DBFile will fire as soon as the app is able to communicate with the server and find out about the change.

Notification not occurring for added passes in Passbook

I'm very simply trying to be notified when a Pass is added to a passbook.
One thing to note is that I'm also attempting to use a Pass that I generated using a different apple dev account than what my app is using. I'm trying to figure out if that is part of the problem or not.
This is one VC in a 3-tab application.
ViewController.m:
#interface ViewController ()
{
PKPassLibrary *_passLibrary;
NSArray *_passes;
}
#end
"viewDidLoad":
- (void)viewDidLoad
{
//init passbook
_passLibrary = [[PKPassLibrary alloc] init];
_passes = [_passLibrary passes];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(passLibraryDidChange:) name:PKPassLibraryDidChangeNotification object:_passLibrary];
}
and my notification handler:
- (void)passLibraryDidChange:(NSNotification *)notification
{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"%#", #"passes added");
});
}
When I run the app in the iOS Simulator, all works as expected and I can see the log output to the console in Xcode.
When I run on the device, the notification is not called when the Pass is added. On the device, I can't even list any passes.
What's even more strange, is that when I go to delete the Pass from passbook, then re-enter the app, the notification will be called.
ps: I really hope it's something simple I'm missing here.
EDIT: updated with more information and a more complete code sample
You don't seem to be retaining a handle to your PKPassLibrary instance. Create a strong #property on your UIViewController subclass. Alloc+Init the property and configure the notification listening in the viewDidLoad (so it'll only get done once).
It might be more appropriate to set this on the UIApplication somewhere, but that depends on the app logic and how your ViewController fits into your UI (for example does it get replaced or released while you still need access to PassKit notifications?)…
Nick.
P.S. Any reason you're NSLogging on the GCD main queue so explicitly? Is that just left over from some UI feedback?
P.P.S. Seeing the notification when you re-enter the app makes perfect sense since that's when viewWillAppear will get called and a new PKPassLibrary instance will immediately fire the notification that's waiting. Not sure why it worked in the simulator - must be accidental.
It came down to having the pass type identifiers created from the same source that the provisioning profile came from.
I then had to take the new Pass Certificates and add them to my keychain, then re-create the actual passes using the new pass type identifiers.
Meanwhile, In Xcode..in the summary section of my target in the Entitlements section, I can refresh the Passes and see the new pass type id's come in.
Once the app runs, I now get properly notified of any change (addition/subtraction/etc).
I also needed to make sure my _passLibrary was being properly retained.

Resources