What is faster in objective C and iphone? self enumeration or for loop?
i have 2 fragments of code to help me compare.
for this example we have as a fact that array is an NSMutableArray with "x" items.
Case 1:
-(void)findItem:(Item*)item
{
Item *temp;
for (int i = 0 ;i<[array count];i++)
{
temp = [array objectAtIndex:i];
if(item.tag == temp.tag)
return;
}
}
Case 2:
-(void)findItem:(Item*)item
{
for(Item *temp in array)
{
if(item.tag == temp.tag)
return;
}
}
it is almost obvious that case2 is faster, is it?
It's called fast enumeration, for a reason.
See: http://cocoawithlove.com/2008/05/fast-enumeration-clarifications.html
Related
I am using a nsmutablearray in loop and want to remove its object (or assign nil) that has just traversed.
But if I am doing so, I get an error as <__NSArrayM: 0x8c3d3a0> was mutated while being enumerated.' . The code is as below
- (TreeNode*)depthLimitedSearch:(TreeNode *)current costLimit:(int)currentCostBound {
NSMutableArray *children=[NSMutableArray arrayWithArray:[current expandNodeToChildren]];
for (TreeNode *s in children) {
if (s.puzzleBox.isFinalPuzzleBox) {//checking for final puzzleBox
return s;
}
/*exploredNodes++;
if (exploredNodes %10000==0) {
NSLog(#"explored nodes for this treshold-%d are %d",currentCostBound,exploredNodes);
}*/
int currentCost =[s.cost intValue]+[s.heuristicsCost intValue];
if (currentCost <= currentCostBound) {
//[s.puzzleBox displayPuzzleBox];
TreeNode *solution = [self depthLimitedSearch:s costLimit:currentCostBound];
if (solution!=nil){//&& (bestSolution ==nil|| [solution.cost intValue] < [bestSolution.cost intValue])) {
bestSolution = solution;
return bestSolution;
}
}else {
if (currentCost < newLimit) {
//NSLog(#"new limit %d", currentCost);
newLimit = currentCost;
}
}
// here I want to free memory used by current child in children
[children removeObject:s]
}
children=nil;
return nil;
}
and I have commented the place where I want to release the space used by the child.
You should not be using a for...in loop if you want to remove elements in the array. Instead, you should use a normal for loop and go backwards in order to make sure you don't skip any items.
for (NSInteger i = items.count - 1; i >= 0; i--) {
if (someCondition) {
[items removeObjectAtIndex:i];
}
}
You can collect the items to be removed in another array and remove them in a single pass afterwards:
NSMutableArray *toRemove = [NSMutableArray array];
for (id candidate in items) {
if (something) {
[toRemove addObject:candidate];
}
}
[items removeObjectsInArray:toRemove];
It’s easier than iterating over indexes by hand, which just asking for off-by-one errors.
Not sure how this plays with your early returns, though.
In my app I'me getting responses from the server and I have to check that I don't create duplicate objects in the NSArray which contains NSDictionaries. Now to check if the objects exists I do this:
for (int i = 0; i < appDelegate.currentUser.userSiteDetailsArray.count; i++){
NSDictionary *tmpDictionary = [appDelegate.currentUser.userSiteDetailsArray objectAtIndex:i];
if ([[tmpDictionary valueForKey:#"webpropID"] isEqualToString:tmpWebproperty.identifier]){
needToCheck = NO;
}
if (i == appDelegate.currentUser.userSiteDetailsArray.count - 1 && ![[tmpDictionary valueForKey:#"webpropID"] isEqualToString:tmpWebproperty.identifier] && needToCheck){
// It means it's the last object we've iterated through and needToCheck is still = YES;
//Doing stuff here
}
}
I set up a BOOL value because this iteration goes numerous times inside a method and I can't use return to stop it. I think there is a better way to perform this check and I would like to hear your suggestions about it.
BOOL needToCheck = YES;
for (int i = 0; i < appDelegate.currentUser.userSiteDetailsArray.count; i++){
NSDictionary *tmpDictionary = [appDelegate.currentUser.userSiteDetailsArray objectAtIndex:i];
if ([[tmpDictionary valueForKey:#"webpropID"] isEqualToString:tmpWebproperty.identifier]){
needToCheck = NO;
break;
}
}
if (needToCheck) {
//Doing stuff here
}
But, as others have said, you can maybe keep a "summary" in a separate NSSet that you check first, vs spinning through all the dictionaries.
NSDictionary *previousThing = nil;
for (NSDictionary *thing in appDelegate.currentUser.userSiteDetailsArray) {
if ([thing[#"webpropID"] isEqualToString:newWebPropertyIdentifier]) {
previousThing = thing;
break;
}
}
if (previousThing == nil) {
// no previous thing
} else {
// duplicate
}
How do I refactor similar methods for the following (Objective C)?
- (void)insertNewSong:(Song *)newSong forArtist:(Artist *)artist {
NSMutableArray *newSongList = [[artist songs] mutableCopy];
BOOL hasInserted = NO;
for (int i = 0; i < [[artist songs] count]; i++) {
Song *existingSong = [[artist songs] objectAtIndex:i];
if ([[newSong title] caseInsensitiveCompare:[existingSong title]] == NSOrderedAscending) {
[newSongList insertObject:newSong atIndex:i];
hasInserted = YES;
break;
}
}
if (hasInserted == NO) {
[newSongList addObject:newSong];
}
artist.songs = newSongList;
}
- (void)insertNewArtistToSongList:(Artist *)newArtist {
BOOL hasInserted = NO;
for (int i = 0; i < [_artists count]; i++) {
Artist *existingArtist = [_artists objectAtIndex:i];
if ([[newArtist name] caseInsensitiveCompare:[existingArtist name]] == NSOrderedAscending) {
[_artists insertObject:newArtist atIndex:i];
hasInserted = YES;
break;
}
}
if (hasInserted == NO) {
[_artists addObject:newArtist];
}
}
For the insertNewSong method, a NSMutableArray [artist songs] containing each Song object is used.
For the insertNewArtist method, a NSMutableArray instance variable _artists containing each Artist Object is used.
Both methods insert an object into an NSMutableArray by comparing the text property of the input object against the text property found within the arrays.
Currently the above methods contain some duplication but is easy to understand (in my case). I was thinking whether there might be a way of simplifying it into a more general method, and does not hurt readability?
There is no general rule, but here are some general rules:
Sometimes it makes sense to combine code like this, sometimes not. Lots of pluses/minuses.
Sometimes it's best to abstract PART of the operation, and leave the other part custom.
Generally, if you have a lot of "if thingA then do this, else that" logic, you've done it wrong (or should not do it at all).
It's best when you can write a single routine and just pass in different parameters (that aren't simply Boolean switches) to differentiate the multiple cases.
It's hard.
And, as a general rule, I don't try too hard to abstract until I have the third instance of nearly the same logic.
(Generally speaking.)
If I have an NSArray and I use enumerateUsingBlock to loop through elements in the array, but in some cases I need to skip the loop body and go to next element, is there any continue equivalent in block, or can I use continue directly?
Thanks!
update:
Just want to clarify, what I want to do is:
for (int i = 0 ; i < 10 ; i++)
{
if (i == 5)
{
continue;
}
// do something with i
}
what I need is the continue equivalent in block.
A block is a lot like an anonymous function. So you can use
return
to exit from the function with return type void.
Use "continue" when using Fast Enumeration to do this.
Sample code:
NSArray *myStuff = [[NSArray alloc]initWithObjects:#"A", #"B",#"Puppies", #"C", #"D", nil];
for (NSString *myThing in myStuff) {
if ([myThing isEqualToString:#"Puppies"]) {
continue;
}
NSLog(#"%#",myThing);
}
and output:
2015-05-14 12:19:10.422 test[6083:207] A
2015-05-14 12:19:10.423 test[6083:207] B
2015-05-14 12:19:10.423 test[6083:207] C
2015-05-14 12:19:10.424 test[6083:207] D
Not a puppy in sight.
You can not use continue in block, you will get error: continue statement not within a loop. use return;
[array enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
/* Do something with |obj|. */
if (idx==1) {
return;
}
NSLog(#"%#",obj);
}];
i have a strange issue , if i remove my item at forin enumeration , it would crash , so like this:
for (Obstacle *obstacleToTrack in _obstaclesToAnimate) {
//this if else not so important for happening crash
if(obstacleToTrack.distance > 0){
obstacleToTrack.distance -= _playerSpeed * _elapsed;
}else{
if (obstacleToTrack.watchOut) {
obstacleToTrack.watchOut = NO;
}
obstacleToTrack.x -= (_playerSpeed + obstacleToTrack.speed) * _elapsed;
}
if (obstacleToTrack.x < -obstacleToTrack.width || _gameState == GS_OVER) {
[self removeChild:obstacleToTrack];
//this line makes crash happen , if remove this line code work fine
[_obstaclesToAnimate removeObject:obstacleToTrack];
}
}
if i change my code to
NSMutableArray *forRemoving = [[NSMutableArray alloc]init];
for (Obstacle *obstacleToTrack in _obstaclesToAnimate) {
//this if else not so important for happening crash
if(obstacleToTrack.distance > 0){
obstacleToTrack.distance -= _playerSpeed * _elapsed;
}else{
if (obstacleToTrack.watchOut) {
obstacleToTrack.watchOut = NO;
}
obstacleToTrack.x -= (_playerSpeed + obstacleToTrack.speed) * _elapsed;
}
if (obstacleToTrack.x < -obstacleToTrack.width || _gameState == GS_OVER) {
// code change here
[self removeChild:obstacleToTrack];
[forRemoving addObject:obstacleToTrack];
}
}
for(Obstacle *obstacleToTrack in forRemoving){
[_obstaclesToAnimate removeObject:obstacleToTrack];
[forRemoving removeObject:obstacleToTrack];
}
[forRemoving release];
this would work perfect , could someone tell me why?
The answer is that if you remove an object the other objects in that array move postion in the array since an item is removed.
For example we have an array with 4 items, if we remove the first item (item 0) the item that used to be at index 1 is now at index 0 and the item at 2 is now at 1.
Thus the enumeration breaks.
You could solve this by looping thru the array from the count down to 0:
for (int i = [array count]-1; i >= 0; i--) {
id object = [array objectAtIndex:i];
if (some check) {
[array removeObjectAtIndex:i];
}
}
Like rckoenes said, you break the enumaration by removing stuff in the array while iterating through it.
What you can do is to have a second array where you insert the objects that you want to remove. Then, after your enumeration is finished you can remove all the objects that are found in the second array, from your first array.
You must not modify a collection while iterating through its items.
If you iterate index based (i.e. the classical for loop), you can remove things, but be careful about adjusting your index.
Check this link
https://developer.apple.com/library/mac/documentation/cocoa/conceptual/Collections/Articles/Enumerators.html
Enumeration is “safe”—the enumerator has a mutation guard so that if
you attempt to modify the collection during enumeration, an exception
is raised.