I actually have two questions regarding exception/error handling in the iPhone app that I am making:
The app uses Internet, but when there's no connection, the app just dies (during launch). How can I handle this to print some infomsg to the user, instead of just getting thrown back to the springboard?
Can someone show me an example of how to handle for instance a "page not found" or "no contact with server" error, so I can give some sort of info to the user in the same way as above?
For crashes, the first step is to use error messages and the debugger to figure out what call is causing the problem. If the problem is caused by an uncaught exception, read this Apple article on exception handling. The specific answer really depends on your code and exactly what is causing the crash, so I won't speculate about a particular solution.
As far as detecting server error response codes (such as 404), that's more specific to WebKit. I assume you're using UIWebView on iPhone, and you've probably noticed that none of the primary methods return errors. This is because it uses a delegate model to report progress or errors asynchronously. (It makes sense because you don't want your UI code to be at the mercy of a slow-loading (or non-existent) webpage. To be notified of such errors, there are a few steps.
Adopt the UIWebViewDelegate protocol, usually in the same class that will start the webpage load for convenience.
Set that object as the delegate of the UIWebView instance. (It has a delegate property, so you can use something like either uiView.delegate = self or [uiView setDelegate:self] based on what you prefer.)
Implement the webView:didFailLoadWithError: method in that class. (You can be notified when the load finishing by implementing webViewDidFinishLoad: as well.) This is where you include the logic of what should happen when an error occurs.
I didn't see any detailed documentation on the content of any particular errors handed back via this delegate method, but it's a standard NSError object, and I recommend checking out the contents by calling its methods, such as -localizedDescription and -userInfo.
Here is some sample code with #import statements excluded for brevity.
MyClass.h
#interface MyClass : NSObject <UIWebViewDelegate> {
IBOutlet UIWebView* myWebView;
}
-(void)webView:(UIWebView*)webView didFailLoadWithError:(NSError *)error;
#end
MyClass.m
#implementation MyClass
- (id) init {
if ((self = [super init]) == nil)
return nil;
// initialize myWebView
myWebView.delegate = self;
return self;
}
- (void) webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error {
...
}
#end
Testing for a connection is pretty easy...
NSString * test = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://www.stackoverflow.com"]];
if (test == nil) {
//display an alertview saying you don't have an internet connection
}
Using a URL to test for a connection is not a good idea, it is not robust enough to determine if the internet connection is down, the website is down or some other network issue etc and above all it adds an overhead to the call as far as network traffic.
Look at the Reachability demo on the Apple site, it uses the correct way to determine connectivity, including whether you are on wifi etc.
Related
My application allows a user to create an item for listing on an eCommerce site. The user goes through a number of screens adding images and information until they need to create the item on the store.
The final upload screen has an UIViewController using AFNetworking has two services to call that:
1) Calls an image upload webservice and returns some ID's. On success it calls (2).
2) Calls another service using these returned ID's as part of the request.
This process is started when the user hits the Submit button.
What I would like to happen is the following:
The users clicks Submit and the process starts in the background
The current storyboard scene returns to the start screen to allow the user to create another item whilst the previous is still running.
As the code for the service calls and handling the responses from them are in the UIViewController once the scene changes the UIViewController will no longer be running on the stack so what will happen to the service response etc?
If I create a separate class to do the work I'll loose the object reference when the scene changes. If the method is still processing would it be garbage collected?
Should I be sticking this on a background thread using Grand Central Dispatch?
For more detail, here's an example.
I usually have a class named NetWrapper which manages whole network related thing.
.h
#interface NetWrapper : NSObject
+ (instancetype)shared;
#pragma mark - APIs
- (void)requestVersion;
#end
.m
static NetWrapper *_netWrapper;
#implementation NetWrapper
+ (instancetype)shared
{
if(_netWrapper == nil)
{
_netWrapper = [[NetWrapper alloc] init];
}
return _netWrapper;
}
#pragma mark - APIs
- (void)requestVersion
{
// do something
}
If you have a singleton class like this, you can alway have the same instance with
[NetWrapper shared]
and invoke instance method like below.
[[NetWrapper shared] requestVersion];
Important update: I found out that most part of my question was based on a false premise (see my answer below). Notifications actually got to the receiver, they just got there too fast. (Although, it still doesn't explain why the behavior with breakpoint and without it was different.)
I'm developing the app that calculates the hashes of files given to it. The calculation takes place in SHHashComputer. It's an abstract class (well, intended to be abstract, as there are no abstract classes in Objective C) that takes the file path and creates an NSInvocationOperation. It, in turn, calls the method (void)computeAndSendHash, which uses the file path saved in the object to compute hash and sends it as notification. The actual computing takes place in (NSString*)computeHash method that child classes need to override.
Here's SHHashComputer.m:
- (NSString*)computeHash {
return [NSString stringWithFormat:#"unimplemented hash for file %#", self.path];
}
- (void)computeAndSendHash {
NSString *result = [self computeHash];
NSString *notificationName = [NSString stringWithFormat:#"%#%#",
gotResultNotification,
self.hashType];
[[NSNotificationCenter defaultCenter] postNotificationName:notificationName
object:result];
self.operation = nil;
}
And here's SHMD5Computer.m (the child class of SHHashComputer):
- (NSString*)computeHash {
return #"MD5 test"; // it actually doesn't matter what it returns
}
I won't bother you with the receivers of notification. Let's just say that as long as I comment out the computeHash method in SHMD5Computer.m everything works just fine: the notification with text "unimplemented ..." is received & displayed in GUI. But if I don't — then it gets really interesting.
If I don't set up any breakpoints, the notification just never comes. However, if I set up a breakpoint at the declaration of computeHash in SHMD5Computer.h and then step over until the line 'self.operation = nil', and continue execution at that point, the notification gets to destination. If I don't stop there, the debugger suddenly switches to the state as if it isn't debugging anything, and the app freezes.
I don't think that 'WTF' is a good form for a question here, so let me put it this way: am I missing something? Are there errors in my code? What can cause this type of behavior in xcode? How can I fix this?
(If you'll want to get all my code to reproduce it, I'll gladly give it to you.)
More experiments:
If I continute execution exactly after stopping at breakpoint, the application encounters EXC_BAD_ACCESS error in the code that receives the notification, at the last line:
id newResult = [newResultNotification object];
if (newResult == nil)
[NSException raise:#"No object"
format:#"Expected object with notification!"];
else if (![newResult isKindOfClass:[NSString class]])
[NSException raise:#"Not NSString"
format:#"Expected NSString object!"];
else
self.result = (NSString*) newResult;
[self.textField setStringValue:self.result];
When I tried to reproduce the previous experiment, something even stranger happenned. In my debug setup, I have two hash computer objects: one SHMD5HashComputer (which we're talking about), and one SHHashComputer (which, of course, produces the "unimpemented" hash). In all previous experiments, as long as app didn't crash, the notification form SHHashComputer always successfully arrived. But in this case, both notifications didn't arrive, and the app didn't crash. (All the steps are exactly the same as in previous one).
As Josh Caswell pointer out in the comments, I wasn't using the notifications correctly. I should've sent the object itself as notification object, as described in documentation. I fixed that, and I'm getting exactly the same results. (Which means that I fixed it correctly, because sometimes the notifications work correctly, and also that it wasn't the problem).
More updates:
The notification that I'm sending should arrive at SHHashResultViewController. That's how I create it and register for notification:
- (id)initWithHashType:(NSString *)hashType {
self = [self initWithNibName:#"SHHashResultView" bundle:[NSBundle mainBundle]];
if (self) {
[self setHashType:hashType];
}
return self;
}
- (void)setHashType:(NSString *)hashType {
[self.label setStringValue:[NSString stringWithFormat:#"%#:", hashType]];
_hashType = hashType;
NSString *notificationName = [NSString stringWithFormat:#"%#%#",
gotResultNotification,
_hashType];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(gotResult:)
name:notificationName
object:nil];
}
Actually, the question was based on a false premise. I thought that notification never came through because I never saw the information displayed in the GUI; however, my error was in the code of controllers (not published there) which made possible the situation in which the GUI first got results of hash calculation and only after that got information about a new input — which resulted in deleting all the text and activating progress animation.
I have an application with 2 components: a desktop application that users interact with, and a background process that can be enabled from the desktop application. Once the background process is enabled, it will run as a user launch agent independently of the desktop app.
However, what I'm wondering is what to do when the user disables the background process. At this point I want to stop the background process but I'm not sure what the best approach is. The 3 options that I see are:
Use the 'kill' command.
Direct, but not reliable and just seems somewhat "wrong".
Use an NSMachPort to send an exit request from the desktop app to the background process.
This is the best approach I've thought of but I've run into an implementation problem (I'll be posting this in a separate query) and I'd like to be sure that the approach is right before going much further.
Something else???
Thank you in advance for any help/insight that you can offer.
The daemon could handle quit apple events or listen on a CFMessagePort.
If you use signals you should handle the signal, probably SIG_QUIT, that is sent instead of just letting the system kill your process.
If you have cleanup that may take a while, use something other than signals. If you are basically just calling exit, then signals are fine.
If you already have a CFRunLoop going then use CFMessagePort. If you are already handling apple events than handle quit.
CFMessagePort is a wrapper around CFMachPort that provides a name and some other conveniences. You can also use the NS wrappers for either.
I found an easier way to do this using an NSConnection object. I created a very simple ExitListener object with this header:
#interface ExitListener : NSObject {
BOOL _exitRequested;
NSConnection *_connection;
}
#property (nonatomic, assign) BOOL exitRequested;
- (void)requestExit;
#end
and this implementation:
#implementation ExitListener
#synthesize exitRequested = _exitRequested;
// On init we set ourselves up to listen for an exit
- (id)init {
if ((self = [super init]) != nil) {
_connection = [[NSConnection alloc] init];
[_connection setRootObject:self];
[_connection registerName:#"com.blahblah.exitport"];
}
return self;
}
- (void)dealloc {
[_connection release];
[super dealloc];
}
- (void)requestExit {
[self setExitRequested:YES];
}
#end
To setup the listener, the background process simply allocates and inits an instance of the ExitListener. The desktop application then asks the background process to exit by making this call:
- (void)stopBackgroundProcess {
// Get a connection to the background process and ask it to exit
NSConnection *connection = [NSConnection connectionWithRegisteredName:#"com.blahblah.exitport" host:nil];
NSProxy *proxy = [connection rootProxy];
if ([proxy respondsToSelector:#selector(requestExit)]) {
[proxy performSelector:#selector(requestExit)];
}
}
Using NSMachPorts directly seemed to lead to far more problems in registering and obtaining references. I found that NSConnection is the simplest way to create a basic communication channel for the sort of situation that I needed to solve.
I'm new to Cocoa programming, and decided for my first project to create a small application to monitor and remember certain battery stats for my laptop. (I have it plugged in most of the time, and apple recommend you discharge it now and again, so why not try to make a small program to help you remember to do this? :))
Anyway, I have a standard Objective-C project, with a DataModel file.
It contains an Entity, BatteryEvent, with properties, charge and event.
I then have PowerListener.m (and .h).
PowerListener.m is implemented as follows:
#implementation PowerListener
void myPowerChanged(void * context) {
printf("Is charging: %d\n", [PowerFunctions isCharging]);
printf("Is on ac: %d\n", [PowerFunctions isOnAC]);
printf("Charge left: %d\n", [PowerFunctions currentCapacity]);
printf("Powerchanged\n");
NSManagedObject *newBatteryEvent = [NSEntityDescription
insertNewObjectForEntityForName:#"BatteryEvent"
inManagedObjectContext:context];
}
- (PowerListener*) init {
self = [super init];
if(self) {
CFRunLoopSourceRef loop = IOPSNotificationCreateRunLoopSource(myPowerChanged, [[NSApp delegate] managedObjectContext]);
CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, kCFRunLoopDefaultMode);
CFRelease(loop);
} else {
printf("Error\n");
}
return self;
}
#end
My problem is that once I run this (inited through main.m's main-method) and the power actually DOES change, I get thrown an error where I try to create the new BatteryEvent object:
2009-08-19 17:59:46.078 BatteryApp[5851:813] +entityForName: could not locate an NSManagedObjectModel for entity name 'BatteryEvent'
So it looks to me like I have the wrong ManagedContext? How do I get the right one?
Am I even on the right track here?
I've tried passing another kind of NSManagedObjectContext to the callback function as well.
I followed this guide: Core Data Guide, but, again same error...
I'm at my wits end!
Any help appreciated!
It looks like your app isn't loading the managed object model as a part of the launch and/or Core Data stack initialization.
Where is your model loaded?
Also, make sure you spelled the entity name correctly in the model.
This is with reference to the StackOverflow question Managing multiple asynchronous NSURLConnection connections
I have multiple asynchronous HTTP requests being made at the same time. All these use the same NSURLConnection delegate functions. (The receivedData object is different for each connection as specified in the other question above. In the delegate, I parse the receivedDate object, and do additional operations on those parsed strings)
Everything works fine for me so far, but I'm not sure if I need to do anything to ensure correct “multithreaded” behavior.
Is it possible that more than two connections will use the delegate at the same time? (I would think yes)
If yes, how is it resolved? (Does Cocoa do this automatically?)
Do I need to have additional checks in place to ensure that each request is handled “correctly”?
I enhanced the Three20 library to implement asynchronous connections across multiple threads in order to fetch data even if the user was playing with the UI. After many hours of chasing down random memory leaks that were detected within the CFNetwork framework I finally root caused the issue. I was occasionally losing track of responses and data.
Any data structures which are accessed by multiple threads must be protected by an appropriate lock. If you are not using locks to access shared data structures in a mutually exclusive manner then you are not thread safe. See the "Using Locks" section of Apple's Threading Programming Guide.
The best solution is to subclass NSURLConnection and add instance variables to store its associated response and response data. In each connection delegate method you then cast the NSURLConnection to your subclass and access those instance variables. This is guaranteed to be mutually exclusive because every connection will be bundled with its own response and data. I highly recommend trying this since it is the cleanest solution. Here's the code from my implementation:
#interface TTURLConnection : NSURLConnection {
NSHTTPURLResponse* _response;
NSMutableData* _responseData;
}
#property(nonatomic,retain) NSHTTPURLResponse* response;
#property(nonatomic,retain) NSMutableData* responseData;
#end
#implementation TTURLConnection
#synthesize response = _response, responseData = _responseData;
- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate {
NSAssert(self != nil, #"self is nil!");
// Initialize the ivars before initializing with the request
// because the connection is asynchronous and may start
// calling the delegates before we even return from this
// function.
self.response = nil;
self.responseData = nil;
self = [super initWithRequest:request delegate:delegate];
return self;
}
- (void)dealloc {
[self.response release];
[self.responseData release];
[super dealloc];
}
#end
/////////////////////////////////////////////////////////////////
////// NSURLConnectionDelegate
- (void)connection:(NSURLConnection*)connection
didReceiveResponse:(NSHTTPURLResponse*)response {
TTURLConnection* ttConnection = (TTURLConnection*)connection;
ttConnection.response = response;
ttConnection.responseData = [NSMutableData
dataWithCapacity:contentLength];
}
- (void)connection:(NSURLConnection*)connection
didReceiveData:(NSData*)data {
TTURLConnection* ttConnection = (TTURLConnection*)connection;
[ttConnection.responseData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
TTURLConnection* ttConnection = (TTURLConnection*)connection;
if (ttConnection.response.statusCode == 200) {
// Connection success
}
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error {
TTURLConnection* ttConnection = (TTURLConnection*)connection;
// Handle the error
}
Assuming you're launching all of the (asynchronous) connections on a single thread, then the delegate messages will all get posted in that thread's run loop. Therefore the delegate only needs to be able to deal with one message being handled at once; the run loop will hand one message off at a time. This means that while the order of the delegate messages is unknown and the next message could come from any connection object, there will be no concurrent execution of your delegate methods.
However, were you actually trying to use the same delegate object across multiple threads, rather than just using the asynchronous nature of the API, then you would need to deal with concurrent delegate methods.
Yes it's possible to have multiple connections. the notification object contains a pointer to the NSURLConnection that triggered the notification.
Internally I guess NSURLConnection listens to a socket and does something like this when it has data ready.
[your_delegate
performSelectorOnMainThread:#selector(connectionCallback:)
withObject:self
waitUntilDone:NO];
so you don't have to worry about it being multithreaded, NSURLConnection will take care of this. For simplicity I have written self, in the real world a NSNotification object is given.
You shouldn't have to do any checks related to multithreading.