#synchronized the writing queue makes thread safe? - ios

NSObject *token = [[NSObject alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
#synchronized (token) {
array = [NSArray arrayWithObject:#"1"];
}
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
NSLog(#"%#",array);
}
});
This code won't crash, even there is no #synchronized in the read queue.
As compared,
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
[lock lock];
array = [NSArray arrayWithObject:#"1"];
[lock unlock];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
NSLog(#"%#",array);
}
});
This code will crash as array is not thread safe. How does this one-side #synchronized keeps the thread safe?

How does this one-side #synchronized keeps the thread safe?
It doesn’t.
Using #synchronized (or any synchronization method) for writes, but not for reads, is not thread-safe. Just because your code does not readily crash does not mean it is thread-safe. Both reads and writes must be synchronized. Neither of these two examples, with unsynchronized reads, is thread-safe.
If you want to test for thread-safety, consider the thread sanitizer (TSAN). For example, when I ran the #synchronized example through TSAN, it reported:
WARNING: ThreadSanitizer: data race (pid=89608)
Read of size 8 at 0x7b0c0007bce8 by thread T1:
#0 __29-[ViewController viewDidLoad]_block_invoke.14 <null> (MyApp12:x86_64+0x100002e3d)
#1 __tsan::invoke_and_release_block(void*) <null> (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x74d7b)
#2 _dispatch_client_callout <null> (libdispatch.dylib:x86_64+0x40af)
Previous write of size 8 at 0x7b0c0007bce8 by thread T3 (mutexes: write M1679):
#0 __29-[ViewController viewDidLoad]_block_invoke <null> (MyApp12:x86_64+0x100002c02)
#1 __tsan::invoke_and_release_block(void*) <null> (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x74d7b)
#2 _dispatch_client_callout <null> (libdispatch.dylib:x86_64+0x40af)
Location is heap block of size 48 at 0x7b0c0007bcc0 allocated by main thread:
#0 malloc <null> (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x5239a)
#1 _Block_object_assign <null> (libsystem_blocks.dylib:x86_64+0x14fc)
#2 _Block_copy <null> (libsystem_blocks.dylib:x86_64+0x141c)
#3 -[ViewController viewDidLoad] <null> (MyApp12:x86_64+0x100002878)
#4 -[NSViewController _sendViewDidLoad] <null> (AppKit:x86_64+0xb41ce)
#5 start <null> (libdyld.dylib:x86_64+0x15620)
Mutex M1679 (0x7b0400004020) created at:
#0 objc_sync_enter <null> (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x72a45)
#1 __29-[ViewController viewDidLoad]_block_invoke <null> (MyApp12:x86_64+0x100002b51)
#2 __tsan::invoke_and_release_block(void*) <null> (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x74d7b)
#3 _dispatch_client_callout <null> (libdispatch.dylib:x86_64+0x40af)
Thread T1 (tid=3657833, running) is a GCD worker thread
Thread T3 (tid=3657837, running) is a GCD worker thread
SUMMARY: ThreadSanitizer: data race (/Users/.../Library/Developer/Xcode/DerivedData/MyApp-ewpoprpgpjgbmrcrkyycvogiazhl/Build/Products/Debug/MyApp12.app/Contents/MacOS/MyApp:x86_64+0x100002e3d) in __29-[ViewController viewDidLoad]_block_invoke.14+0x6d
Whichever synchronization mechanism you use (whether #synchronized, locks, or GCD), both reads and writes must be synchronized. Data race problems are notoriously difficult to manifest, and, as such, one should hesitate to draw any conclusions from an absence of a crash.

Why does it work? Without going into technical details, and really just guessing, I think this has to do with the way in which the multithreading is implemented.
Reason is based on the following, which will also NOT crash
NSLock * lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
int i = 0;
while (1) {
lock.lock;
// Do some work
for ( int j = 0; j < 10000; j ++ )
{
double x = atan ( j );
}
array = [NSArray arrayWithObject:#( i )];
lock.unlock;
i ++;
}
});
This is essentially the same as your code, but inside the lock I do some serious work. I think that work is 'hard' enough for the dispatcher to pause the other thread and I think once that work is done, the multithreading dispatcher says enough, time for some other threads, and then this one in turn is put on hold long enough for the other one to finish unmolested.
PS : That said, I think you will get different results as the load on the system changes and you need to properly sync access.

Related

iOS Core Data dispatch_async background queuing crash

