propagate errors up the call stack - objective c - ios

In a typical layered architecture, how can I effectively communicate errors originating in data access layer to a UIViewContoller?
I have following design:
UIViewControllers --> datacontroller --> specificserviceproxy --> serviceproxybase
serviceproxybase initiates calls to a web service. I check for network availability before invoking operations on the web service, and want to alert users in case of network breakdown.
What is the best practice solution? Thanks.

Follow the pattern used my much of Cocoa-touch. Many methods return nil or NO when there is an error and such methods have an NSError out parameter with details about the error.
You can propagate the result and error up the layers as needed. Or a layer may wrap the error with a more layer specific error and pass up the new error.
Example methods that follow this pattern are:
NSFileManager copyItemAtPath:toPath:error:
NSFileManager attributesOfItemAtPath:error:

You can use NSNotificationCenter to post and receive NSNotifications for various error states.
In serviceproxybase you would post the notification for any interested observers:
[[NSNotificationCenter defaultCenter] postNotificationName:#"NoConnectionNotification" object:nil userInfo:someErrorInfoObject];
Typically you would package up an object in someErrorInfoObject that gives some additional details to the observer, such as an NSError object, the service request that failed, or a service's error code. In a view controller you would listen for that notification in viewDidLoad or viewWillAppear:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleNoConnectionError:)
name:#"NoConnectionNotification"
object:nil];
Now whenever a notification with the name NoConnectionNotification is posted, the view controller will receive a message to handleNoConnectionError: with the corresponding NSNotification.

Related

Problems using notifications as a callback?

