Objective C: What is the best way to create and use a dynamic boolean array? - objective-c

I have been struggling with the best way of creating, accessing and updating values from a dynamic boolean array for more than a week now.
#interface myDelegate : NSObject
{
NSMutableArray *aShowNote;
}
#property (nonatomic, copy) NSMutableArray *aShowNote;
This is how I have initialised my array:
NSMutableArray *aShow = [[NSMutableArray alloc] init];
for (i=0; i < c; i++)
[aShow addObject:[NSNumber numberWithBool:false]];
self.aShowNote = aShow;
This seems to work OK but I'm baffled why each element is initialised with the same address.
But then what I've discovered in my research so far is that is seems that you need to replace the object if you want to change its value:
myDelegate *appDelegate = (myDelegate *)[[UIApplication sharedApplication] delegate];
NSInteger recordIndex = 1;
NSNumber *myBoolNo = [appDelegate.aShowNote objectAtIndex:recordIndex];
BOOL showNote = ![myBoolNo boolValue];
[appDelegate.aShowNote replaceObjectAtIndex:recordIndex withObject:[NSNumber numberWithBool:showNote]];
but this approach just seems to be over complicated (and it crashes too).
Terminating app due to uncaught exception'NSInvalidArgumentException', reason: '-[__NSArrayI replaceObjectAtIndex:withObject:]: unrecognized selector sent to instance 0x5b51d00
Any pointers to improve this code (and of course to make it work) would be very gratefully received.
Thanks
Iphaaw

the problem is that copy in a property copies the assigned object. And copy creates immutable objects.
Change your property to read: #property (nonatomic, retain) NSMutableArray *aShowNote;
And I think there is not much to improve, from what I know this is the way to go if you want an NSArray with booleans.

Why not use plain C for this simple case?
BOOL *aShow = malloc(sizeof(BOOL)*c);
for (i=0 ; i<c ; i++)
aShow[i] = false;
You just have to remember to free(aShow) when you are done with it.

It is not possible to change value of a NSNumber. It not mutable class.
Then, when you ask for two same value, the same object is return.
In your array init, why you don't initialized directly the array to avoid copy problem:
aShowNote = [[NSMutableArray alloc] init];
for (i=0; i < c; i++) {
[aShowNote addObject:[NSNumber numberWithBool:false]];
}

I'm baffled why each element is initialised with the same address.
Why? NSNumbers are immutable. The runtime only needs one NSNumber object to represent FALSE.

Related

NSMutableArray always empty?

