Passing immutable array to mutable property - objective-c

In my prepareForSegue method, I pass an immutable array retrieved from NSUserDefaults to a DetailViewController mutable dictionary property. Do I need to create a mutable copy of the array before I modify it or does that happen automatically in the NSMutableDictionary class setter method?
My code...
ViewController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"EditReminder"])
{
UINavigationController *navigationController = segue.destinationViewController;
DetailViewController *detailViewController = [[navigationController viewControllers] objectAtIndex:0];
detailViewController.delegate = self;
[detailViewController setTitle:#"Edit Reminder"];
// Pass ReminderData to detailVC if editing
NSIndexPath *selectedRowIndex = [self.tableView indexPathForSelectedRow];
NSArray *remindersArray = [[NSUserDefaults standardUserDefaults] objectForKey:#"reminders"];
detailViewController.reminderData = [remindersArray objectAtIndex: selectedRowIndex.row];
}
}
DetailViewController.h
#property (strong, nonatomic) NSMutableDictionary *reminderData;
DetailViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
if (self.reminderData) {
// Reminder data from user defaults is immutable. Create mutable copy.
// Is this necessary?
self.reminderData = [self.reminderData mutableCopy];
}
else {
self.reminderData = [[NSMutableDictionary alloc] init];
}
}

You need to create the mutable copy, but your implementation is wrong.
In prepareForSegue, you need to do the mutableCopy, or you already have a wrong object stored in your property. There is no reason to do that in viewDidLoad, and it can be considered a bug.

Yes, you definitely need to create a mutable copy as you've illustrated here. The compiler might not complain if you assign an instance of NSDictionary (immutable) to an NSMutableDictionary-valued property, but calling any of the mutating methods on the stored object will cause a runtime exception. There's no magic in the language or framework that makes an immutable collection assigned to a mutable-typed variable automatically mutable.
Also, as noted in another answer, you should perform the mutable copy when you assign the property for the first time (in prepareForSegue), rather than at a later time.

Related

(iOS) NSMutableArray as property addObject does nothing

I have a subclass of UIViewController with an NSMutableArray as a property to use as the data source for a UITableView. I create an instance of the class in my storyboard.
I want to populate the array with the addObject: method but if I try, the array always returns (null).
I read that #synthesize doesn't init the array and I might need to override -init and init the NSMutableArray there but -init never gets called.
How is this supposed to work?
You need to create an instance of NSMutableArray and assign it to the property.
Since the object with the property is a UIViewController created in a storyboard, you can do it in a few different places. You can override initWithCoder:, or awakeFromNib, or viewDidLoad.
If you override initWithCoder:, it is imperative that you call the super method.
If you do it in viewDidLoad, the array won't be created until the view is loaded, which doesn't have to happen right away.
I recommend doing it in awakeFromNib:
#synthesize myArray = _myArray;
- (void)awakeFromNib {
_myArray = [[NSMutableArray alloc] init];
}
Another option is to just create the array lazily by overriding the getter method of the property:
#synthesize myArray = _myArray;
- (NSMutableArray *)myArray {
if (!_myArray)
_myArray = [[NSMutableArray alloc] init];
return _myArray;
}
If you do this, it is very important that you always access the array using the getter method (self.myArray or [self myArray]) and never by accessing the instance variable (_myArray) directly.
Here's what your code will need:
#interface BlahBlah : UIViewController
#property (...) NSMutableArray *myArray;
#end
At first, *myArray is just a pointer that's equal to nil. You can't use this as an array yet. It needs to be set to a valid NSMutableArray instance. You need to do this in the designated initializer:
#implementation BlahBlah
#synthesize myArray;
// -initWithNibName:bundle: is the designated initializer for UIViewController
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle
{
self = [super initWithNibName:nibName bundle:nibBundle];
if (self) {
myArray = [[NSMutableArray alloc] init];
// now you can add objects to myArray
}
return self;
}
- (void)dealloc
{
[myArray release];
[super dealloc];
}
Note that you can't just override -init; you must override the designated initializer for your class. Figure out what subclass you are implementing, find out what its designated initializer is, override that (but call the superclass implementation as is often necessary), then init your properties.
Here's Apple documentation regarding multiple/designated initializers.
#synthesis neither do alloc. So alloc and init your array. Then it should work.