I'm getting a non-reproduceable crash and I'm not sure why. I'm caching images on a background queue. The image names are properties on a Core Data NSManagedObject subclass, CCCard. At the same time, I have a collection view that is also accessing these CCCards.
Here is the relevant code and notes to follow.
//CCDeckViewController.m --------------------------------------
- (void)viewDidLoad {
// This will fetch the CCCard objects from Core Data.
self.cards = [[CCDataManager shared] cards];
[self cacheImages];
}
- (void)cacheCardImages {
// Because these are NSManagedObjects, I access them in the background
// via their object ID. So pull the IDs out here.
NSArray *cardIds = [self.cards valueForKey:#"objectID"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
for (NSManagedObjectID *cardId in cardIds) {
// Fetch the actual CCCard object.
CCCard *card = (CCCard *)[[CCDataManager shared] objectWithId:cardId];
// Cache the card's image if it's not already there.
NSString *key = [card thumbnailCacheKey];
if ([self.cardImageCache objectForKey:key]) {
continue;
}
CCDiscardableImage *discardable = [CCHelper decompressedImageForPath:key size:[card thumbnailSize] tintable:[card tintable]];
[self.cardImageCache setObject:discardable forKey:key];
}
});
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
CCCard *card = self.cards[indexPath.item];
// This line calls the code that crashes.
UIColor *color = [card color];
// More code that returns the cell.
}
// CCCard.m --------------------------------------
- (UIColor *)color {
// Crash happens on this line.
if (self.colorId.integerValue == 0) {
// some code
}
}
Here are parts of the stack trace from the Crashlytics report I think are most relevant:
Thread #0: Crashed: com.apple.main-thread
EXC_BREAKPOINT 0x000000000000defe
0 CoreData 0x24c1b070 _sharedIMPL_pvfk_core + 247
1 CoreData 0x24c1b071 _sharedIMPL_pvfk_core + 248
2 myapp 0x4286f -[CCCard color] (CCCard.m:37)
3 myapp 0x40bbb -[CCDeckViewController collectionView:cellForItemAtIndexPath:] (CCDeckViewController.m:127)
Thread #4: com.apple.root.utility-qos
0 libsystem_pthread.dylib 0x2321920c RWLOCK_GETSEQ_ADDR
1 libsystem_pthread.dylib 0x23219295 pthread_rwlock_unlock + 60
2 libobjc.A.dylib 0x22cb8e01 rwlock_tt<false>::unlockRead() + 8
3 libobjc.A.dylib 0x22cb5af5 lookUpImpOrForward + 488
4 libobjc.A.dylib 0x22cb5903 _class_lookupMethodAndLoadCache3 + 34
5 libobjc.A.dylib 0x22cbbd7b _objc_msgSend_uncached + 26
6 CoreData 0x24c1d3ab _PFObjectIDFastEquals64 + 38
7 CoreFoundation 0x233eeed4 CFBasicHashFindBucket + 1820
8 CoreFoundation 0x233ee775 CFDictionaryGetValue + 116
9 CoreData 0x24c17037 _PFCMT_GetValue + 122
10 CoreData 0x24c16ec7 -[NSManagedObjectContext(_NSInternalAdditions) _retainedObjectWithID:optionalHandler:withInlineStorage:] + 58
11 CoreData 0x24c1b45d _PF_FulfillDeferredFault + 940
12 CoreData 0x24c1afcf _sharedIMPL_pvfk_core + 86
13 myapp 0x42991 -[CCCard imagePath] (CCCard.m:53)
14 myapp 0x41d5b __39-[CCDeckViewController cacheCardImages]_block_invoke (CCDeckViewController.m:295)
It's frustrating because it never happens during development, so can't test any theories. I'm trying to understand the code and crash report to figure out the problem now. My guess is that it has something to do with faulting because it accesses the CCCard object's instance method, but then crashes when trying to read the property from it. But why?
You are violating thread confinement.
Per the Core Data documentation, a NSManagedObjectContext and any NSManagedObject associated with it must only be accessed on the queue that it is assigned to.
Your dispatch_async violates that rule and needs to be corrected. There is no longer a design where you can use Core Data in a dispatch_async like that.
If you want the processing to be asynchronous then you should spawn a private NSManagedObjectContext that is a child of your main NSManagedObjectContext and then execute a -performBlock: against it. That will give you background processing that is configured properly.
Also, I highly recommend turning on -com.apple.CoreData.ConcurrencyDebug 1 as that will catch issues like this during development.

dispatch_async block on main queue is not executed in a modal run loop

