0

在启动我的 iOS SpriteKit 项目后,我在后台预加载了一些精灵。NSGenericException每 10 次左右的方法都会以inside失败main。我怀疑[SKTexture loadImageData]与问题直接相关,因为它通过查找纹理来枚举集合。

我知道这个问题,但提出的解决方案并没有真正帮助。这是我的堆栈跟踪,感谢您的任何评论。

2015-02-23 12:03:11.299 Leaving Earth[568:141596] *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <NSConcreteMapTable: 0x17e0b840> was mutated while being enumerated.'
*** First throw call stack:
(0x294a449f 0x36c5ec8b 0x294a3f21 0x2a0f3a95 0x293cbd93 0x2c8035d1 0x2c7f7a9b 0x2c85aacd 0x2c7f73bf 0x2c7fca5f 0x2c7fc149 0x2c86a1df 0x2c870a2f 0x2c808127 0x4e19c7 0x4e91d9 0x2c807cab 0x2c804a35 0x2c836041 0x444183 0x2c3d8803 0x2c3d866b 0x30cc082b 0x2a3ca4e1 0x2945a0a5 0x2946a573 0x2946a50f 0x29468b11 0x293b63c1 0x293b61d3 0x307b40a9 0x2c9c47b1 0x12b251 0x371deaaf)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb) bt
* thread #1: tid = 0x2291c, 0x372a4dfc libsystem_kernel.dylib`__pthread_kill + 8, queue = 'com.apple.spritekit.renderQueue', stop reason = signal SIGABRT
frame #0: 0x372a4dfc libsystem_kernel.dylib`__pthread_kill + 8
frame #1: 0x37322d36 libsystem_pthread.dylib`pthread_kill + 62
frame #2: 0x37244908 libsystem_c.dylib`abort + 76
frame #3: 0x365809c8 libc++abi.dylib`abort_message + 88
frame #4: 0x3659a670 libc++abi.dylib`default_terminate_handler() + 268
frame #5: 0x36c5ef24 libobjc.A.dylib`_objc_terminate() + 192
frame #6: 0x36597de2 libc++abi.dylib`std::__terminate(void (*)()) + 78
frame #7: 0x365975a8 libc++abi.dylib`__cxa_throw + 112
frame #8: 0x36c5ed5e libobjc.A.dylib`objc_exception_throw + 250
frame #9: 0x294a3f20 CoreFoundation`__NSFastEnumerationMutationHandler + 128
frame #10: 0x2a0f3a94 Foundation`-[NSConcreteMapTable countByEnumeratingWithState:objects:count:] + 56
frame #11: 0x293cbd92 CoreFoundation`-[__NSFastEnumerationEnumerator nextObject] + 110
frame #12: 0x2c8035d0 SpriteKit`+[SKTextureAtlas(Internal) findTextureNamed:] + 284
frame #13: 0x2c7f7a9a SpriteKit`__26-[SKTexture loadImageData]_block_invoke + 1654
frame #14: 0x2c85aacc SpriteKit`SKSpinLockSync(int*, void () block_pointer) + 104
frame #15: 0x2c7f73be SpriteKit`-[SKTexture loadImageData] + 302
frame #16: 0x2c7fca5e SpriteKit`-[SKTexture(Private) load] + 174
frame #17: 0x2c7fc148 SpriteKit`+[SKTexture preloadTextures] + 320
frame #18: 0x2c86a1de SpriteKit`SKCRenderer::preRender() + 418
frame #19: 0x2c870a2e SpriteKit`SKCRenderer::renderScene(SKScene*, bool) + 94
frame #20: 0x2c808126 SpriteKit`-[SKView _renderContent] + 1102
frame #21: 0x004e19c6 libdispatch.dylib`_dispatch_client_callout + 22
frame #22: 0x004e91d8 libdispatch.dylib`_dispatch_barrier_sync_f_invoke + 96
frame #23: 0x2c807caa SpriteKit`-[SKView renderContent] + 82
frame #24: 0x2c804a34 SpriteKit`__29-[SKView setUpRenderCallback]_block_invoke + 120
frame #25: 0x2c836040 SpriteKit`-[SKDisplayLink _callbackForNextFrame:] + 248
frame #26: 0x00444182 libglInterpose.dylib`-[DYDisplayLinkInterposer forwardDisplayLinkCallback:] + 270
frame #27: 0x2c3d8802 QuartzCore`CA::Display::DisplayLinkItem::dispatch() + 98
frame #28: 0x2c3d866a QuartzCore`CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 366
frame #29: 0x30cc082a IOMobileFramebuffer`IOMobileFramebufferVsyncNotifyFunc + 90
frame #30: 0x2a3ca4e0 IOKit`IODispatchCalloutFromCFMessage + 256
frame #31: 0x2945a0a4 CoreFoundation`__CFMachPortPerform + 132
frame #32: 0x2946a572 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 34
frame #33: 0x2946a50e CoreFoundation`__CFRunLoopDoSource1 + 346
frame #34: 0x29468b10 CoreFoundation`__CFRunLoopRun + 1608
frame #35: 0x293b63c0 CoreFoundation`CFRunLoopRunSpecific + 476
frame #36: 0x293b61d2 CoreFoundation`CFRunLoopRunInMode + 106
frame #37: 0x307b40a8 GraphicsServices`GSEventRunModal + 136
frame #38: 0x2c9c47b0 UIKit`UIApplicationMain + 1440
* frame #39: 0x0012b250 Leaving Earth`main(argc=1, argv=0x00276a68) + 116 at main.m:16
(lldb)