NSMutableArray as instance variable alway null

After many hours wasted, I officially turn to the experts for help!
My problem lies with using a NSMutableArray as an instance variable, and trying to both add objects and return the array in a method in my class. I am obviously doing something fundamentally wrong and would be grateful for help...I have already tried all the suggestions from other similar questions on stackoverflow, read apples documentation, and basically all combinations of trial and error coding I can think of. The mutable array just alway returns (null). I've even tried creating properties for them, but still the array returns (null) and then I also am running into memory management problems due to the retain while setting the property, and the init in the init method for the class.
Here is what I am trying to do:
1) Loop through a series of UISwitches and if they are 'switched on', add a string to the NSMutableArray
2) Assign this mutable array to another array in another method
Any help much appreciated,
Andy
And for some code...
fruitsViewController.h
#import <UIKit/UIKit.h>
#interface fruitsViewController : UIViewController
{
NSMutableArray *fruitsArr;
UISwitch *appleSwitch;
UISwitch *orangeSwitch;
}
#property (nonatomic,retain) NSMutableArray *fruitsArr; // ADDED ON EDIT
#property (nonatomic,retain) IBOutlet UISwitch *appleSwitch;
#property (nonatomic,retain) IBOutlet UISwitch *orangeSwitch;
- (IBAction)submitButtonPressed:(id)sender;
#end
fruitsViewController.m
#import "fruitsViewController.h"
#implementation fruitsViewController
#synthesize fruitsArr; // ADDED ON EDIT
#synthesize appleSwitch, orangeSwitch;
/* COMMENTED OUT ON EDIT
-(id)init
{
if (self = [super init]) {
// Allocate memory and initialize the fruits mutable array
fruitsArr = [[NSMutableArray alloc] init];
}
return self;
}
*/
// VIEW DID LOAD ADDED ON EDIT
- (void)viewDidLoad
{
self.fruitsArr = [[NSMutableArray alloc] init];
}
- (void)viewDidUnload
{
self.fruitsArr = nil;
self.appleSwitch = nil;
self.orangeSwitch = nil;
}
- (void)dealloc
{
[fruitsArr release];
[appleSwitch release];
[orangeSwitch release];
[super dealloc];
}
- (IBAction)submitButtonPressed:(id)sender
{
if ([self.appleSwitch isOn]) {
[self.fruitsArr addObject:#"Apple"; // 'self.' ADDED ON EDIT
}
if ([self.orangeSwitch isOn]) {
[self.fruitsArr addObject:#"Orange"; // 'self.' ADDED ON EDIT
}
NSLog(#"%#",self.fruitsArr); // Why is this returning (null) even if the switches are on?!
[fruitsArr addObject:#"Hello World";
NSLog(#"%#",self.fruitsArr); // Even trying to add an object outside the if statement returns (null)
}
#end
It seems like your init function is never called. If you're initializing this view controller from a NIB, you need to use initWithCoder. If not, just declare your fruitsArr in viewDidLoad.
Use view did load instead of init...
- (void)viewDidLoad
{
fruitsArr = [[NSMutableArray alloc] init];
}
Change that init for viewDidLoad and see what happens
Is your init method ever being called (in complicationsViewController). Add a NSLog to check this, you might be calling initWithNib: maybe.
At viewDidUnload you should remove self.fruitsArr = nil;, or, if you want to keep it, then initialize the fruitsArr in viewDidLoad (and remove it from init).
because fruitsArr don't be init.
you should do this first:
fruitsArr = [[NSMutableArray alloc] init];
so, I think you don't run - (id)init before you use fruitsArr.

I wonder about releasing variables

UIView *view; //1
UISegmentedControl *scopeBar; //2
NSMutableArray *array; //3
#property (nonatomic, retain) IBOutlet UIView *view;
#property (nonatomic, retain) UISegmentedControl *scopeBar;
#property (nonatomic, retain) NSMutableArray *array;
.m
#synthesize view, scopeBar, array;
for (id subView in [view subviews]) {
if ([subView isMemberOfClass:[UISegmentedControl class]]) {
scopeBar = (UISegmentedControl *)subView;
}
}
array = [[NSMutableArray alloc] init];
- (void)dealloc {
}
I think that only the third of the variables has to be released in the dealloc method.
Is that right?
Yes, (array needs to be released) because you alloc it. So, it's programmer's responsibility to release it. So -
- (void)dealloc {
[ array release ] ;
// Any other resources alloc, init, new should be released
}
For more info on what to release, Memory management - ObjectiveC
And I think you will find good suggestions in this question about your query
Why should we release?
Contrary to some of the answers, you have to release your outlet (view) as well, and not only in the dealloc but also in the viewDidUnload, the easiest way is to set it to nil :
self.view = nil;
Also note that if you don't access your properties but your instance variables (i.e. without self. prefix) your retain attribute won't help you and you are not retaining the object. That means that as soon as scopeBar would be removed out of the subViews of the view, it will be released and you end up accessing a zombie.
As a rule of thumb, it's best to use the properties accessor everywhere except in the init methods so that you don't have to deal with the memory management explicitly. Setting them to nil in the dealloc and viewDidUnload in case of outlets should be enough then.
Also, don't do what Jenifer suggested and once you've called a release on a variable, don't set the property to nil, that would overrelease it.
I think that only the third of the variables has to be released in the dealloc method. Is that right?
// no. your dealloc should look like this:
- (void)dealloc {
// note: *not* using accessors in dealloc
[view release], view = nil;
[scopeBar release], scopeBar = nil;
[array release], array = nil;
[super dealloc];
}
// your assignment of `scopeBar` should look like this:
...
self.scopeBar = (UISegmentedControl *)subView;
...
// you want to retain the view, as advertised.
// consider avoiding an ivar if you can easily access it.
// your assignment of `view` should look like this:
...
self.view = theView;
...
// you want to retain the view, as advertised.
// consider avoiding an ivar if you can easily access it.
// your assignment of `array` should look like this in your initializer:
// note: *not* using accessors in initializer
...
// identical to `array = [[NSMutableArray alloc] init];`
array = [NSMutableArray new];
...
// and the assignment of `array` should look like this in other areas:
...
self.array = [NSMutableArray array];
...
// you're likely to be best suited to declare your array as
// follows (assuming you really need a mutable array):
...
NSMutableArray *array; // << the declaration of the ivar
...
...
// the declaration of the public accessors.
// note the array is copied, and passed/returned as NSArray
#property (nonatomic, copy) NSArray *array;
...
// finally, the implementation manual of the properties:
- (NSArray *)array {
// copy+autorelease is optional, but a good safety measure
return [[array copy] autorelease];
}
- (void)setArray:(NSArray *)arg {
NSMutableArray * cp = [arg mutableCopy];
// lock? notify?
NSMutableArray * prev = array;
array = cp;
[prev release], prev = nil;
// unlock? notify? update?
}
other answers assume that dangling pointers (e.g., you still hold a pointer to view, although the view may have changed behind your back) are allowable.
they should not be allowed in real programs. they are extremely dangerous, and it can very difficult to reproduce errors they cause. therefore, you must ensure you own a reference to the pointers you maintain/hold.
you should also use the accessors in the public interface for the subclasser's sake - in case they override them. if you don't want to allow/support that, consider simply using a private variable.
As i think you should release and set them nil because you have made them properties so do this:-
in your dealloc
[array release];
self.array=nil;
self.scopeBar=nil;
self.view=nil;

array retain question

im fairly new to objective-c, most of it is clear however when it comes to memory managment I fall a little short. Currently what my application does is during a NSURLConnection when the method -(void)connectionDidFinishLoading:(NSURLConnection *)connection is called upon I enter a method to parse some data, put it into an array, and return that array. However I'm not sure if this is the best way to do so since I don't release the array from memory within the custom method (method1, see the attached code)
Below is a small script to better show what im doing
.h file
#import <UIKit/UIKit.h>
#interface memoryRetainTestViewController : UIViewController {
NSArray *mainArray;
}
#property (nonatomic, retain) NSArray *mainArray;
#end
.m file
#import "memoryRetainTestViewController.h"
#implementation memoryRetainTestViewController
#synthesize mainArray;
// this would be the parsing method
-(NSArray*)method1
{
// ???: by not release this, is that bad. Or does it get released with mainArray
NSArray *newArray = [[NSArray alloc] init];
newArray = [NSArray arrayWithObjects:#"apple",#"orange", #"grapes", "peach", nil];
return newArray;
}
// this method is actually
// -(void)connectionDidFinishLoading:(NSURLConnection *)connection
-(void)method2
{
mainArray = [self method1];
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
mainArray = nil;
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[mainArray release];
[super dealloc];
}
#end
Your -method1 first creates a new array and then overwrites it with a new one:
NSArray *newArray = [[NSArray alloc] init]; // first array, retained
newArray = [NSArray arrayWithObjects:...]; // second array, auto-released,
// pointer to first one lost
The first array is simply leaked here. You are also leaking the array stored in the ivar, just use the synthesized setter to avoid that - it retains and releases for you.
If you haven't done so yet, read the Memory Management Guide for Cocoa.
A better version:
- (NSArray *)method1 {
NSArray *newArray = [NSArray arrayWithObjects:...];
return newArray;
}
- (void)method2 {
self.mainArray = [self method1];
}
Yes, your newArray is released when mainArray is released. But this just if method2 is called once.
We're talking about references so if you have
newArray = something
mainArray = newArray
[mainArray release]
both variables will be referencing to just a NSArray*. Then in your case newArray is just a local so there's no problem.
The problem occurs if you call method2 twice:
newArray = something
mainArray = newArray
newArray = something2
mainArray = newArray <- old reference is lost
[mainArray release] <- just something2 is released
To avoid this issue you should make sure to release mainArray before overwriting the reference with another object.
EDIT: didn't noticed that you were creating the array twice :) No, that's not good..

'initializing' a property which is retained

In the iPhone objective-c world, I've seen this pattern everywhere and I use it myself all the time without really understanding what is going on:
In Test.h
#interface Test: UIViewController
{
NSMutableArray *testArray;
}
#property (retain, nonatomic) NSMutableArray *testArray;
And in Test.m
#implementation Test
#synthesize testArray
- (void) viewDidLoad
{
// why do we do this?
NSMutableArray *init = [[NSMutableArray alloc] init];
self.testArray = init;
[init release];
[self.testArray addObject: #"A"]; // why can't I do this directly?
...
}
- (void) dealloc
{
[testArray release];
[super dealloc];
}
My question is: if testArray has a retain on it when it's declared in the property, why do we need to create a new NSMutableArray init object, assign that to testArray and release? Why can't I just start using testArray in viewDidLoad without doing anything else?
I know there's some debate over the best way of doing this (creating a new object, or using an autorelease object), but in both cases, we end up with testArray with a retain count of 1. Which I believe the 'retain' property already gives it. So why the need to create this init object?
The 'retain' property doesn't automatically create an NSMutableArray for you. Rather, it simply indicates that whenever you do assign something to that property, it will be retained.
If your code were this:
- (void) viewDidLoad
{
[self.testArray addObject: #"A"];
}
Then self.testArray would be nil, and thus it would be essentially a no-op. Until you assign something to self.testArray, it's empty.
Here's what's going on.
- (void) viewDidLoad
{
// we need to assign an NSMutableArray to self.testArray.
NSMutableArray *init = [[NSMutableArray alloc] init];
// The array has been retained once (by the call to |alloc|)
self.testArray = init;
// The array is assigned to a property with the 'retain' attribute
// Thus, the array has now been retained twice
[init release];
// We release the array, so it now is retained once.
// We now have an array in self.testArray, so we can add something to it.
[self.testArray addObject: #"A"];
}
The "retain" in the #property directive specifies that the setter should retain the input value instead of simply copying the value. It has nothing to do with allocating (setting aside memory) and initializing (constructing the object) the object. retain on the #property directive simply increments the retain count when the setter is called (which alllows you to do something like self.myobject = something without specifically calling retain.