I have the following code that checks the RunLoop in an outerloop and then dispatches to the main_thread in an inner loop using dispatch_after. I have two cases where this is called, once when a button is pressed on the nav bar, and the other case is during viewDidAppear. When the code is called in the later, it remains stuck in the RunLoop and my breakpoint never hits my dispatch_after block. Why is dispatch_after getting blocked? I need to do this to update some progress indicators even while the code is executing. Is is obvious why this might work in one case an not the other? I looked at the stack and there is not much else in the stack.
// case 1:
// does not hit breakpoint
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.4 * NSEC_PER_SEC), dispatch_get_main_queue(), ^
{
[ self longRunningOp ];
});
// case 2:
// This works fine!
[ self longRunningOp ];
-(void) longRunningOp
{
bool dispatched = false;
while (!finished)
{
if (dispatched)
{
// reusing the same date to avoid buffers building up!
date = [ date initWithTimeIntervalSinceNow:0 ];
[ [ NSRunLoop currentRunLoop ] runMode: NSDefaultRunLoopModes beforeDate:date ];
continue;
}
dispatched = true;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.001 * NSEC_PER_SEC), dispatch_get_main_queue(), ^() {
// In second case breakpoint never gets here!
// OPENGL OPS HERE!
dispatched = false;
}
} );
}
I also noticed on other difference.
Success Case:
In the case where it works, the UIApplicationMain makes a call out to CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION, this happens when the user presses a nav button.
Failure Case:
while in the case where it fails, the UIApplicationMain invokes into CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE, this happens on viewDidAppear.
In your failure case, where you see CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE on the stack, the run loop is “servicing” the main dispatch queue, which means that it is running all the blocks that have been queued on the main queue. One of those blocks ends up sending the viewDidAppear message. So the stack looks something like this:
-[ViewController viewDidAppear:]
-[UIViewController _setViewAppearState:isAnimating:]
-[UIViewController _endAppearanceTransition:]
-[UINavigationController navigationTransitionView:didEndTransition:fromView:toView:]
__49-[UINavigationController _startCustomTransition:]_block_invoke
-[_UIViewControllerTransitionContext completeTransition:]
__53-[_UINavigationParallaxTransition animateTransition:]_block_invoke95
-[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:]
-[UIViewAnimationState sendDelegateAnimationDidStop:finished:]
-[UIViewAnimationState animationDidStop:finished:]
CA::Layer::run_animation_callbacks(void*)
_dispatch_client_callout
_dispatch_main_queue_callback_4CF
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
__CFRunLoopRun
CFRunLoopRunSpecific
GSEventRunModal
UIApplicationMain
main
start
Now suppose that in viewDidAppear:, you put another block on the main dispatch queue and then you run the main run loop recursively, so the stack looks like this:
__CFRunLoopRun
CFRunLoopRunSpecific
-[NSRunLoop(NSRunLoop) runMode:beforeDate:]
-[ViewController viewDidAppear:]
-[UIViewController _setViewAppearState:isAnimating:]
-[UIViewController _endAppearanceTransition:]
-[UINavigationController navigationTransitionView:didEndTransition:fromView:toView:]
__49-[UINavigationController _startCustomTransition:]_block_invoke
-[_UIViewControllerTransitionContext completeTransition:]
__53-[_UINavigationParallaxTransition animateTransition:]_block_invoke95
-[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:]
-[UIViewAnimationState sendDelegateAnimationDidStop:finished:]
-[UIViewAnimationState animationDidStop:finished:]
CA::Layer::run_animation_callbacks(void*)
_dispatch_client_callout
_dispatch_main_queue_callback_4CF
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
__CFRunLoopRun
CFRunLoopRunSpecific
GSEventRunModal
UIApplicationMain
main
start
Now you seem to think that the run loop should service the main dispatch queue again in this recursive call. But it cannot do that. The main dispatch queue is a serial queue, which means that it never runs more than one block at a time. There is already a main queue block running, called CA::Layer::run_animation_callbacks(void*). No other main queue block can start until that block returns. The run loop knows this, so it doesn't try to service the main queue in this recursive call.
Since the code you posted in your question doesn't actually do any work, it's difficult to give you any specific help. However, you mentioned OpenGL. Apple's OpenGL ES Programming Guide for iOS has a chapter titled “Concurrency and OpenGL ES” that discusses how to move processing to a background thread or queue.
After much debugging and thought I have something that works.I can't explain why it does, but it resolves it for me. I can explain why I made the change. I examined the the stack carefully for the success case and saw that it works if the top of stack functions corresponds to a performSelector:withObject:afterDelay call as against a dispatch_async call. This gave me a clue for what I could do. I replaced my outermost dispatch_asynch with performSelector:withObject:afterDelay. It works without a deadlock now. I have listed the failure case and the success case below.
// This causes the queue to block; -- FAILURE CASE
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.4 * NSEC_PER_SEC), dispatch_get_main_queue(), ^
{
[ self longRunningOp ];
});
// This keeps the queue running;!!!!! - SUCCESS CASE
[ self performSelector:#selector(longRunningOp) withObject:nil afterDelay:0.4 ];
This is my naive attempt at the reasoning for why this works.
In my RunMode invocation I am using the current loop, so this call depends on how the current loop is setup. Apparently the current loop is setup differently at the top level based on whether one uses performSelector:withObject:afterDelay as against using dispatch_async. It appears further that clicking on navigation bar results in performSelector:withObject:afterDelay invocations and that is a more desirable state to have to the top level stack in than to have the top level be a dispatch async.
I doubt its the best explanation. But its the best I have at the moment.

iOS screen become black little by little

I'm implementing an iOS app. and found that sometimes the screen became black little by little.
for example, in a view controller, there are a collection view, a button, a page controller, and sometimes I found the collection view became black(or invisible), only black background is shown, after 1-2 seconds, the button is gone, then the whole screen is black. but if I put the app into background, then bring it back, everything is ok.
here is the code:
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSTimeInterval timeLeft = [UIApplication sharedApplication].backgroundTimeRemaining - 1.0;
if(timeLeft &lt 0)
{
completionHandler(UIBackgroundFetchResultNoData);
}
//create new thread to do something, and sleep at current thread
...create new threads
[NSThread sleep:timeLeft];
completionHandler(UIBackgroundFetchResultNewData);
});
}
the issue could be reproduced if I repeat the following actions several times:
Do some thing
Put app to background(press home button)
Bring app back to foreground
repeat 1 - 3
we found following error in organizer for our app:
: CoreAnimation: warning, deleted thread with uncommitted CATransaction; set CA_DEBUG_TRANSACTIONS=1 in environment to log backtraces.
after added , I got the following log:
Myapp[11496] : CoreAnimation: warning, deleted thread with uncommitted CATransaction; created by:
0 QuartzCore 0x31ca6a75 + 268
1 QuartzCore 0x31ca6929 + 224
2 QuartzCore 0x31cabddb + 86
3 QuartzCore 0x31cab9f7 + 150
4 UIKit 0x3203f919 + 344
5 UIKit 0x320bb11b + 138
6 UIKit 0x322b0ebf + 218
7 UIKit 0x322b1169 + 104
8 UIKit 0x322b1735 + 36
9 Myapp 0x002e538d __61-[AppDelegate application:performFetchWithCompletionHandler:]_block_invoke + 632
10 libdispatch.dylib 0x3a487d53 + 10
11 libdispatch.dylib 0x3a48d689 + 228
12 libdispatch.dylib 0x3a48d8dd + 56
13 libsystem_pthread.dylib 0x3a5b8c17 _pthread_wqthread + 298
14 libsystem_pthread.dylib 0x3a5b8adc start_wqthread + 8
my questions is:
how could application:performFetchWithCompletionHandler cause animation issue?
to answer questions:
1. I'm sure that phone is not going to sleep
2. source code. sorry the project is too big
Check that you are working with UI on main thread only, this should be the case.
Edit: - I've seen this one, although never used it(written by Peter Steinberger).
Also this answer can help avoiding several more problems.
The thread created to perform your fetch invokes a call to update your ui, when it finishes performing whatever task it is doing, and its no longer needed it is deallocated. UIUpdates MUST be performed on the main thread or they might not be performed right away.
Have you tried dispatching to the main thread whatever ui update you are performing there?
It's hard to know what you your completion handler is doing, but if it's updating UI try to dispatch them on the main thread
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSTimeInterval timeLeft = [UIApplication sharedApplication].backgroundTimeRemaining - 1.0;
if(timeLeft < 0)
{
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(UIBackgroundFetchResultNoData);
});
}
//create new thread to do something, and sleep at current thread
...create new threads
[NSThread sleep:timeLeft];
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(UIBackgroundFetchResultNewData);
});
});
}

