xcode - correct use of NSMutableDictionary - ios

I defined in a SecondView a public property
#property (weak, nonatomic) NSMutableDictionary * ToModify;
From firseview, I used that property to passing another dicnary to dsecond view
secondView.ToModify =origin;
But when I try to modify the content of ToModify by setObject method, crash appears:
_NSCFDictionary setObject:forKey:]: mutating method sent to immutable object
It seems that ToModify is a NSDictonary, and in fact by nslog class, it appears as NSDictonary.
Why it occurs?

The compiler complains that origin is an immutable NSDictionary.
To make it mutable, call mutableCopy
secondView.ToModify = [origin mutableCopy];

Declaring an object as NSMutableDictionary isn't enough - you have to instantiate it as an NSMutableDictionary. Where are you initialising ToModify?
At some point in SecondView, you currently have
self.ToModify = [[NSDictionary alloc] init]; //or something similar
which needs to be
self.ToModify = [NSMutableDictionary dictionary];
What actually is SecondView, and what's the actual purpose of ToModify? Are you overriding an existing property, or adding a new one?

Related

If we store a NSMutableDictionary into a NSDictionary, can we later use it again as a NSMutableDictionary?

If we replace a NSDictionary instance variable with a NSMutableDictionary that we create, can we later use it again as a NSMutableDictionary by casting it as a NSDictionary?
Example:
create and store the NSMutableDictionary into the NSDictionary slot
NSMutableDictionary *muta = [[NSMutableDictionary alloc] initWithObjects:NSArray forKeys:NSArray];
Object.nsDictionary = muta;
Get the dictionary later
NSMutableDictionary *muta2 = (NSMutableDictionary*) Object.nsDictionary;
//Do stuff like Add objects with it
[muta2 setObject:id forKey#"key"];
Do we have to recreate a NSMutableDictionary from the NSDictionary we pull from the object or does it retain it's "mutability"? Can you please tell me why a subclassed object will or will not retain its specific methods and properties when replacing a generic super class?
If your property is declared as NSDictionary then you shouldn't make any assumptions about whether it is actually mutable or not.
The proper code should be:
NSMutableDictionary *muta2 = [Object.nsDictionary mutableCopy];
This works regardless of what type of dictionary is actually stored in the property.
In your question you are confusing two different things: you refer to assigning to an instance variable but show code which shows assigning to a property. These are not the same. You are also appear to be misunderstanding assignment by referring to it as replacing an object.
In Objective-C (and many other, but not all, languages) an object is referred to by a reference. It is these references which are assigned into variables. So for example in:
NSMutableDictionary *a = [NSMutableDictionary new];
NSMutableDictionary *b = a;
The right hand side of the first assignment is an expression which creates a new object and returns a reference to that object. This reference is then stored into the variable a. The second line copies the reference, not the object, stored in a and stores into into the variable b.
After these two lines one object and two variables have been created, and both variables reference exactly the same object. Assignment of a reference to an object does not change the object it refers to. So if we now change the code to:
NSMutableDictionary *a = [NSMutableDictionary new];
NSDictionary *b = a;
We still have one object and two variables created, and both still refer to exactly the same object. The assignment in this case is allowed as NSMutableDictionary is a subclass of NSDictionary - that is an object of type NSMutableDictionary is also of type NSDictionary, it provides all the same behaviour as the latter.
From your question "Can you please tell me why a subclassed object will or will not retain its specific methods and properties when replacing a generic super class?" you need to read up on inheritance and understand how subclassing works.
Once you've stored a reference to a subclass into a superclass typed variables, a into b in the above code, while you haven't changed the referenced object in anyway you have lost the immediate knowledge that the reference is in fact to an object of the subclass - all you can immediately state about a reference stored in b above is that it refers to an object which is at least an NSDictionary, but may be of any subclass of NSDictionary.
If you are absolutely sure, or just like writing programs that break, you can tell the compiler to trust you that b contains a reference to an NSMutableDictionary by using a cast:
NSMutableDictionary *c = (NSMutableDictionary *)b;
Do this and the compiler trusts you, but if b does not contain a reference to an NSMutableDictionary then your subsequent usage of c will probably be invalid and your program will break.
What you need to do is to test whether the reference refers to an NSMutableDictionary or not, and you do this with the method isKindOfClass::
if ([b isKindOfClass:NSMutableDictionary.class])
{
// b refers to an NSMutableDictionary
NSMutableDictionary *c = (NSMutableDictionary *)b;
// do something with c
}
else
{
// b does not refer to an NSMutableDictionary
// handle this case
}
Back to properties: a property is two methods (assuming read-write, you can have read-only properties), a getter and a setter, which combine to provide an abstraction of a variable - you can "read" and "assign" to them using dot notation in expressions. However as they call a method, rather than performing direct reads or assignments to a variable, that method can change was is read or assigned. In particular an object typed property declared with the copy attribute will make a copy of the object that is reference. For example:
#property (copy) NSDictionary *c;
...
NSMutableDictionary *a = [NSMutableDictionary new];
NSDictionary *b = a;
self.c = a;
then a & b both refer to the same object which is an instance of NSMutableDictionary; while, due to the copy attribute,crefers to a *distinct* object which is an instance ofNSDictionary`.
You can now see why using instance variable and property interchangeably in your question is not right - what is assigned can be different depending on whether the assignment is to a variable or a property.
You should read up on objects, object references, inheritance and properties.
HTH.

JSONModel: Filling an NSArray of generic type

I'm using JSONModel in my iOS app and i'm facing some warnings, let me explain myself a bit.
Let's say i have this JSONModel
CTVContact.h
#interface CTVContact : JSONModel
#property (nonatomic, strong) NSArray<Optional, CTVPhone> *phone;
#end
CTVContact.m
NSMutableArray *phones = [[NSMutableArray alloc] init];
for(NSString *p in personPhones) {
CTVPhone *phn = [[CTVPhone alloc] init];
phn.original = p;
[phones addObject:phn];
}
phone = [NSArray arrayWithArray:phones];
Basically it all works like a charm but i get a warning stating the following:
Incompatible pointer types assigning to 'NSArray<Optional,CTVEventParticipant> *' from 'NSArray *'
How can i go around that warning? I can't find the right way to assign all the array values to the phone Array without that warning.
Thanks a lot in advance!
NSArray<Optional, CTVPhone> *phone; defines a variable that takes an array which conforms to 2 protocols. If you try and set that a variable (phone) to an array that doesn't state that it conforms to those protocols then you will get a compile warning.
phone = [NSArray arrayWithArray:phones]; just creates a 'plain' array, with no special protocols implemented. So you get a warning.
Probably the correct thing to do is to remove the protocols from the #property definition. Unless you have an NSArray subclass which conforms to those protocols that you should actually be using...
Alternatively, and assuming that you don't try to call any methods that might be defined in those protocols:
phone = (NSArray <Optional, CTVPhone> *)[NSArray arrayWithArray:phones];
which adds a cast that basically means to the compiler: 'trust me, it's fine'...
It looks like it may be complaining on your last line, since you're passing in an NSMutableArray when NSArray's arrayWithArray method calls for an NSArray. You can get away with this by calling copy on the phones array, as such:
phone = [NSArray arrayWithArray:[phones copy]];

NSCopying arrays of custom objects

I have a Singleton object that manages all my lists. We'll call it ListStore.
ListStore has a mutable array, which stores Lists.
#interface ListStore : NSObject
#property (nonatomic, copy) NSMutableArray *lists; // an array of List objects
end
Lists has a mutable array, which stores Things.
#interface Wanderlist : NSObject <NSCoding, NSCopying>
#property (nonatomic, copy) NSMutableArray *things; // an array of Thing objects
#end
At any time, a background process might go through ListStore and loop through and process all Lists, while a user might be interacting with a List.
To guard against "object was mutated while being enumerated" type errors, I do this:
// all of this is in a background thread
NSArray *newLists = [[ListStore sharedStore] lists] copy];
for (List *list in newLists) {
// yay, no more crashes, because I'm enumerating over a copied object, so the user
// can do whatever they want while I'm here
for(Thing *thing in list.things) {
// oh crap, my copy and the original object both reference the same list.things,
// which is why i'm seeing the 'mutation while enumerating" errors still
...
}
}
I originally thought that because I made a copy into newLists that all of its members would be properly copied. I now understand that not to be the case: I'm still seeing the "object was mutated while enumerated" errors, but this time it's happening on list.things.
Can I use NSCopying with my setup so that when I say:
[[ListStore sharedStore] copy];
It calls copyWithZone: on Lists, so I can then copyWithZone: on things?
I tried to set it up like this but copyWithZone: wasn't getting called.
I know I could simply say NSArray *newList = [list.things copy] but I'd like to get a better understanding of NSCopying at the very least.
Right before submitting this question I clicked on a question in SO's list of related questions, and found my solution.
Figured it doesn't hurt to post my solution.
Instead of this:
NSArray *newLists = [[ListStore sharedStore] lists] copy];
I had to do:
NSArray *newLists = [[NSArray alloc] initWithArray:[[ListStore sharedStore] lists] copyItems:true];
From the NSArray docs:
- (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag
flag:
If YES, each object in array receives a copyWithZone: message to create a copy of the object—objects must conform to the NSCopying protocol. In a managed memory environment, this is instead of the retain message the object would otherwise receive. The object copy is then added to the returned array.
Once I used initWithArray:copyItems:, it automatically sent copyWithZone to all my List objects, and I was able to then manually perform a copyWithZone on list.things.

EXC_BAD_ACCESS error when changing views with PresentModalViewController

I'm trying to switch views in my app using this chunk of code:
self->variable1 = [[NSNumber alloc] initWithInt:0];
self->variable2 = [[NSMutableArray arrayWithCapacity:1];
self->variable3 = [[NSMutableArray arrayWithCapacity:1];
[self presentModalViewController:titleScreen animated:YES];
If I comment out all of the allocated variable lines, the code works fine. If it leave just 1 line in the code crashes with the "EXC_BAD_ACCESS" error. Why is this happening? The variables aren't being used at all, just declared for later use. I'm not getting any compile errors on the lines either. What am I doing wrong?
UPDATE:
Thank you everyone for the help. I change the way I declare my variables to #property/#synth to clean up my code, but it didn't fix the problem. After a long time of fiddling I fixed it. I changed the code from this:
self.variable1 = [[NSNumber alloc] initWithInt:0];
to this:
self.variable1 = [NSNumber alloc];
[self.variable1 initWithInt:0];
and it worked! Can someone explain why this worked and the first line didn't?
Update:
Thank you Peter Hosey for showing me my evil ways. This time I'm pretty sure it's fixed. I was storing my variable Releases in
-(void)release
I didn't realize xCode will release when it needs to. I moved all the variable releases to
-(void)Destroy
so I can release everything on MY command. Now the code works. Thanks again!
I suggest that you declare variable1, variable2, and variable3 as properties, not instance variables. Then, use self.variable1, self.variable2, and self.variable3 to access them.
The dot syntax (self.variable1, etc.) uses the memory management policy you declared on each property; the arrow syntax (self->variable1, etc.) will access the variables directly. The crash is because you created two arrays in away that doesn't leave you owning them, and then did not assign the arrays to a property that would retain them.
You may also want to upgrade your project to use ARC. Then there is no memory-management difference; assigning to the instance variables rather than the properties will not cause the object to be prematurely released, because ARC considers instance variables to be ownerships by default. You may still want to switch to using properties after you switch to ARC, but not to prevent a crash.
In response to your edit:
I change the way I declare my variables to #property/#synth to clean up my code, but it didn't fix the problem.
Then something else was wrong.
You never did say much about the problem itself. You said you got an EXC_BAD_ACCESS, but not what statement triggered the crash or on what grounds you blamed it on the code you showed.
I changed the code from this:
self.variable1 = [[NSNumber alloc] initWithInt:0];
That's the correct code, though. That's what you should be using.
to this:
self.variable1 = [NSNumber alloc];
[self.variable1 initWithInt:0];
Noooo! That code is wrong, wrong, wrong, on multiple levels.
init methods (including initWithWhatever: methods) are not guaranteed to return the same object you sent the message to. NSNumber's initWithInt: very probably doesn't.
That object creates an uninitialized NSNumber object and assigns that to the property. Then it sends initWithInt: to that object, which will return an initialized object, which can be and very probably will be a different object. Now you are holding an uninitialized object (which you will try to use later) and have dropped the initialized object on the floor.
Never, ever, ever send alloc and init(With…) in separate expressions. Always send them in the same expression. No exceptions. Otherwise, you risk holding the uninitialized object rather than the initialized object. In your case (with NSNumbers), that is almost certainly what will happen.
What you should be doing is declaring and synthesizing a strong property that owns the NSNumber object, and creating the NSNumber object in a single statement: either [[NSNumber alloc] initWithInt:] or [NSNumber numberWithInt:]. If you're not using ARC, you'll want the latter, since the property will retain the object. If you are using ARC, they're effectively equivalent.
And if you get a crash with that code, then something else is wrong, so please tell us—either in this question or in a new question—about the crash so we can help you find the true cause of it.
variable2 and variable3 are being autoreleased before you actually access them (presumably) later after presenting the modal view.
At the very least change the lines to:
self->variable2 = [[NSMutableArray arrayWithCapacity:1] retain];
self->variable3 = [[NSMutableArray arrayWithCapacity:1] retain];
or
self->variable2 = [[NSMutableArray alloc] initWithCapacity:1];
self->variable3 = [[NSMutableArray alloc] initWithCapacity:1];
variable1 should be fine.
Best would be to use #property and #synthesize so you can use dot notation:
.h
#interface MyClass : SuperClass
#property (nonatomic,retain) NSMutableArray *variable2;
#property (nonatomic,retain) NSMutableArray *variable3;
#end
.m
#implementation MyClass
#synthesize variable2,varible3;
- (void)foo {
self.variable2 = [NSMutableArray arrayWithCapacity:1];
self.variable3 = [NSMutableArray arrayWithCapacity:1];
}
#end
By default, all instance variables in objective-c have protected scope. So unless you have explicitly declared them public in your interface file as:
#interface MYClass {
#public
NSNumber *variable1;
NSMutableArray *variable2;
NSMutableArray *variable3;
}
//...
#end
then they will not be accessible using the struct dereferencing operator. This is likely the cause of those EXC_BAD_ACCESS errors.

Can an autoreleased NSArray cause a memory leak?

I am trying to create an NSMutableArray using arrayWithArray, add two objects, sort, and store to an ivar as an NSArray. My code looks like this:
NSMutableArray *_mutableItems = [NSMutableArray arrayWithArray:[self.mainViewController.someDictionary allKeys]];
[_mutableItems addObject:#"Buildings"];
[_mutableItems addObject:#"Parking"];
self.curItems = [_mutableItems sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
When I profile the app I get a memory leak for an NSArray after the view is popped. But what I don't understand is: aren't all of these objects autoreleased? Am I increasing the retain count when I assign it to the instance property?
Yes, setting the property is probably increasing the retain count. Specifically, _mutableItems will be autoreleased, but the array you create with sortedArrayUsingSelectoris retained by the property.
Does your property declaration include retain or copy?
#property (retain) NSArray *curItems;
If so, in your class dealloc method, make sure you call release on the array;
- (void)dealloc {
[curItems release];
[super dealloc];
}

Resources