EDIT:
I believe I have found a potential cause of the exception:
I had a hunch that multiple transactions were trying to run locally on the same node and causing contention because of the tall stack trace. I ended up saving the currently running transactions in a set and testing for a running transaction at the node before starting another one. Here is the code:
@interface MyViewController ()
@property (nonatomic, strong) NSMutableSet *transactions; // holds transactions to prevent contention
@property (nonatomic, strong) NSMutableDictionary *values; // holds most recent values to avoid callback roundtrip
@end
@implementation MyViewController
-(NSArray*)firebasePathTokens:(Firebase*)firebase
{
NSMutableArray *tokens = [NSMutableArray array];
while(firebase.name)
{
[tokens insertObject:firebase.name atIndex:0];
firebase = firebase.parent;
}
return tokens;
}
// workaround for private firebase.path
-(NSString*)firebasePath:(Firebase*)firebase
{
return firebase ? [@"/" stringByAppendingString:[[self firebasePathTokens:firebase] componentsJoinedByString:@"/"]] : nil;
}
- (void)runTransaction:(Firebase*)firebase
{
NSString *firebasePath = [self firebasePath:firebase];
if([self.transactions containsObject:firebasePath])
{
NSLog(@"transaction already in progress: %@", firebasePath);
return;
}
[self.transactions addObject:firebasePath];
NSNumber *myValue = @(42);
[firebase runTransactionBlock:^FTransactionResult *(FMutableData *currentData) {
currentData.value = myValue;
return [FTransactionResult successWithValue:currentData];
} andCompletionBlock:^(NSError *error, BOOL committed, FDataSnapshot *snapshot) {
values[firebasePath] = snapshot.value; // short example for brevity, the value should really be merged into a hierarchy of NSMutableDictionary at the appropriate node
[self.transactions removeObject:firebasePath];
} withLocalEvents:NO];
}
@end
I am getting this problem as well, here is my stack trace:
2014-05-01 12:18:31.641 MY_APP_NAME______[6076:60b] {
UncaughtExceptionHandlerAddressesKey = (
0 CoreFoundation 0x030131e4 __exceptionPreprocess + 180
1 libobjc.A.dylib 0x02d928e5 objc_exception_throw + 44
2 CoreFoundation 0x030a2cf5 __NSFastEnumerationMutationHandler + 165
3 MY_APP_NAME______ 0x000ecf53 -[FTree forEachChild:] + 290
4 MY_APP_NAME______ 0x0011184a -[FRepo(Transaction) pruneCompletedTransactionsBelowNode:] + 373
5 MY_APP_NAME______ 0x00111898 __58-[FRepo(Transaction) pruneCompletedTransactionsBelowNode:]_block_invoke + 43
6 MY_APP_NAME______ 0x000ed01d -[FTree forEachChild:] + 492
7 MY_APP_NAME______ 0x0011184a -[FRepo(Transaction) pruneCompletedTransactionsBelowNode:] + 373
8 MY_APP_NAME______ 0x00111898 __58-[FRepo(Transaction) pruneCompletedTransactionsBelowNode:]_block_invoke + 43
9 MY_APP_NAME______ 0x000ed01d -[FTree forEachChild:] + 492
10 MY_APP_NAME______ 0x0011184a -[FRepo(Transaction) pruneCompletedTransactionsBelowNode:] + 373
11 MY_APP_NAME______ 0x00111898 __58-[FRepo(Transaction) pruneCompletedTransactionsBelowNode:]_block_invoke + 43
12 MY_APP_NAME______ 0x000ed01d -[FTree forEachChild:] + 492
13 MY_APP_NAME______ 0x0011184a -[FRepo(Transaction) pruneCompletedTransactionsBelowNode:] + 373
14 MY_APP_NAME______ 0x00111898 __58-[FRepo(Transaction) pruneCompletedTransactionsBelowNode:]_block_invoke + 43
15 MY_APP_NAME______ 0x000ed01d -[FTree forEachChild:] + 492
16 MY_APP_NAME______ 0x0011184a -[FRepo(Transaction) pruneCompletedTransactionsBelowNode:] + 373
17 MY_APP_NAME______ 0x001127ea -[FRepo(Transaction) rerunTransactionQueue:atPath:] + 2888
18 MY_APP_NAME______ 0x00111a7f -[FRepo(Transaction) rerunTransactionsAndUpdateVisibleDataForPath:] + 422
19 MY_APP_NAME______ 0x001114c7 __50-[FRepo(Transaction) sendTransactionQueue:atPath:]_block_invoke + 3092
20 MY_APP_NAME______ 0x000e61d6 -[FPersistentConnection ackPuts] + 286
21 MY_APP_NAME______ 0x000e492a __38-[FPersistentConnection sendListen:::]_block_invoke + 778
22 MY_APP_NAME______ 0x000e268a -[FPersistentConnection onDataMessage:withMessage:] + 465
23 MY_APP_NAME______ 0x000d733a -[FConnection onDataMessage:] + 106
24 MY_APP_NAME______ 0x000d7293 -[FConnection onMessage:withMessage:] + 282
25 MY_APP_NAME______ 0x000d4ba4 -[FWebSocketConnection appendFrame:] + 402
26 MY_APP_NAME______ 0x000d4c73 -[FWebSocketConnection handleIncomingFrame:] + 161
27 MY_APP_NAME______ 0x000d4cab -[FWebSocketConnection webSocket:didReceiveMessage:] + 40
28 MY_APP_NAME______ 0x000cfbe1 __31-[FSRWebSocket _handleMessage:]_block_invoke + 151
29 libdispatch.dylib 0x0366f7b8 _dispatch_call_block_and_release + 15
30 libdispatch.dylib 0x036844d0 _dispatch_client_callout + 14
31 libdispatch.dylib 0x03672047 _dispatch_queue_drain + 452
32 libdispatch.dylib 0x03671e42 _dispatch_queue_invoke + 128
33 libdispatch.dylib 0x03672de2 _dispatch_root_queue_drain + 78
34 libdispatch.dylib 0x03673127 _dispatch_worker_thread2 + 39
35 libsystem_pthread.dylib 0x039b3dab _pthread_wqthread + 336
36 libsystem_pthread.dylib 0x039b7cce start_wqthread + 30
);
}
2014-05-01 12:18:35.897 MY_APP_NAME______[6076:3e07] *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSDictionaryM: 0x7c93e260> was mutated while being enumerated.'
*** First throw call stack:
(
0 CoreFoundation 0x030131e4 __exceptionPreprocess + 180
1 libobjc.A.dylib 0x02d928e5 objc_exception_throw + 44
2 CoreFoundation 0x030a2cf5 __NSFastEnumerationMutationHandler + 165
3 MY_APP_NAME______ 0x000ecf53 -[FTree forEachChild:] + 290
4 MY_APP_NAME______ 0x0011184a -[FRepo(Transaction) pruneCompletedTransactionsBelowNode:] + 373
5 MY_APP_NAME______ 0x00111898 __58-[FRepo(Transaction) pruneCompletedTransactionsBelowNode:]_block_invoke + 43
6 MY_APP_NAME______ 0x000ed01d -[FTree forEachChild:] + 492
7 MY_APP_NAME______ 0x0011184a -[FRepo(Transaction) pruneCompletedTransactionsBelowNode:] + 373
8 MY_APP_NAME______ 0x00111898 __58-[FRepo(Transaction) pruneCompletedTransactionsBelowNode:]_block_invoke + 43
9 MY_APP_NAME______ 0x000ed01d -[FTree forEachChild:] + 492
10 MY_APP_NAME______ 0x0011184a -[FRepo(Transaction) pruneCompletedTransactionsBelowNode:] + 373
11 MY_APP_NAME______ 0x00111898 __58-[FRepo(Transaction) pruneCompletedTransactionsBelowNode:]_block_invoke + 43
12 MY_APP_NAME______ 0x000ed01d -[FTree forEachChild:] + 492
13 MY_APP_NAME______ 0x0011184a -[FRepo(Transaction) pruneCompletedTransactionsBelowNode:] + 373
14 MY_APP_NAME______ 0x00111898 __58-[FRepo(Transaction) pruneCompletedTransactionsBelowNode:]_block_invoke + 43
15 MY_APP_NAME______ 0x000ed01d -[FTree forEachChild:] + 492
16 MY_APP_NAME______ 0x0011184a -[FRepo(Transaction) pruneCompletedTransactionsBelowNode:] + 373
17 MY_APP_NAME______ 0x001127ea -[FRepo(Transaction) rerunTransactionQueue:atPath:] + 2888
18 MY_APP_NAME______ 0x00111a7f -[FRepo(Transaction) rerunTransactionsAndUpdateVisibleDataForPath:] + 422
19 MY_APP_NAME______ 0x001114c7 __50-[FRepo(Transaction) sendTransactionQueue:atPath:]_block_invoke + 3092
20 MY_APP_NAME______ 0x000e61d6 -[FPersistentConnection ackPuts] + 286
21 MY_APP_NAME______ 0x000e492a __38-[FPersistentConnection sendListen:::]_block_invoke + 778
22 MY_APP_NAME______ 0x000e268a -[FPersistentConnection onDataMessage:withMessage:] + 465
23 MY_APP_NAME______ 0x000d733a -[FConnection onDataMessage:] + 106
24 MY_APP_NAME______ 0x000d7293 -[FConnection onMessage:withMessage:] + 282
25 MY_APP_NAME______ 0x000d4ba4 -[FWebSocketConnection appendFrame:] + 402
26 MY_APP_NAME______ 0x000d4c73 -[FWebSocketConnection handleIncomingFrame:] + 161
27 MY_APP_NAME______ 0x000d4cab -[FWebSocketConnection webSocket:didReceiveMessage:] + 40
28 MY_APP_NAME______ 0x000cfbe1 __31-[FSRWebSocket _handleMessage:]_block_invoke + 151
29 libdispatch.dylib 0x0366f7b8 _dispatch_call_block_and_release + 15
30 libdispatch.dylib 0x036844d0 _dispatch_client_callout + 14
31 libdispatch.dylib 0x03672047 _dispatch_queue_drain + 452
32 libdispatch.dylib 0x03671e42 _dispatch_queue_invoke + 128
33 libdispatch.dylib 0x03672de2 _dispatch_root_queue_drain + 78
34 libdispatch.dylib 0x03673127 _dispatch_worker_thread2 + 39
35 libsystem_pthread.dylib 0x039b3dab _pthread_wqthread + 336
36 libsystem_pthread.dylib 0x039b7cce start_wqthread + 30
)
libc++abi.dylib: terminating with uncaught exception of type NSException
2014-05-01 12:18:49.810 MY_APP_NAME______[6076:60b] {
UncaughtExceptionHandlerSignalKey = 6;
}
It originally started because I was using setValue:withCompletionBlock to attempt to set a node containing a number representing a timestamp. It has various rules to determine whether the timestamp can be updated (if it's < now, etc). Here was my original code:
myValue = @(42);
[myFirebase setValue:myValue withCompletionBlock:^(NSError *error, Firebase *ref) {
if(!error)
myMostRecentValue = myValue;
else
[myFirebase observeSingleEventOfType:FEventTypeValue withBlock:^(FDataSnapshot *mySnapshot) {
myMostRecentValue = mySnapshot.value;
}];
}];
Unfortunately, I think there is an issue with Firebase that would sometimes result in this sequence:
value on server: 41
setValue: 42
error: permission error
observeSingleEventOfType: 42 // returns the attempted value 42 instead of the previous value 41
value on server: 41
app proceeds to inappropriate state with wrong value 42
I think that what is happening is that since I never called observeSingleEventOfType before calling setValue, there was no previous value for Firebase to fall back on when the setValue failed the Firebase rules. So it returns the attempted value instead of an "undefined" placeholder like null. I'm not sure if this is a bug or a feature, but it's something to be aware of. So I replaced that code with the following:
[myFirebase runTransactionBlock:^FTransactionResult *(FMutableData *currentData) {
currentData.value = myValue;
return [FTransactionResult successWithValue:currentData];
} andCompletionBlock:^(NSError *error, BOOL committed, FDataSnapshot *snapshot) {
myMostRecentValue = snapshot.value;
} withLocalEvents:NO];
Which resulted in the NSMutableDictionary being mutated while being enumerated exception. The curious thing is that I'm just passing an NSNumber for the value, and that I'm not trying to set an NSMutableDictionary of my own inside the runTransactionBlock. However, myMostRecentValue is inside of an NSMutableDictionary, but I only set that in the andCompletionBlock so it shouldn't matter.
The only thing I can think of is that maybe I sometimes have two or more transactions running on the same node, or maybe one is running on a parent while another is running on a child. This may be happening because I could be installing listeners as I segue between view controllers if the old view controllers are not unloaded. This is hard for me to test though so it's only a theory.
Not sure if it helps but here is a mutableDeepCopy category function I use to copy the values from Firebase into a local NSMutableDictionary that I use to cache the most recently known values (for example in an observeSingleEventOfType callback):
// category to simplify getting a deep mutableCopy
@implementation NSDictionary(mutableDeepCopy)
- (NSMutableDictionary*)mutableDeepCopy
{
NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:self.count];
for(id key in [self allKeys])
{
id oneValue = [self objectForKey:key];
if([oneValue respondsToSelector:@selector(mutableDeepCopy)])
oneValue = [oneValue mutableDeepCopy];
else if([oneValue respondsToSelector:@selector(mutableCopy)] && ![oneValue isKindOfClass:[NSNumber class]]) // workaround for -[__NSCFNumber mutableCopyWithZone:]: unrecognized selector sent to instance
oneValue = [oneValue mutableCopy];
else
oneValue = [oneValue copy];
[returnDict setValue:oneValue forKey:key];
}
return returnDict;
}
Sometimes I need to avoid a roundtrip in viewDidLoad so I put the last known value in a GUI element until I get the callback for the new value. I can't imagine this would affect Firebase, but perhaps something low level is expecting NSDictionary and chokes because it has a reference to a portion of my NSMutableDictionary that I gave it?
I'm kind of stuck until a solution is found, so hope this helps, thanks!