Reputation: 1233
I believe I've found a solution to the error, but don't understand if I'm just avoiding a race condition with my solution by slowing the code down or if I'm really solving the issue.
My question is 2 part
1) Why does changing dispatch.main.async
to dispatch.main.sync
make the error go away because I've coded it correctly or have I just worked around the issue for now?
2) Do I need to change all the dispatch.main.async
to dispatch.main.sync
for the 2 additional calls?
Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x600002a24180> was mutated while being enumerated.
In this report, the second answer specifically says, don't ever use dispatch.main.sync
as it causes deadlocks. The first answer says, you may want to use sync but it can cause deadlocks.
What does main.sync in global().async mean?
Apple reports the error as follows:
Thread 5 name: Dispatch queue: com.apple.cloudkit.operation-7BCFBC195B4B17C3.callback
Thread 5 Crashed:
0 libsystem_kernel.dylib 0x00000001b4e120dc 0x1b4def000 + 143580
1 libsystem_pthread.dylib 0x00000001b4e8b094 0x1b4e89000 + 8340
2 libsystem_c.dylib 0x00000001b4d6af4c 0x1b4d10000 + 372556
3 libsystem_c.dylib 0x00000001b4d6aeb4 0x1b4d10000 + 372404
4 libc++abi.dylib 0x00000001b4437788 0x1b4436000 + 6024
5 libc++abi.dylib 0x00000001b4437934 0x1b4436000 + 6452
6 libobjc.A.dylib 0x00000001b444ee00 0x1b4449000 + 24064
7 libc++abi.dylib 0x00000001b4443838 0x1b4436000 + 55352
8 libc++abi.dylib 0x00000001b44438c4 0x1b4436000 + 55492
9 libdispatch.dylib 0x00000001b4cb47e8 0x1b4c54000 + 395240
10 libdispatch.dylib 0x00000001b4c5d324 0x1b4c54000 + 37668
11 libdispatch.dylib 0x00000001b4c5de74 0x1b4c54000 + 40564
12 libdispatch.dylib 0x00000001b4c664ac 0x1b4c54000 + 74924
13 libsystem_pthread.dylib 0x00000001b4e95114 0x1b4e89000 + 49428
14 libsystem_pthread.dylib 0x00000001b4e97cd4 0x1b4e89000 + 60628
When I run the code in Xcode, I can reproduce the error in Xcode and get the following stack trace.
2019-07-31 19:51:07.893799-0400 My Toy Chest[49278:1080065] *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x600002a24180> was mutated while being enumerated.'
*** First throw call stack:
(
0 CoreFoundation 0x00000001106d38db __exceptionPreprocess + 331
1 libobjc.A.dylib 0x000000010f6f4ac5 objc_exception_throw + 48
2 CoreFoundation 0x00000001106d07ac __NSFastEnumerationMutationHandler + 124
3 CoreData 0x0000000110048dd4 -[NSManagedObjectContext executeFetchRequest:error:] + 3332
4 My Toy Chest 0x0000000109248bc0 $s12My_Toy_Chest21ActionFigureSpecificsC03setdeF8CoreDataSbyF + 1472
5 My Toy Chest 0x000000010923a4b2 $s12My_Toy_Chest21ActionFigureSpecificsC03setdeF0yyF + 546
6 My Toy Chest 0x0000000109313c6b $s12My_Toy_Chest22SharedDataActionFigureC03getfg26SpecificsICloudForCompletefG5ArrayyyFySaySo8CKRecordCGSg_s5Error_pSgtcfU_ + 3051
7 My Toy Chest 0x000000010931409c $s12My_Toy_Chest22SharedDataActionFigureC03getfg26SpecificsICloudForCompletefG5ArrayyyFySaySo8CKRecordCGSg_s5Error_pSgtcfU_TA + 12
8 My Toy Chest 0x0000000109315233 $s12My_Toy_Chest22SharedDataActionFigureC013fetchCompletefG18SpecificsFromCloud10completionyySaySo8CKRecordCGSg_s5Error_pSgtc_tFySo13CKQueryCursorCSg_AKtcfU0_ + 1331
9 My Toy Chest 0x0000000109315e01 $s12My_Toy_Chest22SharedDataActionFigureC013fetchCompletefG18SpecificsFromCloud10completionyySaySo8CKRecordCGSg_s5Error_pSgtc_tFySo13CKQueryCursorCSg_AKtcfU0_TA + 49
10 My Toy Chest 0x00000001092373b4 $sSo13CKQueryCursorCSgs5Error_pSgIeggg_ACSo7NSErrorCSgIeyByy_TR + 132
11 CloudKit 0x000000010f51e84d -[CKQueryOperation _finishOnCallbackQueueWithError:] + 613
12 CloudKit 0x000000010f51795a -[CKOperation _finishInternalOnCallbackQueueWithError:] + 582
13 CloudKit 0x000000010f5176fa -[CKOperation _handleCompletionCallback:] + 148
14 CloudKit 0x000000010f51e521 -[CKQueryOperation _handleCompletionCallback:] + 197
15 CloudKit 0x000000010f58d438 __82-[CKOperationCallbackManager _performCallbackForOperation:callback:errorCallback:]_block_invoke + 288
16 libdispatch.dylib 0x000000011189e725 _dispatch_block_async_invoke2 + 83
17 libdispatch.dylib 0x0000000111890db5 _dispatch_client_callout + 8
18 libdispatch.dylib 0x0000000111898225 _dispatch_lane_serial_drain + 778
19 libdispatch.dylib 0x0000000111898ed0 _dispatch_lane_invoke + 477
20 libdispatch.dylib 0x00000001118a2ea3 _dispatch_workloop_worker_thread + 733
21 libsystem_pthread.dylib 0x0000000111c79611 _pthread_wqthread + 421
22 libsystem_pthread.dylib 0x0000000111c793fd start_wqthread + 13
)
libc++abi.dylib: terminating with uncaught exception of type NSException
I am pretty sure the code where the defect is occurring is the following set of code that updates the UI in a View Controller collection view.
The following code crashes (async)
DispatchQueue.main.async {
self.delegate?.updateActionFigureSpecificsModel()
}
The following code doesn't crash (sync)
DispatchQueue.main.sync {
self.delegate?.updateActionFigureSpecificsModel()
}
Subsequent calls that update the View Controller collection views
func updateActionFigureSpecificsModel() {
DispatchQueue.main.async {
self.delegate?.updateActionFigureModel(forActionFigure: self, forDelegateProperty: .specifics)
}
}
func updateActionFigureModel(forActionFigure actionFigure: ActionFigure, forDelegateProperty delegateProperty: DelegateProperty) {
switch delegateProperty {
case .specifics, .eBaySaleSummary:
DispatchQueue.main.async {
self.setUIActionFigure(forActionFigure: actionFigure)
}
}
}
func setUIActionFigure(forActionFigure actionFigure: ActionFigure) {
if let indexPath = getIndexPath(forActionFigure: actionFigure) {
DispatchQueue.main.async {
self.collectionView.reloadItems(at: [indexPath])
}
}
Upvotes: 0
Views: 976
Reputation: 3439
I really can't tell from what you've posted what the exact problem is, but you're probably running into one or both of these general issues:
I suspect that the collection you're using/iterating/modifying in whatever thread you're running is colliding with whatever use/iteration/modification that's being done by the tasks you're dispatching on main
. And yes, I suspect that changing async
to sync
is just dodging the issue by waiting for the main
task to complete before the background thread resumes. As a rule, if the background thread and main thread are sharing a reference to the same foundation collection–and either of them modify that collection–you're going to eventually run into problems ... unless you're very, very, careful.
A solution I often use is to keep my collections separate. My background thread will make updates or perform calculations that update a collection. Then I'll make a copy of the collection and pass that copy to main
for display and UI interactions.
Upvotes: 2