7

this is the error that i see when present SKScene, this error occurs randomly and are not able to replicate

* Terminating app due to uncaught exception 'NSGenericException', reason: '* Collection < NSConcreteMapTable: 0x1459da60 > was mutated while being enumerated.'

what's happen?

tell me if you need any other info

thanks

EDIT:

*** First throw call stack:
(
    0   CoreFoundation                      0x025601e4 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x022298e5 objc_exception_throw + 44
    2   CoreFoundation                      0x025efcf5 __NSFastEnumerationMutationHandler + 165
    3   Foundation                          0x01e47f03 -[NSConcreteMapTable countByEnumeratingWithState:objects:count:] + 66
    4   CoreFoundation                      0x0253d77f -[__NSFastEnumerationEnumerator nextObject] + 143
    5   SpriteKit                           0x01d009f2 +[SKTextureAtlas(Internal) findTextureNamed:] + 232
    6   SpriteKit                           0x01cf709c __26-[SKTexture loadImageData]_block_invoke + 1982
    7   SpriteKit                           0x01d34d09 _Z14SKSpinLockSyncPiU13block_pointerFvvE + 40
    8   SpriteKit                           0x01cf6898 -[SKTexture loadImageData] + 228
    9   SpriteKit                           0x01cf65d9 __51+[SKTexture preloadTextures:withCompletionHandler:]_block_invoke + 241
    10  libdispatch.dylib                   0x02b117b8 _dispatch_call_block_and_release + 15
    11  libdispatch.dylib                   0x02b264d0 _dispatch_client_callout + 14
    12  libdispatch.dylib                   0x02b14eb7 _dispatch_root_queue_drain + 291
    13  libdispatch.dylib                   0x02b15127 _dispatch_worker_thread2 + 39
    14  libsystem_c.dylib                   0x02de1e72 _pthread_wqthread + 441
    15  libsystem_c.dylib                   0x02dc9daa start_wqthread + 30
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Ilario
  • 5,979
  • 2
  • 32
  • 46

4 Answers4

4

I get the same exception on occasion. It's been around for a while and I've been trying to pinpoint it for weeks.

My suspicion is that it may occur due to preloading textures, either manually or triggered automatically by Sprite Kit while at the same time some other code causes textures to be loaded or accessed.

I have reduced my preloadTextures: calls to a single one but I still get the issue, just less often. I have tried to performSelector:onMainThread: whenever I run a selector that accesses or loads images (or just might internally) from within a completionBlock or other code that runs on a different thread.

I haven't had this crash the entire day today after I moved my user interface code to the main thread (it was called from a completion handler). I can't say 100% for sure whether this fixed it though.

I hope this helps a little. There's definitely something finicky going on, and if you do po 0x1459da60 (in lldb's command window, using the address provided by the exception) you'll see that it is the SKTextureAtlas texture list that is being modified. I hope that helps you pinpoint where the issue is coming from on your side.

