I need to check specific positions in an NSArray to see if they have already been initialized, but I am having trouble. I tried to do the following, but it causes my application to crash!
if ((NSMutableArray *)[arrAllBlocks objectAtIndex:iLine] == nil)
{
[arrAllBlocks insertObject:[[NSMutableArray alloc] init] atIndex:iLine];
}
NSMutableArray *columArray = (NSMutableArray *)[arrAllBlocks
objectAtIndex:iLine];
[columArray insertObject:newBlock atIndex:iColumn];
What is the best to do this? I already tried some methods like isValid, and things like that!
You have a few options here:
Option 1: Pre-fill the array with instances of NSNull, and then use the code given by Dave DeLong in his answer.
Option 2: (Similar to #1) pre-fill the array with instances of NSMutableArray, and then have no extra code at all. (If you're going to pre-fill, you may as well do this).
Option 3: Do not pre-fill the array, but insert items dynamically as required. This will be almost identical to a pre-fill if the first iLine is near the maximum:
while([arrAllBlocks count] <= iLine)
{
[arrAllBlocks addObject:[NSMutableArray arrayWithCapacity:0]];
}
NSMutableArray *columArray = (NSMutableArray *)[arrAllBlocks
objectAtIndex:iLine];
[columArray insertObject:newBlock atIndex:iColumn];
Option 4: Use a dictionary to maintain the list of NSMutableArrays:
NSString *key = [NSString stringWithFormat:#"%d", iLine];
NSMutableArray *columnArray = [dictAllBlocks objectForKey:key];
if (columnArray == nil)
{
columnArray = [NSMutableArray arrayWithCapacity:0];
[dictAllBlocks setObject:columnArray forKey:key];
}
[columArray insertObject:newBlock atIndex:iColumn];
How to choose:
If the maximum value for iLine is not enormous, I would go with option #2. A handful of NSMutableArrays initialized to zero capacity will take up very little memory.
If the maximum value for iLine is enormous, but you expect it to be accessed sparsely (i.e., only a few values of iLine will ever be accessed), then you should go with Option #4. This will save you from having to fill an NSMutableArray with objects that never get used. The overhead of converting the string-value key for the dictionary will be less than the overhead for creating all of those blanks.
If you're not sure, try out each option and profile them: measure your memory usage and the time required to execute. If neither of these options work, you may have to explore more complex solutions, but only do that if it turns out to be necessary.
A note of caution:
The original code that you posted has a memory leak in the following line:
[arrAllBlocks insertObject:[[NSMutableArray alloc] init] atIndex:iLine];
The NSMutableArray objects that you initialize here are never released. When you call [[NSMutableArray init] alloc], a brand new object is created (with a reference count of one). The insertObject method then adds that new object to arrAllBlocks, and retains it (increasing its retain count to 2). Later, when you release arrAllBlocks, the new array will be sent a release message, but that will only reduce its retain count to one again. At that point, it will stick around in RAM until your program exits.
The best thing to do here is to use [NSMutableArray arrayWithCapacity:0] instead (as I have done in my examples). This returns a new NSMutableArray, just the same as your code did, but this instance has already been autoreleased. That way, arrAllBlocks can take ownership of the new object and you can be sure that it will be released when appropriate.
You can't. NSArray (and its subclass NSMutableArray) do not allow you to insert nil into the array. That's clearly outlined in the documentation.
If, for some reason, you need to have "empty" values in an array, then you should insert [NSNull null] instead and test for that. From the docs: "The NSNull class defines a singleton object used to represent null values in collection objects (which don’t allow nil values)."
UPDATE:
This means you could change your code very simply to this:
if ([[arrAllBlocks objectAtIndex:iLine] isEqual:[NSNull null]]) {
[(NSMutableArray *)arrAllBlocks insertObject:[NSMutableArray array] atIndex:iLine];
}
NSMutableArray *columnArray = (NSMutableArray *)[arrAllBlocks objectAtIndex:iLine];
[columnArray insertObject:newBlock atIndex:iColumn];
To check for NSNull you can simply compare against the pointer, since it's a Singleton:
if ([NSNull null] == [arrAllBlocks objectAtIndex:iLine]) {
[arrAllBlocks insertObject:[NSMutableArray array] atIndex:iLine];
}
NSMutableArray *columnArray = [arrAllBlocks objectAtIndex:iLine];
[columnArray insertObject:newBlock atIndex:iColumn];
I also removed the unsightly casts. Casting is rarely necessary in Objective-C. It usually just adds noise, and can hide real bugs. Since you're experiencing crashes, it's worth removing the casts from this code and listen to what the compiler has to tell you about it.
Telling the compiler to ignore warnings for a piece of code does not make the underlying problem with it go away!
Related
I have an Array comprised of further sub-Arrays
I want to display a summary of the contents of the sub-arrays into a TableView with the count of occurrences of each entry. I have determined that the best and easiest way is through a NSMutableDictionary as an intermediate step.
I declare the dictionary in my implementation
#implementation ReviewViewController
{
NSMutableDictionary *dict;
}
and down in my methods I initialise and use it like so:
dict = [NSMutableDictionary new];
[dict setObject:[Observation entryCount] forKey:[observedItem species]];
The swapping of key and object is deliberate as its the count I want. I'm using the fact that values are retained, but keys are overwritten, so if i swap them round i get the curation for free.
It works!, but every time the method is invoked, the Dictionary is clobbered by the re-initalization so I only get the last thing entered. Anywhere else and it falls out of scope.
if I pass it as an arguement in the method name instead, I get the message "Local declaration of 'dict' hides instance variable ". The code is already an irreducable set of parts, so
so, where is the correct place for the instantiation?
I'd love to make my contribution here as a meaningful thanks, but before I can do that I'm going to look silly with such questions.
You would usually initialise it in your init method. For a view controller this is usually the following:
- (id)initWithNibName:(NSString*)nibName bundle:(NSBundle*)bundle {
if ((self = [super initWithNibName:nibName bundle:bundle])) {
dict = [NSMutableDictionary new];
}
return self;
}
I don't understand what you mean by "Anywhere else and it falls out of scope.". It's an instance variable. It is therefore in scope in any method in the class.
By the way, I would name your instance variables with an underscore prefix, e.g. _dict. This is common convention and it helps you to remember when using it that it's an instance variable.
I am having two arrays, Namely
NMutableArray* first;
NMutableArray* second;
Now I am copying first object to the second array like
for (int i=0;i<first.count; i++)
{
[second addObject:[first objectAtIndex:i];
}
This is ok. I don't know how to access the value of the First Array. I tried like this ,
[second addObject:[[first objectAtIndex:i]name]];
I want to get the name value which is in the first object of first array. I tried using the above line, it is showing some warning. Please help me
Assuming you started with an array like this:
NSArray *array1 = #[#{#name : #"Fred"},
#{#name : #"Bill"}];
You could create a second array that contains the value of a given property of each element of the first array as follows:
NSArray *array2 = [array1 valueForKey:#"name"];
If you then logged the second array...
NSLog(#"%#", array2);
...the resulting output would be
2012-04-18 16:26:11.226 ExampleRunner[23320:707] (
Fred,
Bill
)
EDIT
Note that this will work regardless of whether the objects in the first array are instances of NSDictionary as shown in the example above, or instances of a class or classes that have a name property or instance variable (or an _name instance variable, for that matter). For more information on how and why this works, see the documentation for the NSKeyValueCoding informal protocol:
http://developer.apple.com/library/ios/#DOCUMENTATION/Cocoa/Reference/Foundation/Protocols/NSKeyValueCoding_Protocol/Reference/Reference.html
The brackets are currently in the wrong place:
[second addObject:[[first objectAtIndex:i] name]];
Updated Answer:
Again, I think you should split stuff out into easy to parse lines of code:
for (id theObject in first)
{
// without an actual type, I still think the compiler might
// throw a warning on this next line of code;
// but maybe RJR III is correct and it won't warn.
// I didn't check.
NSString * nameOfObject = [theObject name];
if(nameOfObject)
{
[second addObject:nameOfObject];
}
}
Notice that I do some error checking in here as well (i.e. making sure the name is not nil).
Original Answer:
You're getting a warning because the compiler doesn't know what kind of custom object is being fetched from your call to "[first objectAtIndex: i]". In other words, it doesn't know what kind of object you're trying to get the "name" of.
Cast it to the right type and you'll get rid of the warning.
Or even better, split that one line of multiple things happening at once into two or three lines of code and make your code more readable in the process.
OK, maybe I'm not seeing clear anymore and hope you can help.
I'm trying to select an Object from a NSMutableArray using:
if([car.seat isEqualToString:#"fancyOne"]){
fancyThings = [[NSMUtableArray]init];
[fancyThings addObjects: car];
}
Now I forgot to tell you I'm new at this Objective-C, so maybe I'm thinking the wrong way.
What I'm basically trying to do is to get an Object from one array by selecting a value of it's components.
This is the way to do it, I am however keep having trouble with my if-statement.
If I leave out the IF-statement it does fill my other NSMutableArray with the exact same object (thisCar) but if I put in the IF-statement it doesn't pick up that the string is the same in thisCar.seat.
I next example it puts everything in the normalThings but there are some aCar.seats which contain the string FANCYONE. I checked the XML file on spaces and that sort of things but everything is in order as far as I can see.
Shall I build it using NSScanner instead of IsEqualToString?
- (void)viewDidLoad {
appDelegate = (XMLAppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.fancyThings = [[NSMutableArray alloc]init];
for (CARS *aCar in appDelegate.someCars) {
if ([aCar.seats isEqualToString:#"FANCYONE"]){
[appDelegate.fancyThings addObject:aCar];
}
else {
[appDelegate.normalThings addObject:aCar];
}
}
self.title = #"Cars";
super viewDidLoad];
}
EDIT:
My BAD!! The code supplied was in fact in order!
There was a mistake in my XMLParser, which added blank lines to the strings, so I couldn't get an equal string!
Hopefully this will give you some guidance:
//init new array
NSMutableArray *fancyThings = [[NSMutableArray alloc] init];
//walk your array
for (SomeCarObject *thisCar in arrayOfCars) {
//is thisCar a qualifying object
if ([thisCar.seat isEqualToString:#"fancyOne"]) {
//yes, add thisCar object
[fancyThings addObject:thisCar];
}
}
You'll want to create that NSMutableArray outside of the for loop (assuming you're iterating through a collection). Then you can add to that NSMutableArray like you did.
Hope this helps!
BTW, you should edit your question with the comment you made to elaborate on it..
It's depends from volume of objects, which u deal with. If there is 1000 objects or less, this method looks good. But if there is more objects, u have risk to freeze u application and have a big memory leaks.
Also if u will need concurrency code later, u have to keep in u mind some
other solutions.
U can using not just a string objects in u array, u can try to fill u array after application startup in objects, which response if string is same or not. Or using nsdictionary with appropriate keys.
Please read my post multithread search design
This piece of codes segment fault on me, any idea why? allButtons is a NSMutableArray, it contains 3 objects, a=0, b=1, a and b are int type
if(a != -1 && b!= -1){
//Swap index in "allButtons"
id tempA = [allButtons objectAtIndex:a];
id tempB = [allButtons objectAtIndex:b];
[allButtons replaceObjectAtIndex:a withObject:tempB]; //Seg fault here?????
[allButtons replaceObjectAtIndex:b withObject:tempA];
needLoad = false;
[self setUpButtons];
}
EDIT:
NSMutableArray *allButtons = //fetch the array from Coredata. This work since I display the data onto the screen, plus, [allButtons count] return 3, and a=0, b=1
f(a != -1 && b!= -1){
//Swap index in "allButtons"
[allButtons exchangeObjectAtIndex:a withObjectAtIndex:b];
needLoad = false;
[self setUpButtons];
}
The first call to replaceObjectAtIndex: will release the old object (tempA), but that shouldn't cause a seg fault. As #Zoran mentioned try logging the retainCount for tempA and verify its count.
Also for swapping elements in an array, you should use exchangeObjectAtIndex:withObjectAtIndex instead of replaceObjectAtIndex:withObject. It's supported from iPhone 2.0.
Just because you have said
NSMutableArray *allbuttons = // something
doesn't mean that it is definitely an NSMutableArray, it just means that the compiler thinks that it will be a NSMutableArray.
If it's from CoreData, it's probably just an NSArray so the method calls you are trying won't work - you'll get unrecongnised selector or something like that.
You will have to convert it to a mutable array first
NSArray *coreData = // core data call
// Create a mutable copy
// NB This means that you are now working on a copy, not the original :)
NSMutableArray *allButtons = [coreData mutableCopy];
tempA is going to be released when you call the first replaceObjectAtIndex. Keep that in mind when calling this... I have no idea why releasing tempA would seg fault for you, examine what its dealloc does perhaps.
Check the retain count of tempA to verify that it is indeed dealloc-ed (not simply released) by the call to replaceObjectAtIndex like so:
id tempA = [allButtons objectAtIndex:a];
NSLog(#"retain count for tempA: %i", [tempA retainCount]);
If you see a retain count of 1 at this level, then your object tempA is being dealloc-ed by the call to replaceObjectAtIndex
Please read and understand the Cocoa rules on object ownership. Note that you have not claimed ownership over the objects referenced by tempA and tempB and you must therefore heed the following:
A received object is normally guaranteed to remain valid within the method it was received in ... (although you must also take care if you modify an object from which you received another object). That method may also safely return the object to its invoker.
Basically, the line:
[allButtons replaceObjectAtIndex:a withObject:tempB];
May cause tempA to be deallocated. This means that the subsequent line will cause allButtons to send a retain message to an invalid object, hence the seg fault. To fix the problem, you need to retain tempA before the swap and release it or autorelease it after.
NB it's wise to forget about retain counts. Unless you are fully aware of the implementation of all objects that touch your objects, you cannot make any assumptions about what the retain count of an object is. For instance, there's no rule that says that the implementation of NSMutableArray will only ever retain its elements once.
Use this method pass the appropritate index
exchangeObjectAtIndex:withObjectAtIndex:
I have use below syntax for for set the object.
[dict setObject:eventArray forKey:categoryName];
Now i am trying to get below syntax but i got nothing.
NSMutableArray *tempArrayValue=[[NSMutableArray alloc]init];
tempArrayValue =[tempDict valueForKey:categoryValue];
What is the problem i cant understand can u help me?
you have given key as categoryName not categoryValue, and while retrieving you are using categoryValue.
NSMutableArray *tempArrayValue=[[NSMutableArray alloc]init]; tempArrayValue =[tempDict valueForKey:categoryName];
If you're setting the value like this:
[dict setObject:eventArray forKey:categoryName];
Then you should be fetching it back again like this:
NSMutableArray* eventArray = [dict valueForKey:categoryName];
assuming that eventArray is of type NSMutableArray.
What you are doing has at least two different problems.
This line is a memory leak, since you allocate an object and then throw it away, so delete it:
NSMutableArray *tempArrayValue=[[NSMutableArray alloc]init];
This second line may return a nil object, if there is no object stored for the key categoryValue. (You are using an object called categoryName above, as the key to store the value):
tempArrayValue =[tempDict valueForKey:categoryValue];
You haven't posted enough code to be able to tell why it's not working otherwise.