"Mutating method sent to immutable object" despite object being NSMutableDictionary - objective-c

I'm using NSMutableDictionary and hit this error:
'NSInternalInconsistencyException', reason: '-[__NSCFDictionary removeObjectForKey:]: mutating method sent to immutable object'
Here's the code:
// Turn the JSON strings/data into objects
NSError *error;
NSMutableDictionary *invoiceDictFromReq = [[NSMutableDictionary alloc] init];
// invoiceDictFromReq = (NSMutableDictionary *)[NSJSONSerialization JSONObjectWithData:[request responseData] options:kNilOptions error:&error];
invoiceDictFromReq = [NSMutableDictionary dictionaryWithDictionary:[NSJSONSerialization JSONObjectWithData:[request responseData] options:kNilOptions error:&error]];
NSLog(#"invoiceDictFromReq count: %i, key: %#, value: %#", [invoiceDictFromReq count], [invoiceDictFromReq allKeys], [invoiceDictFromReq allValues]);
// Get values and keys from JSON response
self.invoiceDict = [invoiceDictFromReq objectForKey:#"invoice"];
NSNumber *invoiceAmount = [self.invoiceDict objectForKey:#"amount"];
NSNumber *invoiceId = [self.invoiceDict objectForKey:#"id"];
NSNumber *invoiceNumber = [self.invoiceDict objectForKey:#"number"];
NSNumber *checkoutStarted = [self.invoiceDict objectForKey:#"checkoutStarted"];
NSNumber *checkoutCompleted = [self.invoiceDict objectForKey:#"checkoutCompleted"];
NSLog(#"amount: %#, id: %#, number: %#, started: %#, completed: %#", invoiceAmount, invoiceId, invoiceNumber, checkoutStarted, checkoutCompleted);
All the console logs indicate that the data is fine. This is where things start to break down.
I pass the invoiceDict property to the next view controller:
// Pass the invoice to checkoutViewController
[checkoutViewController setInvoiceDict:self.invoiceDict];
In CheckoutViewController.m:
// Change invoice checkoutCompleted to true
// [self.invoiceDict removeObjectForKey:#"checkoutCompleted"];
[self.invoiceDict setObject:[NSNumber numberWithBool:YES] forKey:#"checkoutCompleted"];
The error is at [self.invoiceDict setObject...]. I made sure that all the dictionaries I use are NSMutableDictionary. I left some of the commented-out lines in the code to show the things I've tried and I hit a brick wall. I suppose I can always create a new dictionary. Is that the preferred way to do it?

NSJSONSerialization returns immutable objects by default. Here is how to get mutable dictionary from the parser:
use option NSJSONReadingMutableContainers
or
use mutableCopy on the result

You are allocing a dictionary in invoiceDictFromReq and next you are creating another dictionary, you are creating a leak of memory there.
Delete the line
NSMutableDictionary *invoiceDictFromReq = [[NSMutableDictionary alloc] init];
But your problem is that you are creating a NSMutableDictionary but you are setting to self.invoiceDict a dictionary inside your mutableDictionary, that is not necessarily a mutableDictionary too.
Change the line
self.invoiceDict = [invoiceDictFromReq objectForKey:#"invoice"];
for
self.invoiceDict = [NSMutableDictionary dictionaryWithDictionary:[invoiceDictFromReq objectForKey:#"invoice"]];

Related

Filtering Parsed JSON in Objective-C

I'm trying to take out the "lasttradeprice" in https://www.allcrypt.com/api.php?method=singlemarketdata&marketid=672 but I can't seem to figure out how to grab the "lasttradeprice" piece.
How would I 'filter' the "price" out? None of the other information is relevant.
Current Code:
NSURL * url=[NSURL URLWithString:#"https://www.allcrypt.com/api.php?method=singlemarketdata&marketid=672"]; // pass your URL Here.
NSData * data=[NSData dataWithContentsOfURL:url];
NSError * error;
NSMutableDictionary * json = [NSJSONSerialization JSONObjectWithData:data options: NSJSONReadingMutableContainers error: &error];
NSLog(#"%#",json);
NSMutableArray * referanceArray=[[NSMutableArray alloc]init];
NSMutableArray * periodArray=[[NSMutableArray alloc]init];
NSArray * responseArr = json[#"lasttradeprice"];
for(NSDictionary * dict in responseArr)
{
[referanceArray addObject:[dict valueForKey:#"lasttradeprice"]];
[periodArray addObject:[dict valueForKey:#"lasttradeprice"]];
}
NSLog(#"%#",referanceArray);
NSLog(#"%#",periodArray);
NOTE: Keep in mind I've never worked with JSON before so please keep your answers dumbed down a tad.
Key value coding provides an easy way to dig through that data. Use the key path for the values you want. For example, it looks like you could get the array of recent trades using the path "return.markets.OMC.recenttrades" like this (assuming your code to get the json dictionary):
NSArray *trades = [json valueForKeyPath:#"return.markets.OMC.recenttrades"];
That's a lot more concise than having to dig down one level at a time.
The value returned for a given key by an array is the array of values returned by the array's members for that key. In other words, you can do this:
NSArray *recentprices = [trades valueForKey:#"price"];
And since that's just the next step in the key path, you can combine the two operations above into one:
NSArray *recentprices = [json valueforKeyPath:#"return.markets.OMC.recenttrades.price"];
The only down side here is that there's no real error checking -- either the data matches your expectations and you get back your array of prices, or it doesn't match at some level and you get nil. That's fine in some cases, not so much in others.
Putting that together with the relevant part of your code, we get:
NSURL *url = [NSURL URLWithString:#"https://www.allcrypt.com/api.php?method=singlemarketdata&marketid=672"];
NSData *data = [NSData dataWithContentsOfURL:url];
NSError *error = nil;
NSMutableDictionary *json = [NSJSONSerialization JSONObjectWithData:data options: NSJSONReadingMutableContainers error:&error];
NSArray *recentprices = [json valueforKeyPath:#"return.markets.OMC.recenttrades.price"];
Update: I just noticed that you want the "lasttradeprice", not the array of prices. Given that, the key path to use is simply #"return.markets.OMC.lasttradeprice", and the value you'll get back will be a string. So replace the last line above with:
NSString *lastTradePrice = [json valueforKeyPath:#"return.markets.OMC.lasttradeprice"];
The value you want is buried a few dictionaries deep. One general idea might be to dig recursively, something like this:
- (BOOL)isCollection:(id)object {
return [object isKindOfClass:[NSArray self]] || [object isKindOfClass:[NSDictionary self]];
}
- (void)valuesForDeepKey:(id)key in:(id)collection results:(NSMutableArray *)results {
if ([collection isKindOfClass:[NSDictionary self]]) {
NSDictionary *dictionary = (NSDictionary *)collection;
if (dictionary[key]) [results addObject:dictionary[key]];
for (id deeperKey in [dictionary allKeys]) {
if ([self isCollection:dictionary[deeperKey]]) {
[self valuesForDeepKey:key in:dictionary[deeperKey] results:results];
}
}
} else if ([collection isKindOfClass:[NSArray self]]) {
NSArray *array = (NSArray *)collection;
for (id object in array) {
if ([self isCollection:object]) {
[self valuesForDeepKey:key in:object results:results];
}
}
}
}
Then call it like this:
NSMutableArray *a = [NSMutableArray array];
[self valuesForDeepKey:#"lasttradeprice" in:json results:a];
NSLog(#"%#", a);

Objective-C: Static NSDictionary throws NSInvalidArgumentException when I add to it

I have a method that is supposed to take an NSManagedObject, copy its attributes into a dictionary, then add the dictionary to an NSMutableArray in a static NSMutableDictionary with NSManagedObjectID keys. The problem is that it crashes when I try to add to a static NSMutableDictionary and only works if I make one on the spot.
The problem is definitely related to the static NSMutableDictionary changes because I do not get the exception if I use a non-static dictionary. It's defined like this (above #implementation):
static NSMutableDictionary* changes = nil;
And here is the method:
+ (void)acceptChange: (NSManagedObject *)change{
if (!changes){
NSLog(#"Making new changes dicitonary"); //it prints this when I run
changes = [[NSDictionary alloc] init];
}
NSManagedObjectID* objectID = change.objectID;
NSMutableArray* changeArray = [changes objectForKey: objectID];
bool arrayDidNotExist = NO;
if (!changeArray){
changeArray = [[NSMutableArray alloc] init];
arrayDidNotExist = YES;
}
[changeArray addObject: [(this class's name) copyEventDictionary: change]]; //copies the NSManagedObject's attributes to an NSDictionary, assumedly works
if (arrayDidNotExist) [changes setObject: changeArray forKey: objectID];//throws the exception
//If I do the exact same line as above but do it to an [[NSMutableDictionary alloc] init] instead of the static dictionary changes, it does not throw an exception.
if (arrayDidNotExist) NSLog(#"New array created");
NSLog(#"changeArray count: %d", changeArray.count);
NSLog(#"changes dictionary count: %d", changes.count);
}
The exact exception message is this:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryI setObject:forKey:]: unrecognized selector sent to instance 0xa788e30'
Use NSMutableDictionary instead of NSDictionary. You are getting exception as because , NSMutableDictionary can be modified dynamically, NSDictionary cannot. .
NSMutableDictionary is subclass of NSDictionary. So all methods of NSDictionary is accessible via NSMutableDictionary object. Moreover NSMutableDictionary also adds complementary methods to modify things dynamically, such as the method setObject:forKey:
EDIT
You have initialized it using NSDictionary instead of `NSMutableDictionary.
if (!changes){
NSLog(#"Making new changes dicitonary"); //it prints this when I run
//changes = [[NSDictionary alloc] init];
^^^^^^^^^^^^^^ ------------------> Change this.
changes = [[NSMutableDictionary alloc] init];
}
[__NSDictionaryI setObject:forKey:] shows that your dictionary is immutable. You are actually initializing your dictionary as immutable. That's why its raising exception on adding an object.
Here change this line:
if (!changes){
....
changes = [[NSDictionary alloc] init];
}
to:
if (!changes){
....
changes = [[NSMutableDictionary alloc] init];
}
You declared your dictionary to be of NSMutableDictionary, so at compile time your dictionary is of NSMutable dictionary, but at run time it is NSDictionary as you allocated it as NSDictionary, to which you can not make changes, hence the exception. Please define the dictionary as :-
changes = [[NSMutableDictionary alloc] init];
If you read the description of your exception, it says the same thing.
Hope this helps.

NSMutableDictionary won't save data

hope someone can help me with a problem I've been wrestling with...
Using MapBox to develop a map-based app, and I want to attach an NSMutableDictionary to each of the map annotations to store additional data. I had it working but XCode kept throwing me warning about some of my data/object types, so I went through and tidied those up, and now it's broken. The idea is that on ViewDidLoad, the program runs through a set of plist dictionaries to set up each annotation correctly - that's still running okay, because my initial anno markers pop up with their correct settings. However rather than run back to the plist every time, I want to attach a dictionary to each annotation's userinfo property, which I can then use for toggling selection data and other functions. Here's my code:
NSDictionary *ExploreSteps = [[NSDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"ExploreSteps" ofType:#"plist"]];
for (NSString *key in [ExploreSteps allKeys])
{
//Loop through keys for each anno
NSDictionary *thisStep = [ExploreSteps objectForKey:key];
NSNumber *annoIndex = [thisStep objectForKey:#"Index"];
NSNumber *isLive = [thisStep valueForKey:#"isLive"];
NSString *annoTitle = [thisStep objectForKey:#"Title"];
NSString *annoText = [thisStep objectForKey:#"Text"];
NSString *imagefile = [thisStep objectForKey:#"Imagefile"];
double longitude = [[thisStep objectForKey:#"Longitude"] doubleValue];
double latitude = [[thisStep objectForKey:#"Latitude"] doubleValue];
NSString *pagefile = [thisStep objectForKey:#"Pagefile"];
NSString *audiofile = [thisStep objectForKey:#"Audiofile"];
CLLocationCoordinate2D annoCoord = CLLocationCoordinate2DMake(latitude, longitude);
RMAnnotation *annotation = [[RMAnnotation alloc] initWithMapView:mapView coordinate:annoCoord andTitle:annoTitle];
annotation.annotationIcon = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource:imagefile ofType:#"png"]];
annotation.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:annoIndex, #"index", isLive, #"isLive", annoTitle, #"annoTitle", annoText, #"annoText", imagefile, #"imagefile", pagefile, #"pagefile", audiofile, #"audiofile", nil];
NSLog(#"Title: %#",[annotation.userInfo objectForKey:#"annoTitle"]);
[mapView addAnnotation:annotation];
}
The NSLog should spit out the annoTitle string, but instead it's giving me a null every time, and the behaviour of the rest of the app also shows that info stored in the dictionary simply isn't "getting through".
Any ideas? Thanks in advance!
ETA: Modified code for initializing the dictionary (not that it seems to make any difference to the problem!):
NSMutableDictionary *myUserInfo = [[NSMutableDictionary alloc] initWithObjectsAndKeys:annoIndex, #"index", isLive, #"isLive", annoTitle, #"annoTitle", annoText, #"annoText", imagefile, #"imagefile", pagefile, #"pagefile", audiofile, #"audiofile", nil];
annotation.userInfo = myUserInfo;
NSLog(#"Title: %#",[annotation.userInfo objectForKey:#"annoTitle"]);
NSLog(#"Length: %u",[[annotation.userInfo allKeys] count]);
(Title now returns "(null)", while Length returns "1", if that's at all helpful...)
Almost certainly one of your objects is nil. You mention that allKeys] count] returns 1 so I can go further and say that your value for isLive is nil. Hence your original line:
[NSMutableDictionary dictionaryWithObjectsAndKeys:annoIndex, #"index", isLive, #"isLive", annoTitle, #"annoTitle", annoText, #"annoText", imagefile, #"imagefile", pagefile, #"pagefile", audiofile, #"audiofile", nil];
Acts exactly the same as:
[NSMutableDictionary dictionaryWithObjectsAndKeys:annoIndex, #"index", nil, #"isLive", annoTitle, #"annoTitle", annoText, #"annoText", imagefile, #"imagefile", pagefile, #"pagefile", audiofile, #"audiofile", nil];
And the dictionary takes annoIndex to be the final key-value pair.
I'd suggest that probably you want to take a mutable copy of thisStep and strip out the keys you don't want, then pass it along as the userInfo.
It's the way you are creating the NSMutableDictionary for userInfo. Take a look at this Difference between [[NSMutableDictionary alloc] initWithObjects:...] and [NSMutableDictionary dictionaryWithObjects:...]?
"
+dictionaryWithObjects: returns an autoreleased dictionary
-initWithObjects: you must release yourself
if you want the dictionary to persist as a instance variable, you should create it with an init method or retain an autoreleased version, either way you should be sure to release it in your dealloc method
"

How to convert json string to nsdictionary on json parser framework on objective c

I am trying to convert raw json string to NSDictionary. but on NSDictionary i got different order of objects as on json string but i need exactly same order in NSDictionary as on json string. following is code i have used to convert json string
SBJSON *objJson = [[SBJSON alloc] init];
NSError *error = nil;
NSDictionary *dictResults = [objJson objectWithString:jsonString error:&error];
From NSDictionary's class reference:
The order of the keys is not defined.
So, basically you can't do this when using a standard NSDictionary.
However, this may be a good reason for subclassing NSDictionary itself. See this question about the details.
NSDictionary is an associative array and does not preserve order of it's elements. If you know all your keys, then you can create some array, that holds all keys in correct order (you can also pass it with your JSON as an additional parameter). Example:
NSArray* ordered_keys = [NSArray arrayWithObjects: #"key1", #"key2", #"key3", .., nil];
for(NSString* key is ordered_keys) {
NSLog(#"%#", [json_dict valueForKey: key]);
}
//parse out the json data
NSError* error;
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:responseData //1
options:kNilOptions
error:&error];
NSArray* latestLoans = [json objectForKey:#"loans"]; //2
NSLog(#"loans: %#", latestLoans); //3
Source: Follow this link http://www.raywenderlich.com/5492/working-with-json-in-ios-5
Good tutorial but works only on iOS5

Obtaining an NSArray of Core Data properties

Consider a Core Data database containing Elements where each Element has a property called symbol, and the question is the most succinct method of obtaining an NSArray of each of the symbols. This can be accomplished with something along the lines of
-(NSArray*)symbolsInDatabase {
ENTRY_LOG;
NSError* err;
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:#"Element"];
request.resultType = NSDictionaryResultType;
request.propertiesToFetch = [NSArray arrayWithObject:#"symbol"];
NSArray* arrayOfDictionaries = [self.database.managedObjectContext executeFetchRequest:request error:&err];
NSMutableArray* symbols = [[NSMutableArray alloc]initWithCapacity:[arrayOfDictionaries count]];
for (NSDictionary* d in arrayOfDictionaries) {
[symbols addObject:[d objectForKey:#"symbol"]];
}
EXIT_LOG;
return symbols;
}
Yet there is a nagging feeling I'm missing something, and that I can be using -(NSArray*)filteredArrayUsingPredicate in some clever manner rather than iterating over the array of dictionaries and extracting the object for the symbol key.
Any thoughts on how to make this cleaner?
Indeed, the valueForKeyPath selector:
NSArray *symbols = [arrayOfDictionaries valueForKeyPath:#"#unionOfObjects.symbol"];