CodeSmile
  • 64,284
  • 20
  • 132
  • 217
  • great answer! thank you very much, so you suggest to call user interface code from initWithSize with: [self performSelectorOnMainThread .. ] right? ...and not simply [self aMethod] – Ilario Mar 18 '14 at 16:31
  • It's more an observation paired with a hunch, but in general if you suspect some code to be running in parallel with texture loading (that happens in the background) then it may be worth trying to run that particular code on the main thread, or after the texture preloading completion handler is called. I'm still not 100% sure whether my issue is fixed or what exactly might have fixed it due to the issue being so sporadic. – CodeSmile Mar 18 '14 at 16:41
  • I did load the textures in the background, and the rest on the main thread, but nothing has changed :( – Ilario Mar 20 '14 at 09:08
  • now i'm trying to load only texture from initWithSize, and after load texture i call method that load UI ... but still do not know if it will work – Ilario Mar 20 '14 at 14:44
  • 1
    Can't say much other than that I managed to get rid of the errors. One part of the problem is stacking code inside completionHandlers or other callback blocks cause they may be running on a separate thread. It may also help to have a separate loading scene to guarantee no other code is running while textures are being preloaded. – CodeSmile Mar 20 '14 at 18:52
4

From what I can tell this a sprite kit bug in the sprite kit method:

preloadTextures: withCompletionHandler:

The only way I was able to fix this was by removing this method completely. According to apple docs the textures also get loaded if you access the size property. So my workaround is just to do exactly that:

for (SKTexture *texture in self.texturesArray) {
    texture.size;
}

It's not pretty but it works!

Zalykr
  • 1,524
  • 1
  • 10
  • 20
  • Did it at least fix your problem? Or did you eventually find another solution? – Zalykr Aug 19 '14 at 09:07
  • This actually worked for me too. I just did NSLog(@"%f", texture.size.width); to avoid the unused variable warning. – Reinaldo Oct 12 '15 at 00:18
2

I had the same problem, when I tried to preload two simple animations. I tried to preload the animations in a dictionary and have them ready to be called via a string key. Here is what I tried

-(void)setupAnimDict {
    animDict = [[NSMutableDictionary alloc] init];

    [animDict setObject:[self animForName:@"blaze" frames:4] forKey:@"blaze"];
    [animDict setObject:[self animForName:@"flame" frames:4] forKey:@"flame"];
}

-(SKAction *)animForName:(NSString *)name frames:(int)frames {
    NSArray *animationFrames = [self setupAnimationFrames:name base:name num:frames];
    SKAction *animationAction = [SKAction animateWithTextures:animationFrames     timePerFrame:0.10 resize:YES restore:NO];
    return [SKAction repeatActionForever:animationAction];
}

-(NSArray *)setupAnimationFrames:(NSString *)atlasName base:(NSString *)baseFileName num:(int)numberOfFrames {

    [self preload:baseFileName num:numberOfFrames];

    NSMutableArray *frames = [NSMutableArray arrayWithCapacity:numberOfFrames];
    SKTextureAtlas *atlas = [SKTextureAtlas atlasNamed:atlasName];
    for (int i = 0; i < numberOfFrames; i++) {
        NSString *fileName = [NSString stringWithFormat:@"%@%01d.png", baseFileName, i];
        [frames addObject:[atlas textureNamed:fileName]];
    }

    return frames;
}

-(void)preload:(NSString *)baseFileName num:(int)numberOfFrames {

    NSMutableArray *frames = [NSMutableArray arrayWithCapacity:numberOfFrames];

    for (int i = 0; i < numberOfFrames; i++) {
        NSString *fileName = [NSString stringWithFormat:@"%@%01d.png", baseFileName, i];
        [frames addObject:[SKTexture textureWithImageNamed:fileName]];
    }

    [SKTexture preloadTextures:frames withCompletionHandler:^(void){}];
}

When I called the setupDict method I sometimes got the same error as you. The problem was that preloading of my two animations run into each other. I got rid of the error by changing the

[SKTexture preloadTextures:frames withCompletionHandler:^(void){}];

to

if ([baseFileName isEqualToString:@"blaze"]) {
        [SKTexture preloadTextures:frames withCompletionHandler:^{
            [self setupFlame];
        }];
    } else {
        [SKTexture preloadTextures:frames withCompletionHandler:^(void){}];
    }

so that the first preloading was done before I attempted to preload the other.

I don't know if this is your problem, but if it is let us know.

Bob Ueland
  • 1,804
  • 1
  • 15
  • 24
  • thanks for your answer, i'm trying to solve this issue loading SKTextureAtlas in initWithSize and after all texture come from this array of textures, without preloading anything.. this works well without crash and all sprite you see properly.. but i'm not sure that this is the right way – Ilario Mar 25 '14 at 10:22
1

Same thing still happening for me in Xcode 6.3 beta / Swift 1.2. Here is a temporary fix that has worked for me.

SKTextureAtlas.preloadTextureAtlases([SKTextureAtlas(named: "testAtlas")], withCompletionHandler: {
    dispatch_async(dispatch_get_main_queue(), {
        handler()
    })
})

I actually wrapped this in a function so that all preloads route through it. That way if it gets fixed on the SpriteKit side, or if there are major flaws with this approach, I can remove the dispatch.

Ryan C
  • 70
  • 2
  • 10