NSMutableArray of Objects misbehaves - iphone

I hope someone understands what happens to my NSMutableArray.
I read records a, b, c, d from a database, load the fields into an object an add the object to an array. To do this I read the records into an instance of that object (tmpEvent) and add the Object to the target array (NSMutableArray myArray).
the code looks like:
for (condition) {
tmpEvent.field1 = [NSString stringWithUTF8String:(char*)sqlite3_column_text(stmt, 0)];
tmpEvent.field2 = [NSString stringWithUTF8String:(char*)sqlite3_column_text(stmt, 1)];
tmpEvent.field3 = [NSString stringWithUTF8String:(char*)sqlite3_column_text(stmt, 2)];
NSLog(#"myArray: adding %#", tmpEvent.field1);
[myArray addObject:tmpEvent];
}
The NSLog shows
myArray: adding a
myArray: adding b
myArray: adding c
myArray: adding d
Subsequent I enumerate the array (this can be in the same or a different method):
for (myObject *records in myArray) {
NSLog(#"iEvents value %#", records.field1);
}
The NSLog now shows:
myArray value d
myArray value d
myArray value d
myArray value d
a mystery .... ??? any thoughts?

You need to allocate a new "iEvent" for each event. tmpEvent.field1 is pointing to the same place in memory for each subsequent add, therefore you are modifying the object that is already stored in the array. NSArray does not make a new copy of the object, just stores its pointer/address.
One fix:
[myArray addObject:[tmpEvent copy]];
This assumes the members of the iEvent class conform to NSCopy.
Another is to allocate a new tmpEvent for each event that you want to store.
Question: But does this mean that when I free my array agein I need to enumerate through and "manually" release the objects again?
Answer: You should send the object a release after adding it to the array as the array sends it a retain. When you dispose of the array, all objects are sent a release so you don't have to. See below...
Give the alloc option a try as conforming to NSCopying requires you to alloc an object anyway:
for (condition) {
tmpEvent = [[TmpEvent alloc] init]; // Or however it is initialized
tmpEvent.field1 = [NSString stringWithUTF8String:(char*)sqlite3_column_text(stmt, 0)];
tmpEvent.field2 = ...
[myArray addObject:tmpEvent];
// NSArray retains objects that you add.
[tmpEvent release];
}
---- To Conform to NSCopy ---
1. Class must inherit from NSCopying
#interface TmpEvent : NSObject ...
Implement - (id) copyWithZone:(NSZone *) zone
(id)copyWithZone:(NSZone *)zone {
TmpEvent *copyOfMyself = [[TmpEvent alloc] init];
copyOfMyself.field1 = [self.field1 copy];
.... etc.
return copyOfMyself;
}

Related

iOS - Passing a NSMutableArray via method return

How would one pass a NSMutableArray via method return.
I have it passing the array "spaces" so an array of 10 objects passes the 10 blocks but none of the information contained in those objects.
Thanks in advance
Edit: Basically I created another class that contains path information because my controller was getting a bit cluttered. So this new class I want call the "create" method which returns an NSMutableArray. The array is created fine in the path class but when the return statement fires it only passes the spaces and not the values or even a pointer.
currently it's
return path;
I've tried
return &path;
and that fails epically.
Edit2: Here is the issue I'm having unfortunately.
Still crashing
calling
newNode = [newNode copy];
causes a crash
- (NSMutableArray *) mutableFloobizwits {
NSMutableArray *array = [NSMutableArray array];
for (NSInteger i = 0; i < TheAnswerToTheUltimateQuestion; ++i) {
void(^MyBlock)(void) = ^{
NSLog(#"captured i: %ld", i);
};
MyBlock = [MyBlock copy]; //move the block from off the stack and onto the heap
[array addObject:[Floobizwit floobizwithWithBlock:MyBlock]];
[MyBlock release]; //the Floobizwit should've -retained the block, so we release it
}
return array;
}
I would set up your other class that returns the array of path objects as follows:
#implementation PathFactory
- (NSMutableArray*) create
{
// In your PathFactory object you create an array and make it autorelease so
// it becomes the callers responsibility to free the memory
NSMutableArray * pathArray = [[[NSMutableArray alloc] init] autorelease];
// Create a bunch of PathObject objects and add them to the mutable array
// also set these to autorelease because the NSMutableArray will retain objects
// added to the collection (ie It is the NSMutableArray's responsibility to ensure
// the objects remain allocated).
for (int i = 0; i < numberOfPaths; i++)
[pathArray addObject:[[[PathObject alloc] init] autorelease]];
// Just return the pointer to the NSMutableArray. The caller will need to
// call the retain message on the pointer it gets back (see next)
return pathArray;
}
#end
So in your caller code:
// create a tempory PathFactory (autorelease will make sure it is cleaned up when we
// are finished here)
PathFactory * newPathFactory = [[[PathFactory alloc] init] autorelease];
// grab the new array of Path objects and retain the memory. _newPathArray
// is a member of this class that you will need to release later.
_newPathArray = [[newPathFactory create] retain];

how can I save NSMutableArray into NSMutableArray so that previous data in NSMutableArray remain same?

I want to know that how can I add NSMutableArray in to an NSMutableArray so that previous data should not lost, and new data will be added on next indexes.
If you don't understand it then you can ask again to me,
I will appraise the right answer.
my code is as below
-(void)setArray1:(NSMutableArray *)arrayValueFromNew
{
self.myArray=arrayValueFromNew;
myArray2 = [[NSMutableArray alloc] initWithArray:arrayValueFromNew];
for(int i=0;i<[myArray2 count];i++)
{
[myArray addObject:[myArray2 objectAtIndex:i]];
}
}
In your code, myArray and myArray2, both have same objects as you are assigning the arrayValueFromNew array to both. So it kind of doesn't make sense.
But to answer your question 'how to add one array to another?' do :
[mutableArray1
addObjectsFromArray:array2];
EDIT:
this is how your method should look
-(void)setArray1:(NSMutableArray *)arrayValueFromNew
{
if(!self.myArray)
{
self.myArray = arrayValueFromNew;
}
else
{
[self.myArray addObjectsFromArray:arrayValueFromNew];
}
}
Your 'myArray must be initialized. You can initialize it in viewDidLoad or init:
self.myArray = [[NSMutableArray alloc]
initWithCapacity:1];
NSMutableArray *array1 = [NSMutableArray array], *array2 = [NSMutableArray array];
// add some objects to the arrays
[array1 addObjectsFromArray:array2];
//array1 now contains all the objects originally in array1 and array2
This will work,
NSMutableArray *mutarr=[[NSMutableArray alloc]initWithArray: array1]
It looks like you just want a new copy of the old array. There is a handy function for that
NSMutableArray *newArray = [oldArray mutableCopy];
Remember that you've used copy in getting this array so you are responsible for managing the memory of newArray
EDIT
What is your code doing?
-(void)setArray1:(NSMutableArray *)arrayValueFromNew //1
{
self.myArray=arrayValueFromNew; //2
myArray2 = [[NSMutableArray alloc] initWithArray:arrayValueFromNew]; //3
for(int i=0;i<[myArray2 count];i++)
{
[myArray addObject:[myArray2 objectAtIndex:i]]; //4
}
}
This looks like a setter for a property array1
You are setting the property 'array' to arrayValueFromNew. Since I don't know whether this property has been declared with retain or copy I don't know whether array is a pointer to arrayValueFromNew or a pointer to a copy of arrayValueFromNew
You set myArray2 to be a new array that contains the objects of arrayValueFromNew
For each object in myArray2 (which are the objects from arrayValueFromNew. see point 3) you add this object to myArray. Assuming myArray is an NSMutableArray it started with the objects from arrayValueFromNew which you have now added again. It contains each item in arrayValueFromNew twice.

how to create NSMutableArray dynamically based on certain condition in iphone

I have a array of dates, so based on certain condition of date I need to create a NSmutablearray and add some values to that particular array. so number of sections in my tableview depends on this mutablearray count(which is internally depends on condition of date).
for example:
if(date1)
1.create array1
2.add x,y,z values to the array1.
if(date2)
1.create array2
2.add xx,yy,zz values to the array2.
....
thanks..
NSMutableArray *a = [NSMutableArray array];
if (b) {
[a addObject: ... some object based on "b" being true ...];
... etc ...
} else if (c) {
[a addObject: ... some object based on "c" being true ...];
... etc ...
} else {
[a addObject: ... some object based on neither "c" nor "b" being true ...];
... etc ...
}
[Responding to LordT's comment]
"Don't" is a strong word considering that
NSMutableArray *aMutableArray = [NSMutableArray array]; // compiles but invokes +array in NSMutableArray's parent class since +array is not declared in NSMutableArray. What you get if you inspect the object returned by the message expression is an NSArray.
If you really want to use +array but not NSMutableArray class method +arrayWithCapacity: , perhaps
NSMutableArray *aMutableArray = [[NSMutableArray array] mutableCopy];
will suffice? Just don't forget to memory manage. 8)

IPhone - copyWithZone leak

Testing my app on the device it returns a leak whe i call the copy of a custom object ande i can't understand why.
this is the call:
NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:5];
for (SinglePart *sp in [copyFrom partList]) {
[arr addObject:[sp copy]];
}
self.partList = arr;
[arr release];
this is the method:
- (id)copyWithZone:(NSZone *)zone {
SinglePart *copy = [[[self class] allocWithZone:zone] initWithSinglePart:self];
[copy loadImage];
return copy;
}
this is the method that is called by copyWithZone:
- (id)initWithSinglePart:(SinglePart *)copyFrom {
if (self = [super init]) {
self.imagePath = [copyFrom.imagePath copy];
self.color = [UIColor colorWithCGColor:copyFrom.color.CGColor];
self.hasOwnColor = copyFrom.hasOwnColor;
self.blendingMode = copyFrom.blendingMode;
}
return self;
}
copy returns a new object with retain count 1. Meaning you need to release the new object, which you are not doing.
NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:5];
for (SinglePart *sp in [copyFrom partList]) {
SingPart *theCopy = [sp copy];
[arr addObject:theCopy];
[theCopy release];
}
self.partList = arr;
[arr release];
Even your custom copyWithZone: method inits an object, but does not autorelease it, which is the expected behavior of a copy method. Copy must be balanced just like a retain or init, meaning you must balance it with release at some point.
Lastly, your initWithSinglePart: method leaks the imagePath as well. In this case if you declare the imagePath property as copy instead of retain then you don't need to do this manually at all. Then you simply assign the value and let the property setter do it for you.
// Header
#property (copy) NSString *imagePath;
// Now this will do the copy for you
self.imagePath = copyFrom.imagePath;
Also, is the property imagePath defined with retain or copy semantics?
If so you need to add an autorelease here:
self.imagePath = [[copyFrom.imagePath copy] autorelease];
because the default setter will retain/copy it too.
So, you either need to autorelease, or omit the "self." to bypass the default setter.
You are making a copy of sp and then adding it to the array. The array then retains the object so your retain count is now 2.
In the end you release arr, thus making the retain count of it's items 1.
You should either add another release to the sp objects, or not use copy.
Try this:
self.partList = [NSMutableArray arrayWithCapacity:5];
for (SinglePart *sp in [copyFrom partList]) {
[arr addObject:sp];
}

how to detect if an array isnt empty?

I am trying to detect if an array isn't empty in order to be able to do a certain call.
I tried using if (![array ==nil]) however that doesn't compile.
I'm sure there is a really easy explanation to this.
Update
If array is empty I want to do this:
array = [[NSMutableArray alloc]init];
If it has an object I want to do this:
array = [[userDefaults arrayForKey:#"MyFavorites"] mutableCopy];
If you declared it but did not assign anything to it at all:
NSMutableArray *array;
Then the array will be nil, meaning it isn't there at all so you can't say if it's empty or not, so you can't check anything.
If you did assign something to it, and you want to find out if the existing array is empty or not, that would depend on how you created it first.
If the array was assigned from some convenience method, it's autoreleased, so just do this:
if ([array count] == 0) {
array = [[NSMutableArray alloc] init];
} else {
array = [[userDefaults arrayForKey:#"MyFavorites"] mutableCopy];
}
If the array was assigned from an init or copy method, or it was retained previously, store the count in a temporary variable, release the array and use the temporary variable to decide what to do:
NSInteger count = [array count];
[array release];
if (count == 0) {
array = [[NSMutableArray alloc] init];
} else {
array = [[userDefaults arrayForKey:#"MyFavorites"] mutableCopy];
}
In your case I'd always use without differentation
array = [[userDefaults arrayForKey:#"MyFavorites"] mutableCopy];
and set the default value in the user defaults to an empty array right away at program start before accessing the defaults (from Apple's example):
+ (void)initialize{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *appDefaults = [NSDictionary
dictionaryWithObject:[NSArray array] forKey:#"MyFavorites"];
[defaults registerDefaults:appDefaults];
}
See Apple's doc on this.
Supposing you are talking about NSArray, if myArray has not been properly alloced+initialized (what you are trying to check) its reference will be nil, so you can do:
if(myArray) //or even if(myArray != nil) since myArray will be a pointer
{
//properly inizialized
}
else
{
//not properly inited
}
If it's been inited on the other hand, you can test its emptiness by checking the count property which returns the number of elements it contains
if([myArray > 0])
//there is at least one element
}
else
{
//no elements
}
you can use count function of NSArray. it will work on NSMutableArray too....
syntext will be,
int ct=[array count];
ct will have number of items in array.
if it us empty it will be Zero