Creating NSError * results in application crash - objective-c

I have the following situation:
My domain class gets some input validates it, and if validation passes it proceeds with saving data.
When control flow reaches the if statements, the application crashes
- (BOOL)createGmailAccountWithName:(NSString *)name
email:(NSString *)email
andPassword:(NSString *)password
error: (NSError **) error {
if (!name || name.length == 0) {
*error = [self createError:#"name"];
return NO;
}
if (!email || email.length == 0) {
*error = [self createError:#"email"];
return NO;
}
if (!password || password.length == 0) {
*error = [self createError:#"password"];
return NO;
}
//..
}
-(NSError *) createError: (NSString *) field {
NSString *errorMessage = [NSString stringWithFormat:#"Property %# is required", field];
NSDictionary *userInfo = #{
NSLocalizedFailureReasonErrorKey: NSLocalizedString(errorMessage, nil)
};
NSError *error = [NSError errorWithDomain:ACCOUNT_STORE_ERROR_DOMAIN
code:-1
userInfo:userInfo];
return error;
}
When I comment out all lines where the validation happens, the application does not crash.
I have no idea why this is happening. Can anyone point me into the right direction?

If you have this method:
- (BOOL)createGmailAccountWithName:(NSString *)name
email:(NSString *)email
andPassword:(NSString *)password
error: (NSError **) error
Folks are probably going to call it either like this:
NSError *error;
[accountCreator createGmailAccountWithName:#"Ben"
email:#"foo#example.com"
andPassword:#"pwd"
error:&error];
if (error)
{
NSLog(#"Hey I got an error: %#", error);
}
Or like this:
[accountCreator createGmailAccountWithName:#"Ben"
email:#"foo#example.com"
andPassword:#"pwd"
error:NULL];
// I couldn't care less about an error
In the second case, your code will will try to dereference **error, *error is not a valid pointer and would cause a crash.

Related

Using RCTAsyncLocalStorage + getAllKeys

I'm trying to get the AsyncStorage on iOS native code. So this is my code
- (void)jsonFromLocalRNStrogeForKey:(NSString *)key completion:(void (^)(NSDictionary * _Nullable, NSError * _Nullable))completion {
RCTResponseSenderBlock rnCompletion = ^(NSArray *response) {
NSString *jsonAsString;
if (response.count > 1) {
NSArray *response1 = response[1];
if (response1.count > 0) {
NSArray *response2 = response1[0];
if (response2.count > 1) {
jsonAsString = response2[1];
}
}
}
#try {
NSData *jsonAsData = [jsonAsString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *json = [
NSJSONSerialization
JSONObjectWithData:jsonAsData
options:NSJSONReadingMutableContainers
error:&error
];
completion(json, error);
}
#catch (NSException *exception) {
NSLog(#"error: %#", exception.reason);
NSMutableDictionary * info = [NSMutableDictionary dictionary];
[info setValue:exception.name forKey:#"ExceptionName"];
[info setValue:exception.reason forKey:#"ExceptionReason"];
[info setValue:exception.callStackReturnAddresses forKey:#"ExceptionCallStackReturnAddresses"];
[info setValue:exception.callStackSymbols forKey:#"ExceptionCallStackSymbols"];
[info setValue:exception.userInfo forKey:#"ExceptionUserInfo"];
NSError *error = [[NSError alloc] initWithDomain:#"" code:1 userInfo:info];
completion(nil, error);
}
};
// RCTAsyncLocalStorage *storage = [RCTAsyncLocalStorage new];
RCTAsyncLocalStorage *storage = [[RCTAsyncLocalStorage alloc] init];
dispatch_async(storage.methodQueue, ^{
#try {
// [storage performSelector:#selector(multiGet:callback:) withObject:#[key] withObject:rnCompletion];
[storage performSelector:#selector(getAllKeys:callback:) withObject:rnCompletion];
}
#catch (NSException *exception) {
NSLog(#"error: %#", exception.reason);
}
});
}
When I try to get one of my keys (multiGet)
[self jsonFromLocalRNStrogeForKey:#"session" completion:^(NSDictionary* data,NSError* error) {
if (data) {
NSString * name = [data valueForKeyPath: #"token"];
if (![name isKindOfClass:[NSNull class]]) {
[self reportIncomingCallFrom:name withUUID:callInvite.uuid];
}
} else {
NSLog(#"error: JSON Parsing Error: %#",error.localizedFailureReason);
}
}];
I'm always getting null
And when I try to get all the keys (...#selector(getAllKeys:...) to see what do I have in my AsyncStorage I got the exception
#"NSInvalidArgumentException" - reason: #"-[RCTAsyncLocalStorage getAllKeys:callback:]: unrecognized selector sent to instance 0x1085512c0"
The RN have RCT_EXPORT_METHOD(getAllKeys:(RCTResponseSenderBlock)callback) in RCTAsyncLocalStorage.m; but at RCTAsyncLocalStorage.h (void)getAllKeys:(RCTResponseSenderBlock)callback it doesn't exists and even adding it doesn't work (https://github.com/facebook/react-native/blob/master/React/Modules/RCTAsyncLocalStorage.h).
"react-native": "^0.48.4",
How can I return NSJsonSerialization
Firstly,
The RN have
RCT_EXPORT_METHOD(getAllKeys:(RCTResponseSenderBlock)callback) in
RCTAsyncLocalStorage.m; but at RCTAsyncLocalStorage.h
(void)getAllKeys:(RCTResponseSenderBlock)callback it doesn't exists
In Objective-C, you can invoke a method even though it is not declared in the header file using performSelector:withObject:.
Invoking this method directly (without first checking if the target respondsToSelector:) is bad practice, as the internal method declaration may change.
Secondly, this line is incorrect:
[storage performSelector:#selector(getAllKeys:callback:) withObject:rnCompletion];
Here, you're saying getAllKeys:: takes two arguments, however the implementation declares only one.
Hence, the correct way to extract all keys is the following:
dispatch_async(storage.methodQueue, ^{
if([storage respondsToSelector:#selector(getAllKeys:)]){
[storage performSelector:#selector(getAllKeys:) withObject:[^(NSArray* response){
NSLog(#"Contents: %#",response);
} copy]];
}else{
NSLog(#"storage does not respond to selector `getAllKeys:`");
}
});

when try to overriding the nserror cant get anything

i want do base on the nserror info,create a new instance of nserror,but seem is not work at all
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError * *)outError
{
NSError *breakError = nil;
todoItems = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListMutableContainers format:NULL error:&breakError];
if (todoItems == nil){
NSString *desc = NSLocalizedString(#"Can't do it!", #"");
NSDictionary *userInfo = #{ NSLocalizedDescriptionKey : desc };
*outError = [NSError errorWithDomain:#"com.pink.test" code:3084 userInfo:userInfo];
return NO;
}
// Insert code here to read your document from the given data of the specified type. If outError != NULL, ensure that you create and set an appropriate error when returning NO.
// You can also choose to override -readFromFileWrapper:ofType:error: or -readFromURL:ofType:error: instead.
// If you override either of these, you should also override -isEntireFileLoaded to return NO if the contents are lazily loaded.
NSException *exception = [NSException exceptionWithName:#"UnimplementedMethod" reason:[NSString stringWithFormat:#"%# is unimplemented", NSStringFromSelector(_cmd)] userInfo:nil];
#throw exception;
return YES;
}
i always only can get the default dialog message,even i not pass the nserror back
i sorry it if not clear,here is a demo for what i repeat what happen of mine.
加油了. 你想陈述的问题,跟别人看到的可能不一样.
If you have a error output in that function , there is no need to throw exception.
Here is a example making nserror from kxsmb:
static NSError * mkKxSMBError(KxSMBError error, NSString *format, ...)
{
NSDictionary *userInfo = nil;
NSString *reason = nil;
if (format) {
va_list args;
va_start(args, format);
reason = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
}
if (reason) {
userInfo = #{
NSLocalizedDescriptionKey : KxSMBErrorMessage(error),
NSLocalizedFailureReasonErrorKey : reason
};
} else {
userInfo = #{ NSLocalizedDescriptionKey : KxSMBErrorMessage(error) };
}
return [NSError errorWithDomain:KxSMBErrorDomain
code:error
userInfo:userInfo];
}
it's done,turn out it's only can be use system define error,not can be custom,it means only can be errorWithDomain:NSCocoaErrorDomain and a valid code
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError * *)outError
{
if (*outError == nil){
NSString *desc = NSLocalizedString(#"Can't do it!", #"");
NSDictionary *userInfo = #{ NSLocalizedDescriptionKey : desc };
*outError = [NSError errorWithDomain:NSCocoaErrorDomain code:3840 userInfo:userInfo];
}
return NO;
thank for the all help.

Assigning data from an anonymous handler ios

I'm trying to get a reference of an NSArray that gets passed when I call sendAsyncrhonousRequest. Once I have that NSArray, I'd like to assign it to a class attribute but it seems I can't do that.
#implementation BarTableViewController {
NSArray *_jsonArray;
}
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode == 200 && data.length > 0 && error == nil)
{
NSError *e = nil;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData: data options: NSJSONReadingMutableContainers error: &e];
if (!jsonArray) {
NSLog(#"Error parsing JSON: %#", e);
} else {
_jsonArray = jsonArray; // this doesn't work? _jsonArray is at the class level
}
}
else if (error)
{
NSLog(#"HTTP Status: %ld", (long)statusCode);
}
else if (statusCode != 200)
{
NSLog(#"HTTP Status: %ld", (long)statusCode);
}
}];
If I traverse jsonArray it will correctly display the data. If I assign it to _jsonArray to use it later, it no longer returns any data. The count of the array is zero.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _jsonArray.count; // always returns zero
}
How can I assign jsonArray to a class attribute so that I can use that data later?
My theory was correct. The UI was loading before the request finished. What I did to fix this issue was to call
[self.tableView reloadData];
Inside the async request.
Forget simple assignment:
#implementation BarTableViewController {
// NSArray *_jsonArray; forget it
}
instead own the object.
#interface BarTableViewController()
#property(nonatomic, strong)NSArray *jsonArray;
#end
/* --------- */
#implementation BarTableViewController
#syntethise jsonArray = _jsonArray;
#end
then to make it own it
self.jsonArray = jsonArray; // will call synthesized setter
I'm assuming you are using ARC, because you get a nil value instead of a dangling pointer. Now you will get a valid jsonArray.

NSError: passing error in method

I'm not sure if this is correct or not:
- (void)parseSomething:(id)targetObject error:(NSError **)error {
NSError *parserError = nil;
[myParser parse:targetObject error:&parserError];
if (parserError != nil) {
*error = parserError;
}
}
the line:
*error = parserError;
I set error in parameter to be a local error, is it done correctly?
or should I do:
error = &parserError;
instead?
You need to make sure error isn't nil before you try to dereference it. Also, there is no need for the local NSError. It'd write that code this way:
- (void)parseSomething:(id)targetObject error:(NSError **)error {
[myParser parse:targetObject error:error];
}
But if you really wanted the locale variable (or for demonstration purposes). then this:
- (void)parseSomething:(id)targetObject error:(NSError **)error {
NSError *parserError = nil;
[myParser parse:targetObject error:&parserError];
if (error && parserError) {
*error = parserError;
}
}
Also, most methods that have an NSError out parameter like this usually have a BOOL return value or some other return value to indicate success or not. You shouldn't rely in the error parameter to indicate whether there was an error or not.
- (BOOL)parseSomething:(id)targetObject error:(NSError **)error {
NSError *parserError = nil;
BOOL ok = [myParser parse:targetObject error:&parserError];
if (error && parserError) {
*error = parserError;
}
return ok;
}
In this case you would probably want to pass the error further without intermediate variables. However, you need to be sure the myParser object handles the error parameter properly.
- (void)parseSomething:(id)targetObject error:(NSError **)error
{
[myParser parse:targetObject error:error];
}
In case you want to handle it here, you need to make sure the pointer to pointer error points to something, otherwise you will have crash when dereferencing it.
For example: the method was called like this
[object parseSomething:targetObject error:NULL];
The following line leads to crash:
*error = parserError;
Correct code would look like this:
- (void)parseSomething:(id)targetObject error:(NSError**)error
{
NSError* parserError = nil;
[myParser parse:targetObject error:&parserError];
if (error != nil)
{
*error = parserError;
}
}
Not a problem when parserError is nil, but when error is nil.

In Objective-C, I'm trying to encapsulate multiple error-able calls and "return" the most useful error

I put "return" in quotes because I don't want to literally return it. I want to do it similar to how you pass a pointer-to-a-pointer for [NSString stringWithContentsOfFile:usedEncoding:error:].
I would like to make parseFiles:error return nil and have the error reference that was passed in contain the first or second error, depending on which one failed. It seems like a Cocoa way to do it?
EDIT: Sorry, I should've been more clear about where I was having the problem. If the first path is bogus, it functions as I want. (I get the error instance outside and it prints.) If the first path is legit, as it filler string below implies, I get EXC_BAD_ACCESS.
But now I fixed it. I need to refer to it as *error inside the parseFiles:error: method and use == nil when checking if it failed. I thought I could just to if (error)...
EDIT 2 Ok, it doesn't work. I'm getting EXC_BAD_ACCESS. I'm not sure what I'm doing wrong with the conditions that check for the errors.
#implementation PassingError
- (id)init {
self = [super init];
NSError *error;
[self parseFiles:#"/untitled.py" error:&error];
if (error != nil) {
NSLog(#"I failed because: %#", error);
}
return self;
}
// Wraps with reading errors.
- (NSString *)parseFiles:(NSString *)path error:(NSError **)error {
NSStringEncoding enc1;
NSString *contents1 = [NSString stringWithContentsOfFile:path
usedEncoding:&enc1 error:*&error];
// there was a read error
// I need an asterisk here...
if (*error != nil) {
// ...and also one here
NSLog(#"FIRST ERROR: %#", *error);
return nil;
}
// here is where you'd do something that might cause another error,
// I'll just try and read a second file for simplicity
NSStringEncoding enc2;
NSString *contents2 = [NSString stringWithContentsOfFile:#"/untitled.py"
usedEncoding:&enc2 error:*&error];
// there was a SECOND error
if (*error != nil) {
NSLog(#"SECOND ERROR: %#", *error);
return nil;
}
// return both or whatever
return [NSArray arrayWithObjects:contents1, contents2, nil];
}
#end
Passing pointers around in Objective-C can get confusing. I remember having trouble grasping what needed to be done. When you have a method like this:
- (BOOL) saveValuesAndReturnError:(NSError **) error
{
BOOL success = [self doSomethingImportant];
if (!success && error)
{
// Unsuccessful and error is a valid ptr-to-ptr-to-NSError.
// Basically, someone has given us the address of a (NSError *).
// We can modify what that pointer points to here.
*error = [NSError errorWithDomain:#"myDomain" code:100 userInfo:nil];
}
return success;
}
This is intended to be invoked like this:
// If the caller doesn't care that it failed:
[someObject saveValuesAndReturnError:NULL];
// Or, if the caller wants to get error information on failure
NSError *anError = nil;
BOOL success;
// pass address of our (NSError *)
success = [someObject saveValuesAndReturnError:&anError];
if (!success)
{
// anError now points to an NSError object, despite being initialised to nil,
// because we passed the address of our NSError ptr, the method was able to
// change where `anError` points to.
NSLog (#"An error occurred while saving values: %#", anError);
}
Perhaps a very relevant read in this case is a CIMGF blog post covering exactly this topic.
However...
I remember reading a while ago that methods that return errors via method arguments such as stringWithContentsOfFile:usedEncoding:error: make no guarantee not to modify the error argument for success. In other words, you cannot rely on the value of the error parameter if the method succeeded. In your particular case, it may be better to do:
- (NSString *)parseFiles:(NSString *)path error:(NSError **)error {
NSStringEncoding enc1, enc2;
NSError *innerError;
NSString *contents1 = [NSString stringWithContentsOfFile:path
usedEncoding:&enc1
error:&innerError];
if (contents1 == nil)
{
if (error) *error = innerError;
return nil;
}
NSString *contents2 = [NSString stringWithContentsOfFile:#"/untitled.py"
usedEncoding:&enc2
error:&innerError];
if (contents2 == nil)
{
if (error) *error = innerError;
return nil;
}
// do whatever with contents1 and contents2
}