我如何规避这个令人讨厌的SKTexture错误?

时间:2015-02-23 10:44:28

标签: ios sprite-kit sktexture

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

我知道this problem,建议的解决方案并没有真正帮助。这是我的堆栈跟踪,感谢任何评论。

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)

更新

在为子类精灵的副本显式重复使用相同的纹理之后,异常仅发生在主要(但仍然具有相同的频率)。这就是我构建节点树的方式:

  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. LEGameplayScene中,LEWorldBuilder从包含节点位置的csv文件中获取数据。


  3. @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. LEWorldBuilder解析CSV并通过调用LESpriteFactory的方法创建精灵,然后将其添加到&#34; root&#34;的不同层。世界节点和/或将它们分组在数组中以动态添加(对象剔除)。
    2. 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
      

1 个答案:

答案 0 :(得分:0)

还不时遇到这个问题。这似乎发生在我预加载一堆纹理时。从我可以收集的内容来看,更新方法在预加载完成之前启动并开始循环通过节点,纹理等...这导致“在枚举时发生了突变”错误。

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

-(void)update:(CFTimeInterval)currentTime {
    if(allAssetsLoaded == true) {
        // all update code here
    }
}