Is there a more efficient way to clean up my CCNodes? - objective-c

Is there a more efficient way to clean up my CCNodes? I'm calling this function (and others like it for different game objects), on a timer.
- (void)pulseBullets:(NSMutableArray *)bs targets:(NSArray *)targets {
for (Bullet *b in bs) {
for (QuantumPilot *p in targets) {
if (p.active) {
[p processBullet:b];
if (!p.active) {
[self processKill:p];
}
}
}
}
NSMutableArray *bulletsToErase = [NSMutableArray array];
for (Bullet *b in bs) {
[b pulse];
if ([self bulletOutOfBounds:b]) {
[bulletsToErase addObject:b];
}
}
for (Bullet *b in bulletsToErase) {
[b removeFromParentAndCleanup:YES];
}
[bs removeObjectsInArray:bulletsToErase];
}

Ok, but i make no 'statement' on performance, you will have to measure that for yourself. If you iterate a mutable array in reverse order, it is safe to delete an object during the iteration, because the iterator will not be invalidated by the deletion. So you could get rid altogther of the bullets to erase array like so :
for (Bullet *b in [bs reverseObjectEnumerator]) { // *** do not change iteration order ***
for (QuantumPilot *p in targets) {
if (p.active) {
[p processBullet:b];
if (!p.active) {
[self processKill:p];
}
}
}
[b pulse];
if ([self bulletOutOfBounds:b]) {
[b removeFromParentAndCleanup:YES];
[bs removeObject:b];
}
}
this is simpler , but obfuscates the inherent risk of altering an array content during iteration. You make the call as to whether it is 'cleaner'. Also, maybe the 'cost' of reversing the iterator is higher that what you save, as i said you would have to measure it.

Related

How to safely remove object from a nsmutablearray while iterating through it?

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.

Better way to observe in ReactiveCocoa