更新

在为子类 sprite 的副本明确地重新使用相同的纹理后,异常只发生在 main 中(但仍然具有相同的频率)。这就是我构建节点树的方式:

  1. 显示菜单时预SKSpriteView加载最后播放的关卡。


    @implementation LESpriteView
    
    //...
    
    [self.gamePlayScene loadLevel:[LEModel getLastPlayedLevel] completionHandler:^{
        if (self.scene == self.loadingScene) {
        SKTransition* transition = [SKTransition crossFadeWithDuration: 0.2];
        [self presentScene:self.gamePlayScene transition: transition];
    }}];
    
    //...
    
    @end
    
  2. 在 中LEGameplaySceneLEWorldBuilder从包含节点位置的 csv 文件中获取数据。


@implementation LEGameplayScene
//...

-(void) loadLevel: (LELevel) level completionHandler:(LELoadCompletionHandler) handler {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        [self loadLevel:level];        
        if (!handler) return;
        dispatch_async(dispatch_get_main_queue(), ^{
            handler();
        });
    });
}

-(void) loadLevel: (LELevel) level {
    self.gameState = LEGameStateLoading;
    self.scene.paused = YES;
    self.view.paused = YES;

    [LESpriteFactory setupSharedSprites];

    //...

    //Load the world
    NSDictionary* infoDict = [LECommonUtilities getInfoForLevelNumber:level];
    [self.world removeFromParent];
    self.world = [[LEWorld alloc] init];
    [self addChild:self.world];
    [LEWorldBuilder buildWorld:self.world basedOnInfo:infoDict];

    //...
}

@end
  1. 解析 CSV 并通过调用的LEWorldBuilder方法创建精灵,LESpriteFactory然后将其添加到“根”世界节点的不同层和/或将它们分组到数组中以动态添加(对象剔除)。

LESpriteFactory处理新对象的创建,如下所示:

    @implementation LESpriteFactory


//Cached sprites
LEItemNode* star;
LEItemNode* cloud;
LEItemNode* asteroid;
LEItemNode* satellite;
LECountdownLabel* label;
LETimeBreakerNode* timeBreaker;
LERepairMarkerNode* repairMarker;    

    //...        

    static BOOL alreadyLoaded = NO;

       /** Loads all shared sprites to copy them on demand */
        + (void) setupSharedSprites {
            if (!alreadyLoaded) {
                star = [[LEStarNode alloc] init];
                asteroid = [[LEAsteroidNode alloc] init];
                timeBreaker = [[LETimeBreakerNode alloc] init];
                label = [[LECountdownLabel alloc] initWithFontSize:10];
                repairMarker = [[LERepairMarkerNode alloc] init];
                cloud = [[LECloudNode alloc] init];
                satellite = [[LESatelliteNode alloc] init];

                //Start idle action after initialisation (during wouldn't work)
                [star runIdleAction];
                [repairMarker runIdleAction];
                [timeBreaker runIdleAction];
                [satellite runIdleAction];
            }
            alreadyLoaded = YES;
        }

    + (LEItemNode*) star {
        return [star copy];
    }

    //...

    @end

虽然每个精灵缓存并重复使用它SKTexture的副本:

@implementation LEStarNode

static SKTexture* sharedTexture;

-(id) init {
    if (!sharedTexture) {
        sharedTexture = [SKTexture textureWithImageNamed:@"LE_Collectable_Star.png"];
    }

    self = [super initWithTexture:sharedTexture];

   //...
}

//...

@end
4

1 回答 1

0

也时不时碰到这个问题。当我预加载一堆纹理时似乎会发生这种情况。据我所知,更新方法在预加载完全完成之前启动并开始循环遍历节点、纹理等......这会导致“在枚举时发生变异”错误。

我可以通过创建一个在所有预加载完成后设置为 true 的 BOOL 来防止发生此错误。我将这个 BOOL 放在更新方法中,如下所示:

-(void)update:(CFTimeInterval)currentTime {
    if(allAssetsLoaded == true) {
        // all update code here
    }
}
于 2015-02-23T13:30:17.993 回答