编辑根据接受的答案,解决方案是使用mutableDeepCopy
。您需要将此值用于发送到Firebase setValue
的任何值,以及观察更改后返回的任何值。这是Firebase SDK的一个已知问题,应尽快解决。
@interface NSDictionary (DeepCopy)
- (NSDictionary*)mutableDeepCopy {
return (NSMutableDictionary *)CFBridgingRelease(CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)self, kCFPropertyListMutableContainers));
}
@end
我正在使用Firebase开发一个用于实时协作的应用程序。 Firebase库由于竞争条件而间歇性崩溃,在枚举NSMutableDictionary
的同时枚举它。我在这里发布它是为了可见性,以及Firebase更喜欢使用Stack Overflow作为错误报告的主要方法。
*** Collection <__NSDictionaryM: 0xd8198f0> was mutated while being enumerated.
2014-04-27 09:39:45.328 SharedNotesPro[29350:870b] *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSDictionaryM: 0xd8198f0> was mutated while being enumerated.'
*** First throw call stack:
(
0 CoreFoundation 0x044711e4 __exceptionPreprocess + 180
1 libobjc.A.dylib 0x03f3e8e5 objc_exception_throw + 44
2 CoreFoundation 0x04500cf5 __NSFastEnumerationMutationHandler + 165
3 SharedNotesPro 0x003fe8f5 +[FSnapshotUtilities nodeFrom:withPriority:] + 1405
4 SharedNotesPro 0x003fe373 +[FSnapshotUtilities nodeFrom:] + 51
5 SharedNotesPro 0x003fe971 +[FSnapshotUtilities nodeFrom:withPriority:] + 1529
6 SharedNotesPro 0x003e2504 -[FRepo setInternal:newVal:withPriority:withCallback:andPutId:] + 298
7 SharedNotesPro 0x003e23af -[FRepo set:withVal:withPriority:withCallback:] + 165
8 SharedNotesPro 0x00402aaf __61-[Firebase setValueInternal:andPriority:withCompletionBlock:]_block_invoke + 174
9 libdispatch.dylib 0x047a07b8 _dispatch_call_block_and_release + 15
10 libdispatch.dylib 0x047b54d0 _dispatch_client_callout + 14
11 libdispatch.dylib 0x047a3047 _dispatch_queue_drain + 452
12 libdispatch.dylib 0x047a2e42 _dispatch_queue_invoke + 128
13 libdispatch.dylib 0x047a3de2 _dispatch_root_queue_drain + 78
14 libdispatch.dylib 0x047a4127 _dispatch_worker_thread2 + 39
15 libsystem_pthread.dylib 0x04ae4dab _pthread_wqthread + 336
16 libsystem_pthread.dylib 0x04ae8cce start_wqthread + 30
)
libc++abi.dylib: terminating with uncaught exception of type NSException
现在,我认为这是我的错......除了我已经做了一切可以想象的事情来阻止它。首先,我创建的每个Firebase
对象都是完全瞬态的。也就是说,它是单次使用的(分配给单个读/写操作)。此外,当我从Firebase加载数据时,我创建了一个可变的内容副本。
供参考,这是我创建的保存/加载方法;这存在于我创建的基类中,作为Firebase的瘦包装器,可以加载和保存数据。您可以在这些要点中找到完整的.h和.m文件。这些是我与Firebase SDK互动的唯一方式。另请注意,崩溃发生在后台线程上。
- (void)save:(void (^)(BOOL success))completionHandler {
Firebase *fb = [[Firebase alloc] initWithUrl:self.firebaseURL];
[fb setValue:[self.contents copy] withCompletionBlock:^(NSError *error, Firebase *ref) {
if(completionHandler) {
completionHandler(error ? NO : YES);
}
}];
}
- (void)save {
[self save:nil];
}
- (void)load:(void (^)(BOOL success))block {
Firebase *fb = [[Firebase alloc] initWithUrl:self.firebaseURL];
[fb observeSingleEventOfType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
_contents = [[snapshot.value isKindOfClass:[NSDictionary class]]?snapshot.value:@{} mutableCopy];
block(_contents.allKeys.count > 0);
}];
}
答案 0 :(得分:3)
编辑:这不再是问题,因为最新的Firebase SDK会在setValue调用中同步克隆您的对象。在将数据传递给Firebase
之前,不再需要手动克隆数据虽然您正在调用“copy”,但这只是最外层NSDictionary的“浅层”副本,所以如果您在外部NSDictionary中有任何NSDictionaries,并且您正在修改它们,我们仍然可以在Firebase时遇到此错误枚举那些内部NSDictionary对象,并且从callstack中,它看起来好像我们正在枚举其中一个内部对象。
Firebase应该自动为您执行此副本,因此您不必担心它。我们打开了一个错误来解决这个问题。但就目前而言,你需要做一个“深层复制”而不是浅层复制。请参阅此处了解一些可能的方法:deep mutable copy of a NSMutableDictionary(第二或第三个答案看起来很不错)。
答案 1 :(得分:0)
修改: 我相信我找到了例外的潜在原因:
我预感到多个事务试图在同一节点上本地运行并因高堆栈跟踪而导致争用。我最终保存了一组中当前运行的事务,并在启动另一个事件之前测试该节点上正在运行的事务。这是代码:
@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
我也遇到了这个问题,这是我的堆栈跟踪:
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;
}
它最初是因为我使用setValue:withCompletionBlock来尝试设置一个包含表示时间戳的数字的节点。它具有各种规则来确定是否可以更新时间戳(如果它&lt; now&lt; now等)。这是我的原始代码:
myValue = @(42);
[myFirebase setValue:myValue withCompletionBlock:^(NSError *error, Firebase *ref) {
if(!error)
myMostRecentValue = myValue;
else
[myFirebase observeSingleEventOfType:FEventTypeValue withBlock:^(FDataSnapshot *mySnapshot) {
myMostRecentValue = mySnapshot.value;
}];
}];
不幸的是,我认为Firebase存在问题,有时会导致此序列:
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
我认为发生的事情是,因为我在调用setValue之前从未调用过observeSingleEventOfType,所以当setValue失败Firebase规则时,Firebase没有先前的值。因此它返回尝试的值而不是&#34; undefined&#34;占位符如null。我不确定这是一个错误还是一个功能,但需要注意的是它。所以我用以下内容替换了该代码:
[myFirebase runTransactionBlock:^FTransactionResult *(FMutableData *currentData) {
currentData.value = myValue;
return [FTransactionResult successWithValue:currentData];
} andCompletionBlock:^(NSError *error, BOOL committed, FDataSnapshot *snapshot) {
myMostRecentValue = snapshot.value;
} withLocalEvents:NO];
导致NSMutableDictionary在枚举异常时发生变异。奇怪的是,我只是传递一个NSNumber值,并且我没有尝试在runTransactionBlock中设置我自己的NSMutableDictionary。但是,myMostRecentValue在NSMutableDictionary中,但我只在andCompletionBlock中设置它,所以它不重要。
我唯一能想到的是,我有时可能在同一个节点上运行两个或更多个事务,或者一个正在父节点上运行,而另一个正在子节点上运行。这可能会发生,因为我可以安装监听器,因为如果没有卸载旧的视图控制器,我会在视图控制器之间进行切换。这对我来说很难测试,所以它只是一个理论。
不确定它是否有帮助,但这里是一个mutableDeepCopy类别函数,我用它将Firebase中的值复制到我用来缓存最近已知值的本地NSMutableDictionary中(例如在observeSingleEventOfType回调中):
// 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;
}
有时我需要避免viewDidLoad中的往返,所以我将最后一个已知值放在GUI元素中,直到我得到新值的回调。我无法想象这会影响Firebase,但也许是低级别的东西会期待NSDictionary和chokes,因为它引用了我给它的NSMutableDictionary的一部分?
我有点卡住,直到找到解决方案,所以希望这有帮助,谢谢!