在启动我的 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 中(但仍然具有相同的频率)。这就是我构建节点树的方式:
显示菜单时预
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
在 中
LEGameplayScene
,LEWorldBuilder
从包含节点位置的 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
- 解析 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