I am trying to update a view when something happens in another class, and after some looking, it appeared that the most common way to do this was to use either delegates or blocks to create a callback. However, I was able to accomplish this task using notifications. What I want to know is: Is there a problem using notifications to trigger methods calls? Are there any risks I'm not aware of? Is there a reason I'd want to use blocks/delegates over notifications?
I'm new to Objective-C, so I'm not sure if the approach I'm taking is correct.
As an example, I'm trying to set the battery level of a BLE device on the ViewController. I have a BluetoothLEManager, which discovers the peripheral, its services/characteristics, etc. But to do this, I need to initiate the "connection" in the detailViewController, then update the battery level once I find it.
Here is some example code of what I'm doing:
DetailViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"Selected tag UUID: %#", [selectedTag.tagUUID UUIDString]);
tagName.text = selectedTag.mtagName;
if(selectedTag.batteryLevel != nil){
batteryLife.text = selectedTag.batteryLevel;
}
uuidLabel.text = [selectedTag.tagUUID UUIDString];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(setBatteryLevel:) name:#"SetBatteryLevel" object:nil];
}
...
-(void)setBatteryLevel:(NSNotification*)notif{
NSMutableString* batLevel = [[NSMutableString alloc]initWithString:[NSString stringWithFormat:#"%#", selectedTag.batteryLevel]];
[batLevel appendString:#" %"];
selectedTag.batteryLevel = batLevel;
batteryLife.text = selectedTag.batteryLevel;
}
BluetoothLEManager.m:
...
-(void) getBatteryLevel:(CBCharacteristic *)characteristic error:(NSError *)error fetchTag:(FetchTag *)fetchTag
{
NSLog(#"Getting battery Level...");
NSData* data = characteristic.value;
const uint8_t* reportData = [data bytes];
uint16_t batteryLevel = reportData[0];
selectedTag.batteryLevel = [NSString stringWithFormat:#"%i", batteryLevel];
NSLog(#"Battery Level is %#", [NSString stringWithFormat:#"%i", batteryLevel]);
[[NSNotificationCenter defaultCenter] postNotificationName:#"SetBatteryLevel" object:nil];
}
...
Let me know if you need any other code, but this is the basics of it all.
Each approach has different strengths and weaknesses.
Delegates and protocols require a defined interface between the object and it's delegate, a one-to-one relationship, and that the object have specific knowledge of the delegate object it's going to call.
Methods with completion blocks involve a similar one-to-one relationship between an object and the object that invokes the method. However since blocks inherit the scope in which they're defined, you have more flexibility as to the context that's available in the completion block. Blocks also allow the caller to define the completion code in same place that the call takes place, making you code more self-documenting.
In both cases, the object that is notifying the delegate or invoking the completion block has to know who it's talking to, or what code is being executed.
A delegate call is like an auto shop calling you back to let you know your car is done. The service manager has to have your phone number and know that you want a call.
A block is more like a recipe you give to a chef. Give the chef a different recipe and he/she performs a different task for you.
Notifications are much less tightly coupled. It's like a town crier, yelling announcements in a crowded public square. The crier doesn't need to know who's listening, or how many people are listening.
Likewise, when you send a notification, you don't know who, if anybody, is listening, or how many listeners there are. You don't need to know. If 10 objects care about the message you are broadcasting, they can all listen for it, and they'll all be notified. The message sender doesn't have to know or care who's listening.
Sometimes you want tighter coupling, and sometimes you want looser coupling. It depends on the problem you're trying to solve.

iOS Google Analytics memory growing out of control FAST

I've used Google Analytics on several iOS apps. No problems. This time, problem.
I do the basic setup using version 3.0. Add library/header, include required frameworks, and stuff the boiler plate code into the AppDelegate.m. So far so good, everything works as expected. I take my first UIViewController and change it to extend GAITrackedViewController and it hits the fan. The app freezes up on the first screen and memory usage starts going up about 4Meg per second. So I change the UIViewController back and all is good. I try making the screen name call manually in viewDidLoad.
// Analytics
id tracker = [[GAI sharedInstance] defaultTracker];
[tracker set:kGAIScreenName value:#"Initial"];
[tracker send:[[GAIDictionaryBuilder createAppView] build]];
Same thing happens. My view controller has a couple custom container views and it the root view controller on a generic UINavigationViewController. I figure it's probably the custom containers confusing it about which is the active view controller and what screen name to use (but I'm not seeing any sign of this in the logging).
Has anyone run into this problem and been able to nail down exactly what's causing it and how to work around it?
João's answer is correct, but I'd like to explain it more.
From Google's Getting Started document
If your app uses the CoreData framework: responding to a notification,
e.g. NSManagedObjectContextDidSaveNotification, from the Google
Analytics CoreData object may result in an exception. Instead, Apple
recommends filtering CoreData notifications by specifying the managed
object context as a parameter to your listener.
What that means is...
// This code will cause a problem because it gets triggered on ANY NSManagedObjectContextDidSaveNotification.
// (both your managed object contact and the one used by Google Analytics)
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(managedObjectContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:nil];
// This code is safe and will only be trigger from the notification generated by your Managed Object Context.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(managedObjectContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:myManagedObjectContext];
Now I read the docs and I had done this properly, but I was still having the problem. Turns out I didn't update my code for when I removed the notification.
// Not Safe
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:nil];
// Safe
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:myManagedObjectContext];
The moral of the story is, pay attention to your notification listeners. It takes a couple seconds to specify a listener for a specific object and it can take a long time to debug an issue because you accidentally listening to events you don't want to or remove listening to events.
Solution 1: Use a specific moc on object parameter when observing NSManagedObjectContextDidSaveNotification, this will allow you to observe only saves on the given moc.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(managedObjectContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:managedObjectContext];
Solution 2: If you are using the Core Data technique of merging mocs created on background threads, you cannot easily solve in the suggested way, so the alternative is to change your method that handles notification in order to avoid merging when the persistentStoreCoordinator of the saved moc doesn't match the persistentStoreCoordinator of your main moc.
- (void)managedObjectContextDidSave:(NSNotification *)notification {
if ([NSThread isMainThread]) {
NSManagedObjectContext *savedMoc = notification.object;
// Merge only saves of mocs that are not my managedObjectContext
if (savedMoc == self.managedObjectContext) {
return;
}
// Merge only saves of mocs that share the same persistentStoreCoordinator of my managedObjectContext (i.e.: ignore the save of Google Analytics moc)
if (savedMoc.persistentStoreCoordinator != self.managedObjectContext.persistentStoreCoordinator) {
return;
}
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
else {
[self performSelectorOnMainThread:#selector(handleBackgroundContextSaveNotification:) withObject:notification waitUntilDone:YES];
}
}
I was having the exact same problem.
I've managed to find the solution in my case: I was registering to NSManagedObjectContextDidSaveNotification without specifying the context:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(managedObjectContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:nil];
Removing this listener solved my out of memory problems.
Cheers
Im working on XMPP Framwork and as soon i fixed GA crash. This one failed https://github.com/robbiehanson/XMPPFramework/blob/master/Extensions/CoreDataStorage/XMPPCoreDataStorage.m
ANy easy way to solve this issue.
https://github.com/robbiehanson/XMPPFramework/issues/577

How to avoid deallocated objects being accessed in callbacks/etc?

The issue has been discussed here and here, but I wonder if there is a more solid way to solve this whether you have delegates or not - when a function is called after a delay.
At a certain point in a program, at a button push, an object - a CCLayer - is created. That layer creates several objects, some of them at callbacks. That created object layer has a "back" button which destroys it. I am running into a problem when the callbacks, etc are triggered AFTER that object is destructed and try to access objects that don't exist anymore - where the "message sent to deallocated instance 0x258ba480" gives me this good news. How do I avoid that?
1) Is there a way to kill the callbacks (because I obviously don't need them anymore)
2) should/can I test for the existence of these possibly non-existent objects at the callbacks themselves
3) something else?
(My callback is code for checking for an internet connection that I copied from this illustrious website - may it live long and prosper-, using Reachability, and I could solve the problem by simply moving it to the main view and setting a flag on the child view, but I don't want to.)
- (void)testInternetConnection
{
internetReachableFoo = [Reachability reachabilityWithHostname:#"www.google.com"];
// Internet is reachable
internetReachableFoo.reachableBlock = ^(Reachability*reach)
{
// Update the UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Yayyy, we have the interwebs!");
//I do the net stuff here
});
};
// Internet is not reachable
internetReachableFoo.unreachableBlock = ^(Reachability*reach)
{
// Update the UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Someone broke the internet :(");
noNetMessageLabel.visible=true; //<-------this goes kaboom
noNetFlag=true;
});
};
[internetReachableFoo startNotifier];
}
There are basically two ways to avoid deallocated delegates from being messaged:
Hold onto the objects you want to message later. That way they won’t get deallocated. This is the case with block callbacks – if a block references some object, the object gets retained until the block ceases to exist. If you message some objects from a block and hit a deallocated object, you must have screwed up the memory management somewhere.
Clear the delegation link before you release the delegate. Nowadays this is usually done using weak, zeroing properties that are automatically set to nil when the referenced object is deallocated. Very convenient. Not your case.
You might consider several options:
First, you may just check for existence of an object before passing message to it:
if (noNetMessageLabel)
noNetMessageLabel.visible = true;
But personally I consider that as a bad architecture.
More wise decision, from my point of view, would be move the code of displaying any alert regarding internet connectivity to the model.
Create method like this in AppDelegate or in the model:
- (NSError*)presentConnectivityAlert
{
if () //any error condition checking appropriate
[[NSNotificationCenter defaultCenter]
postNotificationName:#"connectivityAlert"
object:self
userInfo:userInfo];
}
Also you may consider moving internet checking code to the model too.
In the ViewControllers of your app implement listening to this notification.
- (void)viewDidLoad {
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(didReceiveRemoteNotification:)
name:#"connectivityAlert"
object:nil];
}
- (void)viewDidUnload {
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:#"connectivityAlert"
object:nil];
}
-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo {
if (self.isViewLoaded && self.view.window) {
//Present the user with alert
}
}
Thus you have more general and quite versatile approach to handle connectivity issues throughout all your application.
Is there a way to kill the callbacks
It's not possible to cancel block (in your case), but it's possible to cancel NSOperation in NSOperationQueue. But that will require to rewrite your implementation of Reachability.

Updating UI from IOS library

Could somebody give me guidance/starting point on how I would update a UI Text Label in an IOS app from a library.
I have temperature data being received from a BT module in a library connected to my app. I want to send that Integer data to my app and update the UI Text label.
NOTE: I have full access to the library.
Any help is appreciated
I think what you are asking for is a way to provide feedback to an application from your Cocoa static library.
I would suggest that you have a look at the NSNotificationCenter class. For example, say that you have a class BTThermometer that, upon the reception of a new measurement calls:
[[NSNotificationCenter defaultCenter] postNotificationName:#"com.my.BTThermometer.NewValue" object:self];
Then, in you application, you could do something like:
[[NSNotificationCenter defaultCenter] addObserverForName:#"com.my.BTThermometer.NewValue" object:self queue:nil usingBlock:^(NSNotification* n) {
dispatch_async(dispatch_get_main_queue(), ^{
someLabel.text = ((BTThermometer*)n.object).temperatureValue;
}
}];
This is a standard mechanism in iOS to decouple your application from the internal workings of your library. The only coupling is the name itself, and of course it is usually a good idea to use a constant (e.g. #define kMyTemperatureEvent #"com.my.BTThermometer.NewValue) so that the compiler catches any typos.

How to make program wait for asynchronous NSURLConnection to finish before proceeding with next command?

How can I make my program wait for an asynchronous NSURLConnection to finish before going to the next line of code?
SetDelegate *sjd= [SetDelegate alloc];
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:post delegate:sjd];
[connection start];
This is how I start the connection and I handle the data received in the delegate but I want to wait for the connection to end before proceeding mainly because this is in a for loop and it has to run for each element in my database.
I need to put data from the phones database to a remote database and after the data was successfully put in the data in the phones database is deleted. I am going through each element in the phone's database and start a connection that's why I don't see how the next stuff can be done from the loop. I'm a beginner when it comes to objective-c programming so I'm not sure if this is the right way or not to do it
Making the call synchronous is not an option because it blocks the program and i have a progress bar that should show.
Your question is a bit odd. You have impossibly constrained the issue. You cannot have a line of code "wait" for a process to finish w/o it blocking something, in this case whatever thread the loop is running in.
You can use a synchronous call if you wanted to, it doesn't block your app, it only blocks the thread it is executed on. In your example, you have a loop that is continually getting remote data and you want your UI to reflect that until it is done. But you don't want your UI blocked. That means, this thread with your loop already MUST be on a background thread so you can feel free to do a synchronous call in the loop w/o blocking your UI thread. If the loop is on the UI thread you need to change this to do what you want.
You could also do this using an asynchronous connection. In that case, your operation may actual complete faster b/c you can have multiple requests in progress at the same time. If you do it that way, your loop can remain on the UI thread and you just need to track all of the connections so that when they are finished you can communicate that status to the relevant controllers. You'll need iVars to track the loading state and use either a protocol or NSNotification to communicate when loading is done.
EDIT: ADDED EXAMPLE OF SYNCHRONOUS CALL ON BACKGROUND THREAD
If you want the loop to finish completely only when all requests are finishes and not block your UI thread here's a simple example:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// post an NSNotification that loading has started
for (x = 0; x < numberOfRequests; x++) {
// create the NSURLRequest for this loop iteration
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
// do something with the data, response, error, etc
}
// post an NSNotification that loading is finished
});
Whatever objects need to show loading status should observe and handle the notifications you post here. The loop will churn through and make all your requests synchronously on a background thread and your UI thread will be unblocked and responsive. This isn't the only way to do this, and in fact, I would do it using async connections myself, but this is a simple example of how to get what you want.
If you just want to know when it's complete and don't really care about any data, simply use the NSNotificationCenter to post a notification and have your view subscribe to it.
Delegate - Post Notification upon completion
-(void) connectionDidFinishLoading:(NSURLConnection*)connection {
[[NSNotificationCenter defaultCenter] postNotificationName:#"NSURLConnectionDidFinish" object:nil];
}
View - Add observer and run some code when observed
-(void) viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(yourCleanupMethod)
name:#"NSURLConnectionDidFinish"
object:nil];
}
-(void) yourCleanupMethod {
// finish up
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Now, if you need to pass a simple object back as data you can try loading up the object parameter in your notification like this:
[[NSNotificationCenter defaultCenter] postNotificationName:#"NSURLConnectionDidFinish" object:yourDataObject];
Then change your view and cleanup signature like this:
-(void) viewDidLoad {
// Notice the addition to yourCleanupMethod
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(yourCleanupMethod:)
name:#"NSURLConnectionDidFinish"
object:nil];
}
-(void) yourCleanupMethod:(NSNotification *)notif {
// finish up
id yourDataObject = [notif object];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Now I found myself needing something a little more than this so I ended up creating a singleton to handle all of my requests. Since all of your delegate methods in NSURLConnectionDelegate give you and instance of the NSURLConnection for the specific connection, you can simply store a mutable data object in a dictionary and look it up each time by the connection. From there I have a method signature that takes and object and selector in that I associate with the connection so after everything has wrapped up, I can pass that mutable data object to the requestor by performing the selector on that object.
I won't include all of that code here but maybe that will help get you thinking about what is possible. I found that I had a lot of code tied up in making web service calls so wrapping everything up in a singleton gave me a nice clean way of getting data. Hope this helps!
If you really want it to wait, why use an asynchronous call at all? Use a synchronous call instead:
NSURLResponse* response = nil;
NSData* data = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&response error:nil]
This approach will block the thread it's executed, so you should be sure you want to do it! Did I mention that it will block? :)
You say you want to wait for an asynchronous call to complete, so I'm assuming you're calling the code you posted up in a separate thread.
I would recommend having a look at the new sendAsynchronourRequest method. I've posted up an example of how you can wrap this up in a class which would inform its delegate when the connection has completed / timed out / failed. I'm only referring you to this post because it sounds like you're trying to achieve something very similar to what I was at the time, and this DownloadWrapper class worked flawlessly for me. It's new in iOS5, mind you.
This golden nugget helped me!
I was using synchronous NSURL just fine until I decided I needed SSL for my connection between my client and my server. I took the approach of key pinning which is comparing the cert on the device to the cert on the server (read more on link above) and in order for it to work I needed to add code to the NSURL methods, which from my research you can't do with NSURL synchronous.
Until I found this ridiculously simple solution which worked for me:
NSString *connectionRunLoopMode = #"connectionRunLoopMode";
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:urlRequest delegate:urlConnectionDelegate startImmediately:NO];
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
[connection unscheduleFromRunLoop:currentRunLoop forMode:NSDefaultRunLoopMode];
[connection scheduleInRunLoop:currentRunLoop forMode:connectionRunLoopMode];
[connection start];
while ([currentRunLoop runMode:connectionRunLoopMode beforeDate:[NSDate distantFuture]]);
NSURLConnection is already asynchronous. Simply implement the delegate methods. If you want to update the UI on the MainThread (e.g. a progress bar), you can do so in didReceiveData.
or look at this

Resources