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

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.

Related

"unrecognized selector sent to instance" for NSArrayI

I have the following code which gives the "unrecognized selector sent to instance" error for the NSArray. I've not been able to figure this out and feel its something simple I'm missing here. I can post more if needed.
Quote *myQuote;
NSArray *myQuotes = theSubject.quotes;
//START LOOP HERE
for (myQuote in myQuotes){
NSLog(#" excerpt = %#", myQuote.excerpt);
NSLog(#" desc2 = %#", myQuote.desc2);
NSLog(#" quote_date = %#", myQuote.quote_date);
NSLog(#" myQuote = %#", myQuote);
I believe the problem is in this function which returns an array of Quotes:
- (NSArray *) getQuotesFromSubId:(NSInteger )subId {
QuotesAppDelegate *appDelegate = (QuotesAppDelegate *)[[UIApplication sharedApplication] delegate];
self.quoteMaps = [appDelegate quoteMaps];
self.quotes = [appDelegate quotes];
//get the quote_ids from quote_map for this subject_id
NSString *stringOfSubjectId = [NSString stringWithFormat:#"%ld", (long)subId];
NSPredicate *filterSubjectId = [NSPredicate predicateWithFormat:#"subject_id == %#", stringOfSubjectId];
NSArray *quoteMapSection = [self.quoteMaps filteredArrayUsingPredicate:filterSubjectId];
NSMutableArray *quoteSection = [[NSMutableArray alloc] init];
NSArray *quoteToAdd = [[NSArray alloc] init];
for (QuoteMap *qm in quoteMapSection){
//get the quote_ids from quote_map for this subject_id
NSPredicate *filter = [NSPredicate predicateWithFormat:#"quote_id == %#", qm.quote_id];
quoteToAdd = [self.quotes filteredArrayUsingPredicate:filter];
[quoteSection addObject:quoteToAdd];
}
return quoteSection;
}
This is where I call it:
QuotesAppDelegate *appDelegate = (QuotesAppDelegate *)[[UIApplication sharedApplication] delegate];
NSArray *myQuotes = [appDelegate getQuotesFromSubId:selectedSubject.subject_id];
NSMutableArray *mArray = [appDelegate createMutableArray:myQuotes];
selectedSubject.quotes = mArray;
NSMutableArray *mutableArray = [appDelegate createMutableArray:myQuotes];
selectedSubject.quotes = mutableArray;
I got the following error
2016-02-23 00:24:20.383 Quotes[10631:3698114] -[__NSArrayI excerpt]: unrecognized selector sent to instance 0x15ebbeff0
2016-02-23 00:24:29.164 Quotes[10631:3698114] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI excerpt]: unrecognized selector sent to instance 0x15ebbeff0'
*** First throw call stack:
(0x182b55900 0x1821c3f80 0x182b5c61c 0x182b595b8 0x182a5d68c 0x100078b2c 0x1000642d0 0x187cb17f4 0x187cb1f8c 0x187b9fc90 0x187ba2e88 0x187977284 0x187883394 0x187882e90 0x187882d18 0x185259c00 0x10011dbb0 0x100123658 0x182b0cbb0 0x182b0aa18 0x182a39680 0x183f48088 0x1878b0d90 0x100040398 0x1825da8b8)
libc++abi.dylib: terminating with uncaught exception of type NSException
You are sending -excerpt to the members (myQuote) of myQuotes. The runtime says that NSArray (NSArrayI is an internal subclass) instances cannot understand -excerpt.
So the type of the member is NSArray. We cannot know, why you have instances of NSArray in the array MyQuotes, because we do not see that code. Likely that happened when you tried to add new quotes to the quotes property and incidentally added the whole array instead of its members.
To your edit:
This is wrong:
NSArray *quoteToAdd = [[NSArray alloc] init]; // This is an array. It identifier should be quote*s*ToAdd
// BTW: This above code is meaningless, because you do not need to create an array instance. Simply omit "[[NSArray alloc] init]". But this is not your problem.
for (QuoteMap *qm in quoteMapSection){
…
quoteToAdd = [self.quotes filteredArrayUsingPredicate:filter]; // filtered array returns an *array*
[quoteSection addObject:quoteToAdd]; // You add the *array* instead of the member of the array.
}
What you get back is an array. Then you add the array itself (not its members) to the existing array. As result you get an array that contains an array.
Simply change …
[quoteSection addObject:quoteToAdd];
… to:
[quoteSection addObjectsFromArray:quoteToAdd];
(And change the reference name to a plural form for better readability.)

NSArray copy for undo /redo purposes

If I have an nsarray full of custom objects and I make a second array using:
NSArray *temp = [NSArray arrayWithArray:original];
then work with some properties of the objects inside the original array, then decide to roll back, I am then using the reverse:
original = [NSArray arrayWithArray:temp];
I am finding the objects I changed in the array also effected my temp array. I also tried implementing copyWithZone on my custom class, and using copyItems and it did not help. What else should I try?
To be clear, in order to use copyWithZone, I changed my array creation command to:
NSArray *temp = [[NSArray alloc] initWithArray:original copyItems:YES];
My copyWithZone:
-(id)copyWithZone:(NSZone *)zone{
CustomObject *ret = [[CustomObject allocWithZone: zone] init];
//copy properties
return ret;
}

"Mutating method sent to immutable object" despite object being NSMutableDictionary

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"]];

NSMutableDictionary - "Incorrect decrement of the reference count of an object..."

I have a property in my header file as
#property (nonatomic,retain) NSMutableDictionary* e;
and in my viewDidLoad: method allocated it like
self.e = [[NSMutableDictionary alloc] initWithContentsOfURL:myURL];.
The XCode static analyser is triggered, and says 'Potential leak of an object...' obviously. But when I release the object ([self.e release] in dealloc) the error persists, but now it also is saying that there is an "incorrect decrement of the reference count", and that this object is not owned by the caller (my viewController).
The 'incorrect decrement...' error goes away when I replace [self.e release] with simply [e release]. But the former error about potential leak is still there. What is the problem?
This statement:
self.e = [[NSMutableDictionary alloc] initWithContentsOfURL:myURL];
is over-retaining the object. Both alloc-init and the property retain the object.
In terms of ownership, you own the object returned by alloc-init and by sending it a retain message in the property accessor you claim ownership of it again, which results in the object being over-retained.
You can use a convenience constructor, which returns an object yo do not own, and let the property accessor claim ownership of it:
self.e = [NSMutableDictionary dictionaryWithContentsOfURL:myURL];
Or make use of autorelease:
self.e = [[[NSMutableDictionary alloc] initWithContentsOfURL:myURL] autorelease];
Or use a temporary variable:
NSMutableDictionary *tempDict = [[NSMutableDictionary alloc] initWithContentsOfURL:myURL];
self.e = tempDict;
[tempDict release];
The problem is:
self.e = [[NSMutableDictionary alloc] initWithContentsOfURL:myURL];
your property e has the retain flag set. Therefore, after it retains it you've increased the retain count by two. You should release the object after handing it over to the property.
self.e = [[[NSMutableDictionary alloc] initWithContentsOfURL:myURL] autorelease];

Initializing an instance variable

With an instance variable myArray:
#interface AppController : NSObject
{
NSArray *myArray;
}
Sometimes I see myArray initialized like this:
- (void)init
{
[super init];
self.myArray = [[NSArray alloc] init];
return self;
}
and sometimes I see it with a more complicated method:
- (void)init
{
[super init];
NSArray *myTempArray = [[NSArray alloc] init];
self.myArray = myTempArray
[myTempArray release];
return self;
}
I know that there's no difference in the end result, but why do people bother to do the longer version?
My feeling is that the longer version is better if the instance variable is set up with a #property and #synthesize (possibly because the variable has already been alloced). Is this part of the reason?
Thanks.
If myArray is a property and it's set to retain or copy (as it should be for a property like this), then you'll end up double-retaining the variable when you do this:
self.myArray = [[NSArray alloc] init];
The alloc call sets the reference count to 1, and the property assignment will retain or copy it. (For an immutable object, a copy is most often just a call to retain; there's no need to copy an object that can't change its value) So after the assignment, the object has retain count 2, even though you're only holding one reference to it. This will leak memory.
I would expect to see either a direct assignment to the instance variable
myArray = [[NSArray alloc] init];
Or proper handling of the retain count:
NSArray *newArray = [[NSArray alloc] init];
self.myArray = newArray;
[newArray release];
Or the use of autoreleased objects:
self.myArray = [[[NSArray alloc] init] autorelease]; // Will be released once later
self.myArray = [NSArray array]; // Convenience constructors return autoreleased objects
This is an idiom used in mutators (sometimes called "setters"), but I think you typed it slightly wrong. Usually it looks like this:
-(void)setMyName:(NSString *)newName
{
[newName retain];
[myName release];
myName = newName;
}
The new name is retained, since this instance will need to keep it around; the old name is released; and finally the instance variable is assigned to point to the new name.
I have a feeling you mean this:
NSArray* a = [NSArray arrayWithObjects:#"foo", #"bar", nil];
and this
NSArray* a = [[NSArray alloc] initWithObjects:#"foo", #"bar", nil];
//...
[a release];
With the first style, the static method performs an alloc/init/autorelease on it for you so you don't have to. With the second style, you have more control over when the memory is released instead of automatically releasing when you exit the current block.
That code will crash your application. The second version only copies the pointer then releases the instance. You need to call [object retain] before releasing the reference.