Still have some difficulties to understand Obj-C's gestion of memory and some of its concepts.
So my main class contains a NSMutableArray containing some characters and their list of weapons. It's like this :
In Main class.h
#property (nonatomic, retain) NSMutableArray *players;
In Main class.m's init
for(int i = 0 ; i < 30 ; i++)
{
[players addObject:[[PlayerInGame alloc] init:[self.tabPlayers objectAtIndex:i] :[self.tabWeapons:objectAtIndex:i]]];
}
PlayerInGame.h
#property (nonatomic, retain) NSMutableArray *weaponsList;
PlayerInGame.m
- (id) init : (Player*) player : (Weapon*) weapon
{
[self.weaponsList addObject:weapon];
// Then I try NSLog of weaponsList's count.
}
Here weaponsList is always empty. What is wrong?
The other answers are right. On any other language if you reference a unallocated object you will get a NullPointerException. But in objective C the method sent to nil just returns 0 and it won't crash the app.. If you want further read, read this
That is why
[self.weaponsList addObject:weapon];
didn't crash, while in java if you try to add object to a unallocated array your program will crash.. As other answers pointed out, the statement
self.weaponsList = [[[NSMutableArray alloc] init] autorelease];
alloc memory to store array, and a reference is given back to to variable weaponList. Now weaponList != nil.
You have to alloc your array before add any object in it. you can use following code in viewDidLoad method
NSMutableArray *players = [[NSMutableArray allo]init];
NSMutableArray weaponsList = [[[NSMutableArray alloc] init]
I've not seen weaponList object allocation. Do you initialize it?
self.weaponsList = [[[NSMutableArray alloc] init] autorelease];
PS: Minor advice. Method "- (id) init : (Player*) player : (Weapon*) weapon" signature will look better and being used easier if you change it as
- (id) initWithPlayer:(Player *)player weapon:(Weapon *)weapon
I aslo suggest to change a bit your init syntax and init the array with object:
- (id) initWithPlayer:(Player *)aPlayer weapon:(Weapon *)aWeapon {
self.weaponsList = [NSMutableArray arrayWithObject:aWeapon];
[self.weaponsList retain];
// remember to release inside dealloc, or use autorelease
}

Got exception when i try to replace a Mutable Array item

i got this exception:
[__NSArrayI replaceObjectAtIndex:withObject:]: unrecognized selector sent to instance
when i try to replace a specific element by another:
EDIT:
this is my whole code:
//declaring an AppDelegate instance
AppDelegate *myAppDelegate=(AppDelegate *)[[UIApplication sharedApplication]delegate];
//get the array in which we have stored all the choosed themes
NSMutableArray *aMutableArray=myAppDelegate.themesChoosed;
for (int i=0; i<[aMutableArray count]; i++) {
NSString *str=[NSString stringWithString:[aMutableArray objectAtIndex:i]];
if ([str isEqualToString:#"B1"]) {
[aMutableArray replaceObjectAtIndex:i withObject:#"B2"];
}
}
I maked sure that the B1 element does exist in the array.
What is happening to your NSMuatbleArray before you get into the for loop?
Is it a property? If so, what is the property declaration? Did you use copy?
If you implement a property like this:
#property (nonatomic, copy) NSMutableArray *myArray;
...then you can run into problems like this because the synthesized setter sends copy to the array, which results in an immutable copy. If this is the case, you need to implement your own setter that calls mutableCopy on the array (or just use retain instead and design your code a little differently).
EDIT:
Based on your comments below and the updated code, I'm sure the problem must be something to do with the array on the app delegate not being mutable.
Try this:
NSMutableArray *mutableThemeseChoosed = [NSMutableArray arrayWithArray:myAppDelegate.themesChoosed];
I just tried your code and it works fine.
NSMutableArray *aMutableArray = [NSMutableArray arrayWithObjects:[NSString stringWithString:#"A1"],[NSString stringWithString:#"B1"],[NSString stringWithString:#"B2"],[NSString stringWithString:#"A1"],[NSString stringWithString:#"A2"],[NSString stringWithString:#"A1"],[NSString stringWithString:#"A1"], nil];
NSLog(#"%#",aMutableArray);
for (int i=0; i<[aMutableArray count]; i++) {
NSString *str=[NSString stringWithString:[aMutableArray objectAtIndex:i]];
if ([str isEqualToString:#"B1"]) {
[aMutableArray replaceObjectAtIndex:i withObject:#"B2"];
}
}
NSLog(#"%#",aMutableArray);
Your problem is - as the error tells - that your mutable array is a NSArray (which is not mutable)
What you get out of myAppDelegate.themesChoosed; is likely a NSArray. Try the following: NSMutableArray *aMutableArray= [NSMutableArray arrayWithArray:myAppDelegate.themesChoosed];

NSMutableDictionary error

I want to use NSMutableDictionary to cache some data i will use later. My custom object is following:
#interface MyData : NSObject {
NSRange range;
NSMutableArray *values;
}
#property (nonatomic, retain) NSMutableArray *values;
and implement:
- (id)init {
if (self = [super init]) {
values = [[NSMutableArray alloc] init];
}
return self;
}
and when i wanna cache it, i use it like this:
NSMutableDictionary *cache = [[NSMutableDictionary alloc] init];
NSString *key = #"KEY";
MyData *data = [[MyData alloc] init];
// save some data into data
[data.values addObject:"DATA1"];
[data.values addObject:"DATA2"];
//... ...
[cache setObject:data forKey:key];
My questions is the count of cache.values is zero when i retrieve this object later as follow:
[cache objectForKey:#"KEY"];
i can retrieve "data" and the object's memory address is the same as the address when i put it into cache.
what's wrong? i need some kind guys help, any info is helpful. thanks
As Carl Norum pointed out, you're passing C strings to addObject:. addObject:, as its name suggests, requires a pointer to a Cocoa object; a C string is a pointer to characters. You need to pass NSString objects there; for literal strings, this simply requires prefixing them with #: "Fred" is a constant C string, whereas #"Fred" is a constant NSString object.
Is cache an instance variable? It looks like it's not; it appears to be a local variable, which means you're creating a new dictionary object every time. That's why there's nothing you've added previously (to previous dictionaries) in the new one. It also means you're leaking those previous dictionaries, since you're not releasing them (not in the code you showed, anyway).
Make cache an instance variable and only create the dictionary when you don't already have one (i.e., when cache == nil). Creating the dictionary in your init method is one good way. And make sure you manage its lifetime appropriately, so you don't leak and/or crash.
First of all your objects your adding don't look right it should have an # before the string. Like #"DATA1"
Second when you add an object to a dictionary or an array it does not make an actual copy of it. It just creates a pointer to it so if those objects are destroyed or moved somewhere also they are also gone out of your dictionary. A better way to make a cache of your values would be to copy the objects like so:
MyData* cache = [[MyData alloc] init];
for (int i = 0; i < [data.values count]; i ++){{
[cache.values addObject:[NSString stringWithString:[data.values objectAtIndex:i]]];
}
Don't use a dictionary in this situation.

Objective C /iPhone : Is it possible to re initialize an NSArray?

I read that non mutable data types can't be modified once created.(eg NSString or NSArray).
But can they be re-initialized to point to a different set of objects?
If so, do I use release to free any alloc from first time round in between uses? eg:
myArray declared as NSArray *myArray in interface, and as nonatomic/retain property.myArray set in initialization code to a point to an array of strings as follows.
self.myArray = [myString componentsSeparatedByString:#","];
But later I want to re-initialize myArray to point to a different set of strings
self.myArray = [myOtherString componentsSeparatedByString:#","];
Is it possible? Thanks...
It really depends what you mean with re-initialize. You can assign another immutable object to a pointer, because the pointers aren't constant.
Example:
#interface MyObj : NSObject {
NSString *name; // not needed in 64bit runtime AFAIK
}
#property(retain) NSString *name; // sane people use copy instead of retain
// whenever possible. Using retain can
// lead to some hard to find errors.
#end
/* ... another file ... */
MyObj *theObject = [[[MyObj alloc] init] autorelease];
theObject.name = #"Peter";
NSString *oldName = theObject.name;
NSLog(#"%#", theObject.name); // -> Peter
NSLog(#"%#", oldName); // -> Peter
theObject.name = #"Martin";
NSLog(#"%#", theObject.name) // -> Martin
NSLog(#"%#", oldName) // -> Peter
If the behavior above is what you want, that's fine.
If you want that last line to return Martin you're in trouble. Those are constant strings and are not meant to be modified. You could, if you really want, modify the memory of the object directly, but this is dangerous and not recommended. Use mutable objects if you need such behaviour.
Yes you can reinitialized the NSArray. Here is the sample code that i used to re-initialized the NSArray.
NSString *keywords = #"FirstName|LastName|Address|PhoneNumber";
NSArray *arr = [keywords componentsSeparatedByString:#"|"];
NSLog(#"First Init - %#,%#,%#,%#",[arr objectAtIndex:0],[arr objectAtIndex:1],
[arr objectAtIndex:2],[arr objectAtIndex:3]);
arr = nil;
keywords = #"First_Name|Last_Name|_Address|_PhoneNumber";
arr = [keywords componentsSeparatedByString:#"|"];
NSLog(#"Second Init - %#,%#,%#,%#",[arr objectAtIndex:0],[arr objectAtIndex:1],
[arr objectAtIndex:2],[arr objectAtIndex:3]);
Of course they can. Saying that an NSArray is immutable doesn't mean that an attribute of a class of that type cannot be changed. You can't change the content, but you can assign new content to it.
If you want to make also changing the reference impossible you should use const keyword.

Array of pointers causes leaks

-(void)setUserFilters{
//init the user filters array
userFilters = [[NSMutableArray alloc] init];
SearchCriteria *tmpSc= [[SearchCriteria alloc] init];
for(int i=0;i<[searchFilters count];i++)
{
tmpSc=[self.searchFilters objectAtIndex:i];
if(tmpSc.enabled==TRUE)
[userFilters addObject:tmpSc];
}
}
searchFilters is a list of filters that can be setted to true or false and I use userFilters to populate a table view with the filters that are only setted to TRUE
But the line SearchCriteria *tmpSc= [[SearchCriteria alloc] init]; causes leaks, and I don't know how to solve because if I release at the end of the function I loose my pointers and it crashes
Any ideas?
twolfe18 has made the code >much slower if searchFilters can be large. -objectAtIndex: is not a fast operation on large arrays, so you shouldn't do it more than you have to. (While true that FE is faster than objectAtIndex:, this overstated the issue and so I've striken it; see my other comments on the advantages of Fast Enumeration.)
There are a number of problems in your code:
Never create a method that begins "set" but is not an accessor. This can lead to very surprising bugs because of how Objective-C provides Key-Value Compliance. Names matter. A property named userFilters should have a getter called -userFilters and a setter called -setUserFilters:. The setter should take the same type that the getter returns. So this method is better called -updateUserFilters to avoid this issue (and to more correctly indicate what it does).
Always use accessors. They will save you all kinds of memory management problems. Your current code will leak the entire array if -setUserFilters is called twice.
Both comments are correct that you don't need to allocate a temporary here. In fact, your best solution is to use Fast Enumeration, which is both very fast and very memory efficient (and the easiest to code).
Pulling it all together, here's what you want to be doing (at least one way to do it, there are many other good solutions, but this one is very simple to understand):
#interface MyObject ()
#property (nonatomic, readwrite, retain) NSMutableArray *userFilters;
#property (nonatomic, readwrite, retain) NSMutableArray *searchFilters;
#end
#implementation MyObject
#synthesize userFilters;
#synthesize searchFilters;
- (void)dealloc
{
[searchFilters release];
serachFilters = nil;
[userFilters release];
userFilters = nil;
[super dealloc];
}
- (void)updateUserFilters
{
//init the user filters array
// The accessor will retain for us and will release the old value if
// we're called a second time
self.userFilters = [NSMutableArray array];
// This is Fast Enumeration
for (SearchCriteria *sc in self.searchFilters)
{
if(sc.enabled)
{
[self.userFilters addObject:sc];
}
}
}
It seems that your initially creating a SearchCriteria object and before you use it or release it your reassigning the variable to another object from self.searchFilters. So you don't need to create the initial object and why it's leaking and not being released.
Try:
SearchCriteria *tmpSc = nil;
Hope that helps.
first of all, the worst n00b code you can write involves if(condition==true) do_something(), just write if(condition) do_something().
second, there is no reason to have tempSc at all (never mind alloc memory for it), you can just do the following:
-(void)setUserFilters{
//init the user filters array
userFilters = [[NSMutableArray alloc] init];
for(int i=0;i<[searchFilters count];i++)
{
if([self.searchFilters objectAtIndex:i].enabled)
[userFilters addObject:[self.searchFilters objectAtIndex:i]];
}
}