nil object in NSDictionary - ios

I have a NSDictionary from which I read some values in two different methods in my code, however only in one of the methods the value of the key is retrieved correctly whereas in the other method I always get nil.
This is the method that works:
-(void)drawDriver:(NSString*)driverId withPosition:(CLLocationCoordinate2D)position withRotation:(float)bearing isAvailable:(BOOL)isDriverAvailable{
GMSMarker *driverMarker = [driversList objectForKey:driverId];
if (driverMarker != nil) {
// do some work
}
}
and it's called from this other method:
- (void) socketIO:(SocketIO *)socket didReceiveMessage:(SocketIOPacket *)packet
{
NSMutableDictionary *datos = (NSMutableDictionary*)packet.data;
NSDictionary *driverId = datos[#"from"];
NSString *id = [driverId objectForKey:#"id"];
//gets other values
NSLog(#"Socket message: driverId: %#, position %f,%f, isAvailable:%d", id, latitude, longitude, isDriverAvailable);
//Call the first method
[self drawDriver:id withPosition:position withRotation:bearing isAvailable:isDriverAvailable];
}
And this is the method that doesn't work (returns nil for driverMarker):
-(void)updateMapForTripWithDriver:(NSString *)driverId {
GMSMarker *driverMarker = [driversList objectForKey:driverId];
if (driverMarker != nil) {
//do some work
}
}
and this is called from here:
- (void)updateTripWithData:(NSDictionary *)data {
//This NSDictionary comes from a notification
NSString *newStatus = [[data objectForKey:#"aps"] objectForKey:#"alert"];
if ([newStatus isEqualToString:NO_TRIP]) {
} else if ([newStatus isEqualToString:WAITING_CONFIRMATION]) {
} else if ([newStatus isEqualToString:DRIVER_ON_THE_WAY]) {
//Get the driverId and call the other method
NSString *driverId = [data objectForKey:#"driver_id"];
[self updateMapForTripWithDriver:driverId];
}
}
As you can see both methods have the exact same code to retrieve the object but still I get different results.
The only difference I could find is when I put some breakpoints on the methods and this is what I found:
On the first method, although I pass the driverId as a NSString, somehow it's read as NSFCNumber and that seems to work because I get the value with no problems.
But on the second method the key is read as an actual NSString which for some reason makes the method to return nil for an object that does exists on the NSDictionary.
How can I get the second method to work and return the right value?

Related

Parsing id to NSString

When parsing API responses, sometimes I can not rely on strings being embedded in quotation marks. ID's are a good example of this, where some API's will send the numerical ID as a string while some will send it as a number.
What is a good practice when parsing such a value? If I simply parse it to an NSString like so:
NSString *myID = (NSString *)message["myID"];
I can end up with an NSString object that somehow contains (long)123.
And using stringValue would cause issues when the value is actually already sent as a string (since NSString does not have a stringValue function).
A way that works, but is somewhat ugly, is this:
id myID = (NSString *)message["myID"];
if ([myID respondsToSelector:#selector(stringValue)])
{
myID = [myID stringValue];
}
You could do something like:
id myID = message["myID"];
if ([myID isKindOfClass:[NSString class]]) { ... }
else { ... }
As long as this logic is encapsulated inside data parser and is opaque for your api users (i.e. they will always get a string) any approach is fine, e.g.:
- (NSString*)parseID:(NSDictionary*)message {
id rawID = message["myID"];
if ([rawID isKindOfClass:[NSString class]]){
return rawID;
} else if ([rawID isKindOfClass:[NSNumber class]]) {
return [(NSNumber*)rawID stringValue];
} else {
// We might still want to handle this case.
NSAssert(false, #"Unexpected id type");
return nil;
}
}
Alternative is to define stringValue in extension, so any possible objet will respond to selector:
#implementation NSString(JSONStringParsing)
- (NSString *)stringValue {
return [self copy];
}
#end
Why not just use description?
NSArray *objects = #[
#NSIntegerMin,
#NSIntegerMax,
#"123456789"
];
for (id object in objects) {
NSString *stringObject = [object description];
NSLog(#"%# -> %# | %#", [object className], [stringObject className], stringObject);
}

Undefined selector with Objective-C runtime - blocks

I'm trying to create a function where multiple times I do the same thing. I've deceided to go with a block this time. However after writing following code:
- (BOOL)readyForProcessing {
void (^notDeclaredError)(id) = ^(id missingObject) {
NSString *missingObjectName = NSStringFromSelector(#selector(missingObject));
NSString *errorDescription = [NSString stringWithFormat:#"You need to provide %# property", missingObjectName];
[self configureErrorWithLocalizedDescription:errorDescription];
};
if (!self.delegate) notDeclaredError(self.delegate);
return (self.error == nil);
}
I get a warning in the line, where I declare missingObjectName.
Undeclared selector 'missingObject'
I know it will probably try to make a NSString from missingObject instead of delegate. How to pass it this way, that the output will be delegate and the code will be inside the block?
Here you are:
- (BOOL)readyForProcessing {
void (^notDeclaredError)(SEL) = ^(SEL missingObject) {
NSString *missingObjectName = NSStringFromSelector(missingObject);
NSString *errorDescription = [NSString stringWithFormat:#"You need to provide %# property", missingObjectName];
[self configureErrorWithLocalizedDescription:errorDescription];
};
if (!self.delegate) notDeclaredError(#selector(delegate));
return self.error ? NO : YES;
}

iOS Incompatible pointer types returning NSDictionary from NSMutableDictionary

I'm having an issue where I use NSMutableDictionaries returns values from NSDictionary.
Here is the warning message:
incompatible pointer types returning 'NSDictionary *' from a function
with result type 'NSMutableDictionary *'
Here is the code:
- (NSMutableDictionary *)dictionaryWithContentsAtPath:(NSString *)path
{
if ([path rangeOfString:#"/SessionStore"].location == 0)
{
return [_inMemoryCache objectForKey:[path stringByReplacingCharactersInRange:NSMakeRange(0, 13) withString:#""]];
}
if ([path rangeOfString:#"/PermanentStore"].location == 0)
{
return [NSDictionary dictionaryWithContentsOfFile:[self.persistentStoragePath stringByAppendingPathComponent:[path substringFromIndex:15]]];
}
return nil;
}
Please help me how to resolve this warning.
You need to ensure you're returning the right type. Your method states it's returning an NSMutableDictionary, but then returns only an NSDictionary.
Try this instead:
- (NSMutableDictionary*)dictionaryWithContentsAtPath:(NSString*)path {
if ([path rangeOfString:#"/SessionStore"].location == 0) {
return [[_inMemoryCache objectForKey:[path stringByReplacingCharactersInRange:NSMakeRange(0, 13) withString:#""]] mutableCopy];
}
if ([path rangeOfString:#"/PermanentStore"].location == 0) {
return [NSMutableDictionary dictionaryWithContentsOfFile:[self.persistentStoragePath stringByAppendingPathComponent:[path substringFromIndex:15]]];
}
return nil;
}
Note: added a call to mutableCopy to turn your literal NSDictionary into a mutable version, and in the second case, used called the dictionaryWithContentsOfFile method on the NSMutableDictionary subclass instead of the NSDictionary parent class.

Check if JSON value exists - iOS

I have an iOS application which downloads and parses a Twitter JSON feed and then presents that feed in a UITableView. This all works fine but I have one question:
When the user taps a UITableView cell, the app will look into the array "tweets_links" and see if that particular tweet has an attached URL, if it does then the web view will appear.
Because not all tweets have website URLs, I have added a simple try catch statement (like in C++) which can tell me if there is an exception when trying to access that part of the array.
My question is: is this is good or bad approach to doing this??
Here is my code:
int storyIndex = indexPath.row;
int url_test = 1;
NSString *url;
#try {
url = [[tweets_links[storyIndex] valueForKey:#"url"] objectAtIndex:0];
}
#catch (NSException *problem) {
// There is NO URL to access for this Tweet. Therefore we get the out of bounds error.
// We will NOT take the user to the web browser page.
// Uncomment the line below if you wish to see the out of bounds exception.
// NSLog(#"%#", problem);
url_test = 0;
}
if (url_test == 1) {
WebBrowser *screen = [[WebBrowser alloc] initWithNibName:nil bundle:nil];
self.seconddata = screen;
seconddata.web_url = url;
screen.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:screen animated:YES completion:nil];
}
else if (url_test == 0) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Info" message:#"There is no URL attatched to this Tweet." delegate:self cancelButtonTitle:#"Dismiss" otherButtonTitles:nil];
[alertView show];
[tweetTableView deselectRowAtIndexPath:indexPath animated:YES];
}
Is there a much better way of trying to achieve what I am doing???
Thanks, Dan.
Using try and catch is Objective-C isn't encouraged there are other ways checking and handling errors
// firstObject will return the first object in the array or nil if the array is empty.
url = [[tweets_links[storyIndex][#"url"]] firstObject];
if (!url) {
// handle the case for no url
} else {
// do something with url
}
Since sending a message to nil is safe in Objective-C and returns nil it's safe to chain calls. e.g. If the dictionary didn't have an object for that key, then it would return nil and sending firstObject to nil returns nil.
Using either if the below approaches will be fine because TRY CATCH is used to catch programming errors
and use
objectForKey:
instead of
valueForKey:
if ([tweets_links[storyIndex] objectForKey:#"url"] != nil)
OR
if ([url isKindOfClass:[NSString class]])
{
// Code handling the URL
}
else
{
// Code handling there is no URL
}
I don't know a ton about the Twitter feed, but you can probably check for a nil value returned from objectForKey: like so
if ([tweets_links[storyIndex] objectForKey:#"url"] != nil) { /* process the URL */ }
Your code assumes that the value is always an array of at least size = 1, it would be safer to inspect the #"url" key's value before assuming it's an array.
Using exceptions in Objective-C is throwned upon. Exceptions are reserved for programming errors. You don't catch them, you fix the code. With a JSON document, you never have any guarantees what you received, so just be careful.
NSString* url = nil;
NSArray* linksArray = nil;
NSDictionary* linkDict = nil;
NSArray* urlArray = nil;
if ([tweet_links isKindOfClass:[NSArray class]])
linksArray = tweet_links;
if (storyIndex >= 0 && storyIndex < linksArray.count)
linkDict = linksArray [storyIndex];
urlArray = linkDict [#"url"];
if ([urlArray isKindOfClass:[NSArray class]] && urlArray.count > 0)
url = urlArray [0];
if ([url isKindOfClass:[NSString class]])
{
// Code handling the URL
}
else
{
// Code handling there is no URL
}
Note that sending messages to a nil object always returns 0 / NO / nil as appropriate.
And please get into the habit of naming variables properly. You wrote "int url_test = 1;". What does url_test mean? I read the variable name, I have no idea what it means. I need to understand all the code. Making it "int" means it could be 0, 1, 2, 20000 or whatever. If you write instead "BOOL urlValid = YES;" that is clear: It means that you have a valid URL.
Since url value is a NSString value, you could use length to check both if it's nil and if not, if it has any value (not empty string). You can check then if this NSString is a valid url.
- (BOOL) validateUrl: (NSString *) candidate {
NSString *urlRegEx = #"(http|https)://((\\w)*|([0-9]*)|([-|_])*)+([\\.|/]((\\w)*|([0-9]*)|([-|_])*))+";
NSPredicate *urlTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", urlRegEx];
return [urlTest evaluateWithObject:candidate];
}
....
NSString *url = [[tweets_links[storyIndex][#"url"]] firstObject];
if ([url length] && [self validateUrl: url]) {
// Has a valid URL
}

NSString Category Method Not Getting Called

The problem I am trying to solve is to convert nil strings into empty strings so I can put that as a value in NSDictionary.
I have the following code:
//Accessed in class
prospect.title = [self.titleLabel.text emptyWhenNil];
// NSString+Additions Category
-(NSString *) emptyWhenNil
{
if(self == nil)
return #"";
return self;
}
But for some reason the emptyWhenNil method is never even invoked!! I am lost..
The problem is, when your string pointer is nil, you have no object to send that category message to. Messages to nil are not executed (and return nil) in Objective-C.
If you want to put "null" objects into a collection, such as an NSDictionary, or NSArray, you can use the NSNull class instead.
Alternatively you could implement this method as a class method:
+ (NSString *)emptyStringIfNil:(NSString *)str {
if (!str) return #"";
return str;
}
You would then call this as
prospect.title = [NSString emptyStringIfNil:self.titleLabel.text];
If the string is nil, there is no self to execute the method on. The default behaviour of the runtime in this situation is to return a default nil value.
You should implement this as a static method instead. Something like this:
+ (NSString *)emptyStringIfNil:(NSString *)input
{
return input ? input : #"";
}
If the NSString pointer is nil, the method will not be called on it, what you can do is create a static method:
+ (NSString *) emptyIfNil:(NSString *) str
{
return str == nil ? #"" : str;
}

Resources