Ios Firebase FSnapshotUtilities由于在枚举时变异NSMutableDictionary而崩溃
编辑根据接受的答案,解决方案是使用Ios Firebase FSnapshotUtilities由于在枚举时变异NSMutableDictionary而崩溃,ios,objective-c,firebase,Ios,Objective C,Firebase,编辑根据接受的答案,解决方案是使用mutableDeepCopy。对于发送到Firebase的设置值的任何值,以及观察到的更改返回的任何值,都需要使用此选项。这是Firebase SDK的已知问题,应该很快修复 @interface NSDictionary (DeepCopy) - (NSDictionary*)mutableDeepCopy { return (NSMutableDictionary *)CFBridgingRelease(CFPropertyListCreateDe
mutableDeepCopy
。对于发送到Firebase的设置值
的任何值,以及观察到的更改返回的任何值,都需要使用此选项。这是Firebase SDK的已知问题,应该很快修复
@interface NSDictionary (DeepCopy)
- (NSDictionary*)mutableDeepCopy {
return (NSMutableDictionary *)CFBridgingRelease(CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)self, kCFPropertyListMutableContainers));
}
@end
我正在开发一个用于实时协作的应用程序。Firebase库由于竞争条件而间歇性崩溃,其中它在枚举时变异了
NSMutableDictionary
。我在这里发布它是为了提高可视性,同时Firebase更喜欢使用堆栈溢出作为bug报告的主要方法
*** 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 SDK将在setValue调用中同步克隆您的对象。在将数据传递给Firebase之前,不再需要手动克隆数据 尽管您正在调用“复制”,但这只会对最外层的NSDictionary进行“浅层”复制,因此如果您在外部NSDictionary中有任何NSDictionary,并且您正在修改这些NSDictionary,那么当Firebase枚举这些内部NSDictionary对象时,我们仍然可以从调用堆栈中遇到此错误,看起来我们是在列举一个内在的 Firebase真的应该自动为您制作此副本,这样您就不必担心了。我们打开了一个bug来解决这个问题。但现在,你需要做一个“深度拷贝”,而不是浅拷贝。有关一些可能的方法,请参见此处:(第二个或第三个答案看起来很有可能)。编辑: 我相信我已经找到了异常的潜在原因: 我有一种预感,多个事务试图在同一个节点上本地运行,并且由于高堆栈跟踪而导致争用。最后,我将当前正在运行的事务保存在一个集合中,并在启动另一个事务之前在节点上测试正在运行的事务。代码如下:
@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;
}
不幸的是,我认为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没有以前的值可以依赖。因此,它返回尝试的值,而不是像null这样的“未定义”占位符。我不确定这是一个bug还是一个特性,但这是需要注意的。因此,我将该代码替换为以下代码:
[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 category函数,用于将值从Firebase复制到本地NSMutableDictionary,用于缓存最新的已知值(例如在observeSingleEventOfType回调中):
有时我需要避免viewDidLoad中的往返,因此我将最后一个已知值放在GUI元素中,直到得到新值的回调。我无法想象这会对Firebase造成影响,但可能是一些低级的东西在期待NSDictionary和Choke,因为它引用了我提供给它的NSMutableDictionary的一部分
在找到解决方案之前我有点困了,所以希望这能有所帮助,谢谢 糟糕的是,我拿走了我的东西。我误读了你的三元运算符。@Logan混乱可能是我写了一行很长的逻辑的错——对不起;)嗯,这确实很奇怪。从堆栈上看,它确实像是Firebase问题,但我不确定在枚举时为什么会发生变异。这不应该发生。我明天会看一看,然后再给你回复。ping@MichaelLehenbauer。。。运气好吗?@ZaneClaes很抱歉回复太慢了!:-(在再次查看并仔细思考之后,我怀疑我知道发生了什么。请参阅我刚刚提交的答案。这听起来是一个可能的答案,但我需要花费周末的压力测试来确认。这并没有解决问题。这是一个发生的屏幕截图:这是我的Firebase实现的更新要点:以下是显示所有线程正在执行的操作的屏幕截图。请注意,我的代码没有运行。至少有2个Firebase线程处于活动状态,但没有一个线程处于枚举/变异过程中,因此Firebase中的冲突似乎是100%(与我对对象的操作无关):我还尝试使用
[Firebase setDispatchQueue:dispatch_get_main_queue()]
。这并没有解决问题;Firebase Worker
线程似乎仍然在后台线程上创建……FWIW,mutableDeepCopy可以大大简化:返回(NSMutableDictionary*)CbrigingRelease(CFPropertyListCreateDeepCopy(kCFAllocatorDefault,(CFDictionaryRef)自身,kCFPropertyListMutableContainers)是的,谢谢你,在你的建议之后,我偶然发现了这一点,并实现了copyDeep、mutableCopyDeep和immutableCopyDeep,它们分别使用archivedDataWithRootObject、KCFPropertyListTableContainers和Leaves和kCFPropertyListImmutable。这样我就可以控制节点的可变性
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
[myFirebase runTransactionBlock:^FTransactionResult *(FMutableData *currentData) {
currentData.value = myValue;
return [FTransactionResult successWithValue:currentData];
} andCompletionBlock:^(NSError *error, BOOL committed, FDataSnapshot *snapshot) {
myMostRecentValue = snapshot.value;
} withLocalEvents:NO];
// 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;
}