I'm still new to ReactiveCocoa. I wanted to simply add an observer to a field, so did it like this:
[_countryPicker rac_observeKeyPath:#"value" options:nil observer:self block:^(VBCountry* value, NSDictionary* change)
{
if ([_mobileField.textField.text length] == 0)
{
[_mobileField.textField setText:[NSString stringWithFormat:#"+%i", value.dialCode]];
}
}];
With block callback, and no need to explicitly detach the observer, this is already better than old-style KVO.
However, is this a low-level method to which there's a higher level of abstraction? If so, is it OK to call this method directly? And what's the better/higher way to do it?
I'd advise against depending on the direct KVO methods. They're really an implementation detail.
Let's progressively re-write that with idiomatic RAC operators.
First we'll just replace the direct KVO code with RACObserve:
[RACObserve(_countryPicker, value) subscribeNext:^(VBCountry *value) {
if ([_mobileField.textField.text length] == 0)
{
[_mobileField.textField setText:[NSString stringWithFormat:#"+%i", value.dialCode]];
}
}];
Then we'll replace the if and string formatting with -filter: and -map::
[[[RACObserve(_countryPicker, value) filter:^(id _) {
return [_mobileField.textField.text length] > 0;
}] map:^(VBCountry *value) {
return [NSString stringWithFormat:#"+%i", value.dialCode];
}] subscribeNext:^(NSString *text) {
[_mobileField.textField setText:text];
}];
Finally we'll use the RAC macro to make the assignment over time explicit:
RAC(_mobileField.textField, text) = [[RACObserve(_countryPicker, value) filter:^(id _) {
return [_mobileField.textField.text length] > 0;
}] map:^(VBCountry *value) {
return [NSString stringWithFormat:#"+%i", value.dialCode];
}];

Refactoring similar methods for objective C

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.)

How to break block loop in Objective - C?

I have these declarations in header file:
Note: I won't explain the whole code, I think it is easy to understand
typedef void (^loopCell)(id cell);
-(id)allCells:(loopCell)cell;
And allCells function implementation:
-(id)allCells:(loopCell)cell
{
for (AAFormSection *section in listSections)
{
for (id _cell in section.fields) {
cell(_cell);
}
}
return nil;
}
The usage of allCells function:
-(void)setFieldValue:(NSString *)value withID:(int)rowID
{
[self allCells:^(id cell) {
if([cell isKindOfClass:[AAFormField class]]) {
AAFormField *_cell = (AAFormField *)cell;
if(_cell.rowID == rowID) {
_cell.value = value;
//return; Here I want to terminate loop
}
}
}];
}
My problem is, I can't terminate allCells loop in the middle (actually when I found object I need in the loop, I don't want iterate through other objects)
How can I stop allCells loop in the middle?
Look at the docs for NSArray enumerateObjectsUsingBlock:. They setup the block signature to take a BOOL pointer. Set the stop BOOL to YES to cause the iteration to stop.
typedef void (^loopCell)(id cell, BOOL *stop);
-(id)allCells:(loopCell)cell {
BOOL stop = NO;
for (AAFormSection *section in listSections) {
for (id _cell in section.fields) {
cell(_cell, &stop);
if (stop) {
break;
}
}
if (stop) {
break;
}
}
return nil;
}
-(void)setFieldValue:(NSString *)value withID:(int)rowID {
[self allCells:^(id cell, BOOL *stop) {
if([cell isKindOfClass:[AAFormField class]]) {
AAFormField *_cell = (AAFormField *)cell;
if(_cell.rowID == rowID) {
_cell.value = value;
if (stop) {
*stop = YES;
}
}
}
}];
}
You can't break from setFieldValue, but you can from allCells.
It's up to the method you're using that calls the block — allCells in this case — to provide a mechanism for stopping the loop. Usually, it's a parameter to the block.
If allCells is yours and you don't mind modifying it, you modify the block signature to take a pointer to a BOOL, initialized to YES, and check if the block modified it to NO.
(Note: You can break from a for in loop.)

Do [sprite stopActionByTag: kTag]; work differently for CCAction and CCSequence?

//prog 1
-(void)gameLogic:(ccTime)dt
{
id actionMove = [CCMoveTo actionWithDuration:1.0 position:ccp(windowSize.width/2-400, actualY)];
[actionMove setTag:6];
[self schedule:#selector(update:)];
[hitBullet runAction:actionMove];
}
-(void)update:(ccTime)dt
{
if ( (CGRectIntersectsRect(hitRect, playerRect)) )
{
[[[self getActionByTag:6] retain] autorelease];
[hitBullet stopActionByTag: 6];
}
}
//prog 2
-(void)gameLogic:(ccTime)dt
{
id actionMove = [CCMoveTo actionWithDuration:1.0 position:ccp(windowSize.width/2-400, actualY)];
id hitBulletAction = [CCSequence actionWithDuration:(intervalforEnemyshoot)];
id hitBulletSeq = [CCSequence actions: hitBulletAction, actionMove, nil];
[hitBulletSeq setTag:5];
[self schedule:#selector(update:)];
[hitBullet runAction:hitBulletSeq];
}
-(void)update:(ccTime)dt
{
if ( (CGRectIntersectsRect(hitRect, playerRect)) )
{
[[[self getActionByTag:5] retain] autorelease];
[hitBullet stopActionByTag: 5];
}
}
While prog1 is working prog2 is not working ? I think the both are same. But why the two stopActions are working differently in two prog1 and prog2 ? I mean the actions are stopped in prog1 but the actions are not stopping in prog2 ?
thank You.
Srikanth, what version of cocos2d are you using?
The answer to your question is no. All actions are treated the same.
The code responsible for removing the action is the following:
// -[CCActionManager removeActionByTag:target:]
-(void) removeActionByTag:(int) aTag target:(id)target
{
NSAssert( aTag != kActionTagInvalid, #"Invalid tag");
NSAssert( target != nil, #"Target should be ! nil");
tHashElement elementTmp;
elementTmp.target = target;
tHashElement *element = ccHashSetFind(targets, CC_HASH_INT(target), &elementTmp);
if( element ) {
NSUInteger limit = element->actions->num;
for( NSUInteger i = 0; i < limit; i++) {
CCAction *a = element->actions->arr[i];
if( a.tag == aTag && [a originalTarget]==target)
return [self removeActionAtIndex:i hashElement:element];
}
// CCLOG(#"cocos2d: removeActionByTag: Action not found!");
} else {
// CCLOG(#"cocos2d: removeActionByTag: Target not found!");
}
}
What I would recommend you do is turn on Cocos2d debugging, and un-comment those two CCLog lines. That should tell you if there's a problem in there.
If that doesn't tell you anything, it may be of interest to look into the CCSequence class. Perhaps, if the sequence is being removed, it doesn't remove the actions IN the sequence itself?
All actions are treated equal, but perhaps this Sequence action should be an exception to that.