I have a view controller class called PresidentsViewController that sets up data in a UITableView. This data is in the form of NSMutableArray called list. I have another class, PresidentAddController, that is supposed to handle adding an object of type President to this list based on user inputted data about a president. However, I can't get the object to add to the list. I have confirmed that the user inputted data for a new president is being collected correctly, so it is adding to the list that's in another class that's causing problems. I believe the correct code to add an object to the list is:
[pvc.list addObject:newPresident];
However, I don't know how to properly create the reference/instance/? (which is what pvc would be) to PresidentsViewController inside of PresidentAddController so that I can properly add a new president to the list. I am not using Interface Builder for this because it is just a UITableView.
How do I add a president to the list in this situation?
Edit: Here is how the array is being initialized:
#property (nonatomic, retain) NSMutableArray *list;
And here is how PresidentAddController is being set up in PresidentsViewController:
PresidentAddController *childController = [[PresidentAddController alloc] initWithStyle:UITableViewStyleGrouped];
childController.title = #"Add President";
[self.navigationController pushViewController:childController animated:YES];
[childController release];
Add a pointer to PresidentAddController this way:
// in #interface
PresidentsViewController *listController;
#property (nonatomic, assign) PresidentsViewController *listController;
// in #implementation
#synthesize listController;
Then when you instantiate your PresidentAddController, set the pointer:
PresidentAddController *childController =
[[PresidentAddController alloc]
initWithStyle:UITableViewStyleGrouped];
childController.title = #"Add President";
childController.listController = self;
[self.navigationController pushViewController:childController animated:YES];
[childController release];
So then you can go [listController.list addObject:newPresident]; in PresidentAddController.
EDIT: childController.listController = self calls [childController setListController:self], which in turn reads the #synthesized method in your implementation and sets the pointer *listController to point to the current class (if you're writing code in the PresidentsViewController class, then self is going to be the current instance of PresidentsViewController).
The reason why I use assign is because if you were to use retain, then when you set listController to self it will actually keep an owning reference to the object. This can cause all sorts of problems if you ever try to deallocate PresidentsViewController, because if you have an owning reference in PresidentAddController then it will not deallocate until that reference is also released. Using assign ensures that if you ever release the PresidentsViewController before PresidentAddController disappears, it will be properly deallocated. Of course, maybe you want to keep it around in that situation, in which case using retain here is also fine.
My suspicion here would be that you have a property defined as #property (nonatomic,copy) NSMutableArray *list; Are you getting and exception trying to add the object into the array? If so, it's likely because the copy modifier is returning an immutable copy of the array. Try to create a method that takes the object and adds it to the list without using self.list ... just [list addObject] and if that works then this is your problem.
Related
I create a NSMutableArray that I need as long as my app lives, lets call it suseranArray, just after the #implementation of my main class. This Array will hold several objects of a class called Vassal. A Vassal is simply:
1) A NSMutableString
2) Another NSMutableString
3) A NSMutableArray
4) Another NSMutable Array
Each Vassal created is also needed for the life of the app, and they never change.
These objects are made as (retain) properties in an .h file, synthesized in the .m file, and each given an alloc+init whenever the object Vassal is created during the init function. Each vassal has data filled in and stored in the suzerain Array. the 3rd item always has several elements, and after a bug appeared, I put a line to check if it is ever empty, but it never is, and life is good.
Now, later on when a certain Vassal object is needed, we try to access its 3rd property to fetch the data in there, and sometimes that array empty... I checked to see if it disappeared somehow, but it is always there on the debug, carrying a nice address like 0x2319f8a0 which makes sense since the NSMutableString just above it is at address 0x2319fb40 - (I was expecting 00000000 after a lot of headache). What is happening? I my head, I am creating an RETAINed objects, which retains data put in by default, and that object is put inside another, but somehow the data inside the array vanishes. What possible scenario could lead to this? Thank you for your time :)
Note: the last array currently just holds one item at this stage of development, and curiously enough, that one item is never missing, despite the two arrays being 'brothers'
Vassal.h
#interface Vassal : NSObject
#property (retain) NSMutableString *wordBody;
#property (retain) NSMutableString *wordCode;
#property (retain) NSMutableArray *wordRelations;
#property (retain) NSMutableArray *wordLinks;
#end
Vassal.m
#implementation Vassal:NSObject
#synthesize wordBody;
#synthesize wordCode;
#synthesize wordRelations;
#synthesize wordLinks;
-(NSObject*) init
{
if(self=[super init])
{
wordBody=[[NSMutableString alloc] init];
wordCode=[[NSMutableString alloc] init];
wordRelations=[[NSMutableArray alloc] init];
wordLinks=[[NSMutableArray alloc] init];
}
return self;
}
//Somewhere in Suseran:
-(void)fillStuff
{
...
Vassal *vassal=[Vassal new];
for (int i=0;i<[originalDataString length];i++)
{
...
[vassal.wordRelations addObject:anItem];
...
}
int errorTest=[vassal.wordRelations count];
if (errorTest==0)
{
//breakpoint here. Program NEVER comes here
}
[bigArrayOfVassals addObject:vassal];
}
//these arrays are never touched again but here:
-(void) getVassalstuff:(NSMutableString*)codeOfDesiredVassal
{
Vassal *aVassal;
for (int i=0;i<[bigArrayOfVassals count];i++)
{
aVassal=bigArrayOfVassals[i];
if ([codeOfDesiredVassal isEqualToString:aVassal.wordCode)
{
int errorTest=[aVassal.wordRelations count];
if (errorTest==0)
{
//yay! this breakpoint sometimes is hit, sometimes not,
//depending on code's mood. Why is this happening to me? :,(
}
}
}
}
I see that that you have properties that are mutable (which is itself a bad idea except for specific cases) and that you are retaining them.
Mutability means that if you have set the array as a property based on some other array, and if that original array is changed, the array in your property is also changed. It may be, and I don't know because you haven't shown any code, that you are emptying the original array, and thus changing the array you have as a property
Solutions:
My preferred solution is to use the immutable versions of these classes for your properties; NSString, NSArray and instead of retain use copy
A second solution is to leave the properties as mutable, but write a custom setter for each of them that stores a mutableCopy of the object that you pass in.
In both of these cases, your property will be a copy of the object used to set the property, so that if the object is changed outside of your class it will not affect your class's properties.
edited to add, after a comment
If you declare your property as
#property (copy) NSArray wordRelations;
Then simply writing
vassal wordArray = tempArray;
will do the same thing and is cleaner and more readable..
There is a need to run some code when one object get dealloc. E.g, I set up one observer which updates the label A's text when object B's name changed. When label A's retain count reach 0, I want to remove the observer from B.
Possible solutions:
1 Subclass and call the clean code in dealloc.
2 Create a wrapper class which able to run arbitrary code in dealloc and associate this object to label A. When A get dealloc, the associated object get dealloc too (suppose only A holds the strong ref to it), then the code get called.
I don't like the 1st one since it is so intrusive that makes it barely useless, need to subclass just for some easy stuff. So I am using No.2.
Do you have any comments? How do you do it?
I put my solution here in case someone needed.
#interface ExecuteWrapper : NSObject
#property (nonatomic, copy) void(^block)();
-(void)dealloc;
#end
#implementation ExecuteWrapper
-(void)dealloc{
if(self.block){
self.block();
}
}
#end
#implementation NSObject (SLUtil)
+(void)executeWhenDealloc:(NSObject *)object block:(void(^)())block{
static char key;
NSMutableArray *executeWrapperArray = [object associatedValueForKey:&key];
if ( executeWrapperArray == nil){
executeWrapperArray = [NSMutableArray array];
[object associateValue:executeWrapperArray withKey:&key];
}
ExecuteWrapper *executeWrapper = [[ExecuteWrapper alloc] init];
executeWrapper.block = block;
[executeWrapperArray addObject:executeWrapper];
}
#end
In client code
[NSObject executeWhenDealloc:labelA block:^{
// clean up code
}];
Note: Keep in mind that don't hold a strong ref to label A in the label
still having problems!
I have 2 classes, and i want to access the properties of one class from the other.
I have a class called MenuViewController, which is a Facebook styled popout menu, and depending on which cell is selected, i want to load different data.
I have created a property:
#property int rowSelected;
and synthesized it:
#synthesize rowSelected;
and in my other class, i have created an instance of the class:
MenuViewController *menu = [[MenuViewController alloc] init];
NSInteger rowValue = menu.rowSelected;
and when i NSLog it, it always returns 0...
NSLog(#"%i", menu.rowSelected);
Help is much appreciated!
The call to alloc creates a new MenuViewController instance. Unless you set a value for your property in its init method, then you should expect it to be 0.
I have a property declared in my .h file as
#property (weak, nonatomic) UIPickerView *levelPicker;
which is synthezised in my implementation file as:
#synthesize levelPicker = _levelPicker;
I then have a code block in the same implementation file which does the following:
if (self.levelPicker == nil) {
self.levelPicker = [[UIPickerView alloc] initWithFrame:CGRectZero];
self.levelPicker.delegate = self;
self.levelPicker.dataSource = self;
}
textField.inputView = self.levelPicker;
In this case, self._levelPicker is not set to the new UIPickerView. I.e. the self.levelPicker = blah assignment doesn't work.
However, if I change the property declaration to:
#property (strong, nonatomic) UIPickerView *levelPicker;
then everything works as expected and _levelPicker is set to the newly allocated UIPickerView.
Can someone please tell me why this is the case? I thought I was coming to an understanding of how references work, but I guess I still have more to learn. I read some of the other related SO posts, but it's still not entirely clear to me.
As #Inazfiger says, your objects need at least one strong (retaining) reference, otherwise they won't be retained.
In this case, you're assigning the picker view to a UITextField's inputView property. The text field will retain the picker view (I know this because the inputView property on UITextField is declared with the modifiers "readwrite, retain"), but only once you've made the assignment. So if you want to stick with a weak reference, you need to rearrange your code slightly - something like this:
// Declare a temporary UIPickerView reference. By default, this is
// a strong reference - so tempPicker will be retained until this
// variable goes out of scope.
UIPickerView *tempPicker = [[UIPickerView alloc] initWithFrame:frame];
// Configure the picker
tempPicker.delegate = self;
tempPicker.dataSource = self;
// Assign the picker view to the text field's inputView property. This
// will increase the picker's retain count. Now it'll no longer be
// released when tempPicker goes out of scope.
textField.inputView = tempPicker;
// Finally, assign the same object to self.levelPicker - it won't
// go out of scope as long as it remains assigned to textField's
// inputView property, and textField itself remains retained.
self.levelPicker = tempPicker;
Well, the short answer is that the assignment actually does work.
However, since it is a weak reference, it isn't retained since there is no (other) strong reference to your picker and it is automatically set to nil.
There has to be at least one strong reference to any object otherwise it isn't retained, which in this case there isn't.
For more information, see "ARC Introduces New Lifetime Qualifiers" in Apple's "Transitioning to ARC Release Notes".
Ray Wenderlich created a great tutorial on this here.
The "strong" qualifier creates an owner relationship that stops the object being deallocated, which is equivalent what you would do in the non-ARC world previously:
#property(retain) NSObject *obj;
While "weak" qualifier doesn't create the owner relationship so the object will be deallocated as you would do before:
#property(assign) NSObject *obj;
In your case, you need the first relationship because you need your instance variable (_levelPicker) to hold on to the newly created UIPickerView instance. The weak assignment you did actually work but it was deallocated shortly after.
I'm pretty new to objective-c and try to create a small app for the iphone.
I'm nearly done beside this little error here. Actually, I've searched hours with google to find a proper solution but unfortunately I'm not able to find a solution which works.
I'm using this tutorial here to build up an UITableView: UITableView Tutorial
The full error message looks like this:
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '* -[NSCFArray insertObject:atIndex:]: mutating method sent to immutable object'
This is the Data Controller Header:
MyLinksDataController.h
#interface MyLinksDataController : NSObject {
NSMutableArray *tableList; //<---important part
}
- (unsigned)countOfList;
- (id)objectInListAtIndex:(unsigned)theIndex;
- (void)addData:(NSString *)data; //<---important part
- (void)removeDataAtIndex:(unsigned)theIndex;
#property (nonatomic, copy, readwrite) NSMutableArray *tableList; //<---important part
.....
And the Data Controller Method:
MyLinksDataController.m
#import "MyLinksDataController.h"
#implementation MyLinksDataController
#synthesize tableList;
- (id)init {
if (self = [super init]) {
NSLog(#"Initilizing DataController");
//Instantiate list
NSMutableArray *localList = [[NSMutableArray alloc] init];
self.tableList = [localList copy];
[localList release];
//Add initial Data
[self addData:#"AAAAAAAAAAAAAA"];
[self addData:#"BBBBBBBBBBBBBB"];
}
return self;
}
-------------------------------later on in the source code---------------------------------
- (void)addData:(NSString*)data; {
[tableList addObject:data]; //<---- here the app crashes
}
I would pretty much appreciate any help.
Thank you for your support in advance.
Daniel
Sending the copy message to an NSMutableArray -- as in the following statement in init -- returns an immutable copy.
self.tableList = [localList copy];
Cocoa documentation uses the word immutable to refer to read-only, can't-be-changed-after-initialization objects. Hence the subsequenct call to addObject: fails with an error message.
Note how the assignment statement above doesn't trigger any compiler warning. copy returns an id, which fits comfortably -- as far as the compiler is concerned -- in the NSMutableArray* tableList. There's no runtime error here either, as no messages get passed around; an NSArray pointer is just placed in an NSMutableArray pointer variable.
To obtain a mutable copy, use mutableCopy instead.
Note that both copy and mutableCopy create a new array and copy the content of the original to it. A change in the copy will not be reflected in the original. If you just need another reference to the original array, use retain instead.
You can find more detail in the discussion section of the copyWithZone reference and in the NSMutableCopying protocol reference.
You're running into, basically, the memory management rules of Cocoa (specifically, these details). If there is an object with an immutable version and a mutable version, then sending -copy to an object will return an immutable object.
Let's step through the relevant part.
NSMutableArray *localList = [[NSMutableArray alloc] init];
This creates a new, empty mutable array that you own. Fine.
self.tableList = [localList copy];
This creates an immutable copy of the empty array. Furthermore, you own this freshly created copy. That's two objects you own at the moment.
This also assigns your copied object to the tableList property. Let's look at the property declaration:
#property (nonatomic, copy, readwrite) NSMutableArray *tableList;
This property is declared with the copy attribute, so whenever a new value is assigned to it, another -copy method is sent to it. This third copy, however, is not owned by you—it's owned by the object.
[localList release];
That releases the original empty mutable array. Fine, but there's still the one you made in the second line floating around, unreleased. That's a memory leak.
If you actually need a mutable copy of something, you want the -mutableCopy method. (The documentation for these methods is found under NSCopying and NSMutableCopying.) However, you're never going to get a mutable version of something into a property with the copy attribute, since it will send -copy to whatever it is assigned. Your property should use the retain attribute instead of the copy attribute, and the code to initialize it should look something like this:
NSMutableArray *localList = [[NSMutableArray alloc] init];
self.tableList = localList;
[localList release];
Or, a shorter version:
self.tableList = [NSMutableArray array];
There's no need to copy anything in this situation, you're just creating a fresh object.
If u are assigning localList from another object may be that is not Mutable in that case it can through this kind of error.
I hope it will be helpful.
self.tableList = [localList mutableCopy];
Hi instead of mutableCopy i believe "strong" can also be used to tackle this problem. I had similar problem in my code as well because of using "copy" instead of "strong." So the below line:
#property (copy, nonatomic) NSMutableArray *computers;
It should be:
#property (strong, nonatomic) NSMutableArray *computers;
Hope it will be of immense help for beginners making mistakes like me.
This will resolve the issue:
NSMutableArray *localList = [[NSMutableArray alloc] init];
self.localList = [[NSMutableArray alloc]initWithArray:localList];