How does Fast Enumeration (looping) work in Objective-C? (ie: for (NSString *aString in aDictionary)...) - iphone

I'm working on implementing a customized searchBar for a fairly complex table and have come across this code pattern AGAIN. This is a sample from the Beginning iPhone Development book:
- (void)handleSearchForTerm:(NSString *)searchTerm
{
NSMutableArray *sectionsToRemove = [[NSMutableArray alloc] init];
[self resetSearch];
for (NSString *key in self.keys)
{
NSMutableArray *array = [self.names valueForKey:key];
NSMutableArray *toRemove = [[NSMutableArray alloc] init];
for (NSString *name in array)
{
if ([name rangeOfString:searchTerm
options:NSCaseInsensitiveSearch].location == NSNotFound)
[toRemove addObject:name];
}
if ([array count] == [toRemove count])
[sectionsToRemove addObject:key];
[array removeObjectsInArray:toRemove];
[toRemove release];
}
[self.keys removeObjectsInArray:sectionsToRemove];
[sectionsToRemove release];
[table reloadData];
}
The part I'm curious about is the "for (NSString *name in array)" section. What is this doing exactly? It seems to create a string for every item in the array. Also, how does this work with dictionaries?
Thanks!

This construct is a different kind of for loop that runs over items in an Objective-C collection, rather than a C array. The first part defines an object that is being set to one element in the collection each run of the loop, while the second part is the collection to enumerate. For example, the code:
NSArray *array = [NSArray arrayWithObjects:#"foo", #"bar", nil];
for(NSString *string in array) {
NSLog(string);
}
would print:
foo
bar
It's defining an NSString *string that, each run of the loop, gets set to the next object in the NSArray *array.
Similarly, you can use enumeration with instances of NSSet (where the order of objects aren't defined) and NSDictionary (where it will enumerate over keys stored in the dictionary - you can enumerate over the values by enumerating over keys, then calling valueForKey: on the dictionary using that key).
It's extremely similar to the construct in C:
int array[2] = { 0, 1 };
for(int i = 0; i < 2; i++) {
printf("%d\n", array[i]);
}
which prints:
0
1
It's just a syntactical way of making the code more readable and hiding some of the fancy enumeration that goes into listing objects in an NSArray, NSSet, or NSDictionary. More detail is given in the Fast Enumeration section of The Objective-C 2.0 Programming Language document.

This is called fast enumeration. It loops through the array, setting key to each item. It's the same, functionally, as doing this:
NSString *key;
for ( NSInteger i = 0; i < [[ self keys ] count ]; i++ ) {
key = [[ self keys ] objectAtIndex:i ];
NSMutableArray *array = [self.names valueForKey:key];
NSMutableArray *toRemove = [[NSMutableArray alloc] init];
for (NSString *name in array)
{
if ([name rangeOfString:searchTerm
options:NSCaseInsensitiveSearch].location == NSNotFound)
[toRemove addObject:name];
}
if ([array count] == [toRemove count])
[sectionsToRemove addObject:key];
[array removeObjectsInArray:toRemove];
[toRemove release];
}

It's a for loop with one iteration for each key in the dictionary.
The for..in construct is called Fast enumeration. You can read more about it in Objective-C 2.0 Programming Guide.
How it works with an object depends on it's implementation of the NSFastEnumeration protocol. The NSDictionary class reference describes how it works with dictionaries:
On Mac OS X v10.5 and later, NSDictionary supports the NSFastEnumeration protocol. You can use the for…in construct to enumerate the keys of a dictionary, as illustrated in the following example.

Related

How do I find (not remove) duplicates in an NSDictionary of NSArrays?

The title pretty much says it all, but just to clarify: I have an NSMutableDictonary containing several NSMutableArrays. What I would like to do is find any value that is present in multiple arrays (there will not be any duplicates in a single array) and return that value. Can someone please help? Thanks in advance!
Edit: For clarity's sake I will specify some of my variables:
linesMutableDictionary contains a list of Line objects (which are a custom NSObject subclass of mine)
pointsArray is an array inside each Line object and contains the values I am trying to search through.
Basically I am trying to find out which lines share common points (the purpose of my app is geometry based)
- (NSValue*)checkForDupes:(NSMutableDictionary*)dict {
NSMutableArray *derp = [NSMutableArray array];
for (NSString *key in [dict allKeys]) {
Line *temp = (Line*)[dict objectForKey:key];
for (NSValue *val in [temp pointsArray]) {
if ([derp containsObject:val])
return val;
}
[derp addObjectsFromArray:[temp pointsArray]];
}
return nil;
}
this should work
If by duplicates you mean returning YES to isEqual: you could first make an NSSet of all the elements (NSSet cannot, by definition, have duplicates):
NSMutableSet* allElements = [[NSMutableSet alloc] init];
for (NSArray* array in [dictionary allValues]) {
[allElements addObjectsFromArray:array];
}
Now you loop through the elements and check if they are in multiple arrays
NSMutableSet* allDuplicateElements = [[NSMutableSet alloc] init];
for (NSObject* element in allElements) {
NSUInteger count = 0;
for (NSArray* array in [dictionary allValues]) {
if ([array containsObject:element]) count++;
if (count > 1) {
[allDuplicateElements addObject:element];
break;
}
}
}
Then you have your duplicate elements and don't forget to release allElements and allDuplicateElements.

Error spliting NSarray into sections

Okay so I have been working through an example that closely matches what I am trying to achive, the sole difference being that in the example he is directly calling from his database the data he needs to be sectioned etc. Where as I already have a sorted NSArray.
This is the tutorial I am working off - iPhone Development: Creating Native Contacts like screen
I have created a Method that is capturing each entry in the NSArray and putting these results into a alpha based NSDictionary (so their will be a NSDictionary for A,B,C... etc)
here is my method.
//method to sort array and split for use with uitableview Index
- (IBAction)startSortingTheArray:(NSMutableArray *)arrayData
{
//Sort incoming array alphabetically
//sortedArray = [arrayData sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
[self setSortedArray:[arrayData sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)]];
arrayOfCharacters = [[NSMutableArray alloc]init];
objectsForCharacters = [[NSMutableDictionary alloc]init];
for(char c='A';c<='Z';c++)
{
if([sortedArray count] >0)
{
[arrayOfCharacters addObject:[NSString stringWithFormat:#"%c",c]];
[objectsForCharacters setObject:sortedArray forKey:[NSString stringWithFormat:#"%c",c]];
NSLog(#"%#", objectsForCharacters);
}
[sortedArray release];
//Reloads data in table
[self.tableView reloadData];
}
}
This is putting every value into every alpha section, I am hoping someone can help me with making it so that only alpha sections are established if there is a value in the array for it.. then only loading those values into each section, not every section.
This piece of code will do just that and will be much more efficient than filtering the array once for each letter.
//Sort incoming array alphabetically so that each sub-array will also be sorted.
NSArray *sortedArray = [arrayData sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
// Dictionary will hold our sub-arrays
NSMutableDictionary *arraysByLetter = [NSMutableDictionary dictionary];
// Iterate over all the values in our sorted array
for (NSString *value in sortedArray) {
// Get the first letter and its associated array from the dictionary.
// If the dictionary does not exist create one and associate it with the letter.
NSString *firstLetter = [value substringWithRange:NSMakeRange(0, 1)];
NSMutableArray *arrayForLetter = [arraysByLetter objectForKey:firstLetter];
if (arrayForLetter == nil) {
arrayForLetter = [NSMutableArray array];
[arraysByLetter setObject:arrayForLetter forKey:firstLetter];
}
// Add the value to the array for this letter
[arrayForLetter addObject:value];
}
// arraysByLetter will contain the result you expect
NSLog(#"Dictionary: %#", arraysByLetter);
Note that arraysByLetter is a dictionary that contains one array per "first letter" that exists in your initial data.
--- Added on 2011-09-23 ---
[sortedArray removeAllObjects];
NSArray *sortedKeys = [arraysByLetter.allKeys sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
for (NSString *key in sortedKeys) {
[sortedArray addObject:key];
[sortedArray addObjectsFromArray: [arraysByLetter objectForKey:key]];
}
NSLog(#"Sorted Array: %#", sortedArray);
The output is the following:
C,
Computer,
H,
Helene,
Hello,
J,
Jules,
W,
World
Looks like you need to filter sortedArray with a predicate for each letter. Something like this...
for(char c='A';c<='Z';c++) {
NSPredicate *predicate =
[NSPredicate predicateWithFormat:#"SELF beginswith[c] '%c'", c];
NSArray *objectsBeginningWithCurrentLetter = [array filteredArrayUsingPredicate:predicate];
if([sortedArray count] >0)
{
[arrayOfCharacters addObject:[NSString stringWithFormat:#"%c",c]];
if ([objectsBeginningWithCurrentLetter count] > 0) {
[objectsForCharacters setObject:objectsBeginningWithCurrentLetter forKey:[NSString stringWithFormat:#"%c",c]];
NSLog(#"%#", objectsForCharacters);
}
}
[sortedArray release];
//Reloads data in table
[self.tableView reloadData];
}

how do I create fresh NSMutableArray?

I have an NSMutableArray which only lasts during the session.
Currently I create it like this
NSMutableArray *temp = [[NSMutableArray alloc] initWithCapacity:10];
[self setScoreArray:temp];
[temp release];
Problem is when I go to check each index I'm getting an array outofbounds error
NSNumber *previousScore = [[self scoreArray] objectAtIndex:[self quizNum]];
if ( previousScore != nil )
{
[self clearQuizBtns];
NSInteger previousScoreValue = [previousScore integerValue];
[self selectButtonAtTag:previousScoreValue];
}else {
[self clearQuizBtns];
}
I've read in other posts that initWithCapacity doesn't actually create the array. So what can I populate the array with initially?
Thanks in advance.
Two ways:
first: to initiate array with default values of NSNull class
NSMutableArray *temp = [[NSMutableArray alloc] initWithCapacity:10];
for (int i = 0 ; i < 10 ; i++)
{
[temp insertObject:[NSNull null] atIndex:i];
}
[self setScoreArray:temp];
[temp release];
and then to check: if object is kind of NSNull class means it was a never set before
id previousScore = [[self scoreArray] objectAtIndex:[self quizNum]];
if (![previousScore isKindOfClass:[NSNull class]])
{
[self clearQuizBtns];
NSInteger previousScoreValue = [(NSNumber *)previousScore integerValue];
[self selectButtonAtTag:previousScoreValue];
}else {
[self clearQuizBtns];
}
second: store scores in NSMutableDictionary and use NSNumber's as keys
// scoreDictionary property of NSMutableDictionary class must be declared in self
NSNumber *previousScore = [self.scoreDictionary objectForKey:[NSNumber numberWithInt:[self quizNum]]];
if (previousScore != nil)
{
[self clearQuizBtns];
NSInteger previousScoreValue = [previousScore integerValue];
[self selectButtonAtTag:previousScoreValue];
}else {
[self clearQuizBtns];
}
NSArray does not support "holes". The capacity is just a hint to the initializer.
You could either fill the array with placeholder objects or, more typically, change your algorithm to either fully prepopulate the array or to lazy load it linearly.
Your problem seems to be that you're never actually setting any score in the score array.. are you? NSArrays have an actual count of items in them, and accessing an index beyond that count will blow up, as you've seen. If there will only ever be a fixed (small) number of scores, like 10, then you could set them all initially to something default like:
for (int i = 0; i < 10; i++) {
[temp addObject:[NSNumber numberWithInt:0]];
}
P.S. -initWithCapacity does "create the array", it just doesn't create any objects in the array. The capacity is a hint only.
Using the arrayWithObject: or arrayWithObjects: methods can provide an array with pre-populated values.
One cool thing about NSMutableArrays is that you can just do an "init" and the array will handle adding and removing objects on the fly. Remember that you generally addObject: or removeObjectAtIndex: when dealing with mutable arrays.

Adding # & search sign to TableIndex in UITableView

In iPhone native Phone book - there is a search character at the top & # character at the bottom.
I want to add both of that character in my table Index.
Currently I have implemented following code.
atoz=[[NSMutableArray alloc] init];
for(int i=0;i<26;i++){
[atoz addObject:[NSString stringWithFormat:#"%c",i+65]];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView{
return atoz;
}
How to have # character & search symbol in my UITableView?
The best way to tackle this is to make use of the tools the framework provides. In this case, you want to use UILocalizedIndexedCollation (developer link).
I also have a decorator for this class that is designed to insert the {{search}} icon for you and handle the offsets. It is a like-for-like drop-in replacement for UILocalizedIndexedCollation.
I've posted a more in-depth description of how to use this on my blog. The decorator is available here (Gist).
The basic idea is to group your collection into an array of arrays, with each array representing a section. You can use UILocalizedIndexedCollation (or my replacement) to do this. Here's a small NSArray category method I use to do this:
#implementation NSArray (Indexing)
- (NSArray *)indexUsingCollation:(UILocalizedIndexedCollation *)collation withSelector:(SEL)selector;
{
NSMutableArray *indexedCollection;
NSInteger index, sectionTitlesCount = [[collation sectionTitles] count];
indexedCollection = [[NSMutableArray alloc] initWithCapacity:sectionTitlesCount];
for (index = 0; index < sectionTitlesCount; index++) {
NSMutableArray *array = [[NSMutableArray alloc] init];
[indexedCollection addObject:array];
[array release];
}
// Segregate the data into the appropriate section
for (id object in self) {
NSInteger sectionNumber = [collation sectionForObject:object collationStringSelector:selector];
[[indexedCollection objectAtIndex:sectionNumber] addObject:object];
}
// Now that all the data's in place, each section array needs to be sorted.
for (index = 0; index < sectionTitlesCount; index++) {
NSMutableArray *arrayForSection = [indexedCollection objectAtIndex:index];
NSArray *sortedArray = [collation sortedArrayFromArray:arrayForSection collationStringSelector:selector];
[indexedCollection replaceObjectAtIndex:index withObject:sortedArray];
}
NSArray *immutableCollection = [indexedCollection copy];
[indexedCollection release];
return [immutableCollection autorelease];
}
#end
So, given an array of objects, for example books that I want to divide into sections based on their name (the Book class has a name method), I would do this:
NSArray *books = [self getBooks]; // etc...
UILocalizedIndexedCollation *collation = [UILocalizedIndexedCollation currentCollation];
NSArray *indexedBooks = [books indexUsingCollation:collation withSelector:#selector(name)];

NSArray to NSMutableArray as random stack

Just a conceptual description first:
I am reading input from a text file (a list of words) and putting these words into an NSArray using componentsSeparatedByString method. This works.
But I wanted to select the words randomly and then delete them from the array so as to ensure a different word each time. Of course, you cannot change the NSArray contents. So...
I copied the contents of the NSArray into an NSMutableArray and use IT for the selection source. This also works - 269 objects in each array.
To return a word from the NSMutableArray I use the following code:
note- the arrays are declared globally
as
arrMutTextWords = [[NSMutableArray alloc] init]; //stack for words
arrTextWords = [[NSArray alloc] init]; //permanent store for words
-(NSString*) getaTextWord
{
// if the mutable text word array is empty refill
if ([arrMutTextWords count] == 0){
for (int i = 0 ; i < [arrTextWords count]; i++)
[arrMutTextWords addObject:[arrTextWords objectAtIndex:i]];
}
int i = random() % [arrMutTextWords count];
NSString* ptrWord = [arrMutTextWords objectAtIndex:i];
[arrMutTextWords removeObjectAtIndex:i];
return ptrWord;
}
The program crashes during a call to the method above - here is the calling code:
arrTmp is declared globally arrTmp = [[NSArray alloc] init]; //tmp store for words
for (int i = 0 ; i < 4; i++) {
tmpWord = [self getaTextWord];
[arrTmp addObject:tmpWord];
[arrTmp addObject:tmpWord];
}
I'm thinking that somehow deleting strings from arrMutTextWords is invalidating the NSArray - but I can't think how this would occur.
One possible source for problems is your fetching AND removing the NSString object from your list. Removing it releases that NSString instance therefore devalidating your reference.
To be shure to retain a reference you should use this code sequence instead:
NSString * ptrWord = [[[arrMutTextWords objectAtIndex:i] retain] autorelease];
[arrMutTextWords removeObjectAtIndex:i];
return ptrWord;
By the way: You should use
NSMutableArray *mutableArray = [NSMutableArray arrayWithArray: array];
instead of copying all values by hand. While i do not know the implementation of NSMutableArray, i know from times long ago (NeXTstep), that there are several possible optimizations that may speed up basic NSArray operations.
And finally copying this way is much more concise.
Just ran this through XCode and got random words returned, however I skipped the whole for loop and used addObjectsFromArrayfrom NSMutableArray.
NSArray *randomArray = [[NSArray alloc] initWithObjects:#"Paul", #"George", #"John", nil];
NSMutableArray *muteArray = [[NSMutableArray alloc] init];
[muteArray addObjectsFromArray:randomArray];
int i = random() % [muteArray count];
NSString* ptrWord = [muteArray objectAtIndex:i];
[muteArray removeObjectAtIndex:i];
NSLog(#"ptrWord %#", ptrWord); //gave me a different name each time I ran the function.
Hope this clears some things up.