RestKit Core Data NSError dealloc Crash

Trying to get to the bottom of an issue I've been seeing in production builds and FINALLY was able to reproduce it while testing. Using RestKit v0.23.1, when doing an RKManagedObjectRequestOperation using the following code (while plugged into instruments) I get "An Objective-C message was sent to a deallocated 'NSError' object (zombie)" and the app crashes every time there's objects in the response JSON - if the response is something like "objects = ();" there's no crash - so I'm guessing it's somewhere in the RestKit/Core Data mapping or storage?
RKManagedObjectRequestOperation *objectRequestOperation = [_objectManager managedObjectRequestOperationWithRequest:request managedObjectContext:_objectManager.managedObjectStore.mainQueueManagedObjectContext success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
DDLogInfo(#"INSIDE SUCCESS BLOCK");
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
DDLogInfo(#"INSIDE ERROR BLOCK");
}];
[objectRequestOperation setWillMapDeserializedResponseBlock:^id(id deserializedResponseBody) {
DDLogInfo(#"Response JSON: %#", deserializedResponseBody);
return deserializedResponseBody;
}];
objectRequestOperation.savesToPersistentStore = YES;
[objectRequestOperation start];
The raw JSON is properly logged inside the setWillMapDeserializedResponseBlock, but the logs inside the success and error block are never reached. Here is the stack trace I get back from crashlytics:
Thread : Crashed: NSOperationQueue Serial Queue
0 libobjc.A.dylib 0x37dd4626 objc_msgSend + 5
1 Foundation 0x2df5802d -[NSError dealloc] + 60
2 libobjc.A.dylib 0x37dd9b6b objc_object::sidetable_release(bool) + 174
3 libobjc.A.dylib 0x37dda0d3 (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 358
4 CoreFoundation 0x2d569501 _CFAutoreleasePoolPop + 16
5 Foundation 0x2df69999 -[__NSOperationInternal _start:] + 1064
6 Foundation 0x2e00d745 __NSOQSchedule_f + 60
7 libdispatch.dylib 0x382b8cbd _dispatch_queue_drain + 488
8 libdispatch.dylib 0x382b5c6f _dispatch_queue_invoke + 42
9 libdispatch.dylib 0x382b95f1 _dispatch_root_queue_drain + 76
10 libdispatch.dylib 0x382b98dd _dispatch_worker_thread2 + 56
11 libsystem_pthread.dylib 0x383e4c17 _pthread_wqthread + 298
This isn't a problem with RestKit. I've seen this problem frequently and it actually looks like the over-release actually happens in Apple's code. The problem happens when you try to save to a Core Data store and it fails. Core Data reports an error as it should, but that error is mishandled.
I had a few scenarios causing the save failures and this is how I fixed them:
The data store is inaccessible because of the Data Protection API.
Either busy wait and let your app fail to launch like this:
while(![[UIApplication sharedApplication] isProtectedDataAvailable]) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5f]];
}
Or disable protection if the data in your store isn't sensitive like this:
[_coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:url
options:#{NSPersistentStoreFileProtectionKey:NSFileProtectionNone}
error:&error];
The important thing is that you don't try to save until you can access the file. If you can re-structure you code to prevent accessing the database when it is inaccessible that is good too. You can use the Data Protection API application delegate methods to trigger that mechanism.
The data store is corrupted - The best thing to do here is to delete the store and start over. Here is a good way to detect a corrupted store using the sqlite library directly.
#import <sqlite3.h>
sqlite3 *dbConnection;
if (sqlite3_open([[url absoluteString] UTF8String], &dbConnection) != SQLITE_OK) {
NSLog(#"[SQLITE] Unable to open database!");
}
sqlite3_stmt *statement = nil;
sqlite3_prepare_v2(dbConnection, "PRAGMA quick_check;", -1, &statement, NULL);
NSString *result = nil;
while (sqlite3_step(statement) == SQLITE_ROW) {
for (int i=0; i<sqlite3_column_count(statement); i++) {
int colType = sqlite3_column_type(statement, i);
if (colType == SQLITE_TEXT) {
const unsigned char *col = sqlite3_column_text(statement, i);
result = [NSString stringWithFormat:#"%s", col];
} else {
NSLog(#"[SQLITE] UNKNOWN DATATYPE");
}
}
}
sqlite3_close(dbConnection);
This runs a sqlite PRAGMA query to perform an integrity check. I use quick_check, but you could also use integrity_check if you are willing to wait the extra time. You can tell things are good using [result isEqualToString:#"ok"]

NSGenericException reason Collection <NSConcreteMapTable: xxx>

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

Resources