I was doing a rather ordinary addPersistentStore to an NSPersistentStoreCoordinator, and it generated an &error code.
So I went to NSLog it, and got an access error when I did this:
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
which seems to be the common idiom.
When I reformatted the error statement as follows:
NSLog(#"Unresolved error %#", [error userInfo]);
...the problem went away.
Even NSZombie didn't trap the bad access error correctly!
Any ideas?
How are you catching the error?
The correct way, as described by bbum, is:
NSError *error;
BOOL success = [blah blah:blah error:&error];
if (!success) {
NSLog(#"Error: %# %#", error, [error userInfo]); //Or other error handling (e.g., [NSApp presentError:error]).
} else {
//Succeeded—ignore the error variable entirely
}
(That's for a method blah:error:that returns a BOOL; the example in the question bbum answered was for a method that returned an object. The error-handling pattern is the same for both cases.)
According to his Twitter updates shortly afterward, some APIs will output an error object under the hood even if what you asked for succeeded, which means that testing the error variable instead of the BOOL return can fool you. I think this is what happened to you.
The solution is to only look at the error object if the API reports failure.
Do not forget to init NSError with nil value
NSError* err = nil;
[anObject doSomethingGetError:&err];
if (err) {
NSLog(...);
}
If that does not help, it is API bug
Related
I have a custom NSIncrementalStore. If there's a problem adding it, the error is automatically logged to the console.
The problem is that the options can contain sensitive data that I obviously don't want logged to the console.
I presume the error is logged by Core Data, which I don't really need, since I already have an NSError argument that I can use appropriately.
For example:
#implementation FakeStore
- (BOOL)loadMetadata:(NSError **)error {
*error = [NSError errorWithDomain:#"faildomain" code:531 userInfo:nil];
return NO;
}
#end
Attempt at adding the store:
[NSPersistentStoreCoordinator registerStoreClass:[FakeStore class] forStoreType:#"FakeStore"];
...
NSDictionary *options = #{#"option": #"sensitivedata"};
NSError *error;
[persistentCoordinator addPersistentStoreWithType:#"FakeStore" configuration:nil URL:storeUrl
options:options error:&error];
The error that is automatically logged to the console:
CoreData: error: -addPersistentStoreWithType:FakeStore configuration:(null) URL:<URL> options:{
option = sensitivedata;
} ... returned error Error Domain=faildomain Code=531 "The operation couldn’t be completed. (faildomain error 531.)" with userInfo dictionary {
}
A workaround is that the sensitive data shouldn't be passed into the options for my store, but it shouldn't be necessary.
Is there anyway to suppress this error?
well you can't turn it off but you should be able to redirect it. Bummer.. needs private API .. is that an option for you?
https://www.google.de/search?client=safari&rls=en&q=_NSSetLogCStringFunction&ie=UTF-8&oe=UTF-8&gfe_rd=cr&ei=uUKdVe-QN4nj8wfl9AE
tried the first link and found it still worked!
if private API is no option: no (and Id file a bug :D)
Sorry, if found a lot of threads like the but they were not about this Error**-thing.
I tried to 'design' my methods like the error-examples I found. But calling the second, the error is not pointing to nil, the debugger says error: summary string parsing error.
This is my controller-method:
-(void) refresh {
NSError *error;
ServerApi *serverApi = [mainModel newServerApi];
NSArray *newItems = [serverApi getNewItems: &error];
...
This is the called method:
- (NSArray *) getNewItems: (NSError **) error {
// Breakpoint here, error is: 'error: summary string parsing error'
...
NSURLResponse *response;
NSData *responseData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: error];
I thought, I did the same as Apple with sendSynchronousRequest.... Their comment tells
error Out parameter (may be NULL) used if an error occurs
while processing the request. >>>>Will not be modified if the
load succeeds.<<<<
What did I do wrong and why does this work for Apples sendSynchronousRequest...?
The code is fine, as error does not need to be initialized "from outside".
Also, if you use ARC it will automatically initialize local object pointers to nil, so
NSError *error;
is no different than
NSError *error = nil;
under ARC.
While explicit initialization is still a good practice, that's not the source of any error here.
That being said,
summary string parsing error
is a lldb error. My hypothesis is that it gets confused by the double pointer, but I wouldn't worry too much.
By the way, you're doing a slight mistake in implementing this pattern.
Synchronous methods that may fail, should method to return a BOOL value indicating whether the computation was successful and then clients will check that value and subsequently inspect the error object in case it failed.
Checking the error object is in general a bad idea: even some Apple APIs can fail and yet return a nil error, so avoid doing that!
Remember to set your pointer to nil in refresh:
NSError *error = nil;
Also, remember, your checks should be:
Checking (*error) for nil (aka "there was no error passed") - operation may or may not have been successful;
Checking error for NULL (aka "there was no NSError* pointer passed, so don't assign it an object").
Xcode 4.3
I've read the SO questions on NSError**, so I wrote a simple test program that uses a slightly different syntax recommended by Xcode 4.3 (see __autoreleasing below), so I'm not 100% sure if this is correct, although the code does appear to function properly. Anyway, just a simple file reader, prints an error if the file can't be found.
Questions
Would like to know if the NSError initialization, argument passing using &, and error condition checking are correct.
Also, in the readFileAndSplit.. method, I noticed a big difference between if(!*error) and if(!error), in fact, if(!error) does not work when no error condition is raised.
File Reading Method w/Possible Error Condition
-(NSArray*) readFileAndSplitLinesIntoArray:(NSError *__autoreleasing *) error {
NSString* rawFileContents =
[NSString stringWithContentsOfFile:#"props.txt"
encoding:NSUTF8StringEncoding
error:error
NSArray* fileContentsAsArray = nil;
if(!*error)
fileContentsAsArray =
[rawFileContents componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
return fileContentsAsArray;
Caller
SimpleFileReader* reader = ...
NSError* fileError = nil;
NSArray* array = [reader readFileAndSplitLinesIntoArray: &fileError];
if(fileError){
NSLog(#"Error was : %#, with code: %li",
[fileError localizedDescription],(long)[fileError code]);
}
There are a couple of issues.
First, As per Apple's Error Handling Programming Guide, you should be checking a method's return value to determine whether a method failed or not, and not NSError. You only use NSError to get additional error information in the event that the method failed.
E.g.:
NSArray* fileContentsAsArray = nil;
NSString* rawFileContents = [NSString stringWithContentsOfFile:#"props.txt"
encoding:NSUTF8StringEncoding
error:error];
if (rawFileContents)
{
// Method succeeded
fileContentsAsArray = [rawFileContents ...];
}
return fileContentsAsArray; // may be nil
Second, NSError out parameters are typically optional and may be NULL. But if you pass a NULL error variable into your method it will crash on this line:
if (!*error) {
because you're dereferencing a NULL pointer. Instead, you must always check for NULL before referencing a pointer, like so:
if (error && *error)
{
// Do something with the error info
}
However, if you rewrite the method as indicated above then you won't be accessing the error variable at all.
Is there a way I can tell when the reason why my Restkit didFailWithError function is called is the lack of connection to the server?
-(void) objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError *)error{
//What should I do here to know the server could not be reached?
}
In the NSURLConnection didFailWithError method I use this code, it may apply to RESTKit, but I'm not sure. I thought I would post this so you could at least check (it may help) :)
if (error)
{
NSLog(#"%#", [NSString stringWithFormat:#"Connection failed! Error code: %d - %# %#", error.code, error.localizedDescription, [error.userInfo objectForKey:NSURLErrorFailingURLStringErrorKey]]);
if (error.code == -1009)
{
// This is the case that a connection failed based on bad connectivity
}
}
Let me know if you need anything else :)
You should be able to take a look at the NSError class reference.
Where you will find the following relevant methods:
-(NSUInteger)code;
//A string containing the localized description of the error.
-(NSString *)localizedDescription;
So you would be checking the return value of this methods from the NSError you are receiving.
I got into the habit of coding my error handling this way:
NSError* error = nil;
NSDictionary *attribs = [[NSFileManager defaultManager] removeItemAtPath:fullPath error:&error];
if (error != nil) {
DLogErr(#"Unable to remove file: error %#, %#", error, [error userInfo]);
return;
}
But looking at the documentation It seems like I got this wrong.:
- (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error
If an error occurs, upon return contains an NSError object that describes the problem. Pass NULL if you do not want error information.
Technically there is no difference between nil and NULL so does this mean I'm actually turning this off and will never get a error message (even if the delete in the above example did fail) ?
Is there a better way to code this ?
Thanks.
First off, the following line doesn't really make sense:
NSDictionary *attribs = [[NSFileManager defaultManager]
removeItemAtPath:fullPath error:&error];
-removeItemAtPath:error: returns a BOOL value, not a dictionary.
I think I see what you’re wondering about with the NULL value. Notice carefully though, how there are 2 *'s in the error parameter in the method signature:
- (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error
That means a pointer to a pointer. When you pass in &error, you are passing in the address of the pointer to the NSError. (Ugh, someone else can probably help me out here, as my head still starts to swim when dealing with pointers to pointers). In other words, even though you have set error to nil, you aren't passing in error to the method, you're passing in &error.
So, here’s what the re-written method should look like:
// If you want error detection:
NSError *error = nil;
if (![[NSFileManager defaultManager] removeItemAtPath:fullPath
error:&error]) {
NSLog(#"failed to remove item at path; error == %#", error);
// no need to log userInfo separately
return;
}
// If you don't:
if (![[NSFileManager defaultManager] removeItemAtPath:fullPath
error:NULL]) {
NSLog(#"failed to remove item at path");
return;
}
Passing NULL means the following:
BOOL itemRemoved = [[NSFileManager defaultManager] removeItemAtPath:fullPath
error:NULL];
i.e., the error parameter is NULL. Internally, -removeItemAtPath:error: sees if a valid pointer was passed. If it’s NULL, it simply won’t report the error as an NSError instance — but the return value will indicate whether the method completed successfully.
Also, your test is wrong. You shouldn’t be using the error output parameter to detect if an error occurred because it might be set even if the method completes successfully. Instead, you should use the return value of the method to detect errors. If the return value (in this particular case) is NO, then use the error output parameter to get information about the error:
NSError *error = nil;
BOOL itemRemoved = [[NSFileManager defaultManager] removeItemAtPath:fullPath error:&error];
if (itemRemoved == NO) {
DLogErr(#"Unable to remove file: error %#, %#", error, [error userInfo]);
return;
}
Quoting the Error Handling Programming Guide,
Important: Success or failure is indicated by the return value of the method. Although Cocoa methods that indirectly return error objects in the Cocoa error domain are guaranteed to return such objects if the method indicates failure by directly returning nil or NO, you should always check that the return value is nil or NO before attempting to do anything with the NSError object.
Edit: As NSGod pointed out, -removeItemAtPath:error: returns BOOL, not NSDictionary *. I’ve edited my answer to reflect that as well.
No I do it the same way and it works just fine for detecting errors. You are not passing NULL to it you are passing a pointer to NULL to it which is a very different thing. Although another option you might want to add is.
if (error != nil){...
}else{
[NSApp presentError:error]
}