Combine's receive(on:) not dispatching to serial queue, causing data race - ios

According to Apple, receive(on:options:) runs callbacks on a given queue. We use a serial dispatch queue to prevent racing on localOptionalCancellable in the code below. But receiveCancel is not getting dispatched to that queue. Can someone tell me why?
From the documentation,
You use the receive(on:options:) operator to receive results and completion on a specific scheduler, such as performing UI work on the main run loop.
...
Prefer receive(on:options:) over explicit use of dispatch queues when performing work in subscribers. For example, instead of the following pattern:
Issue Reproduction:
import Foundation
import Combine
class Example {
private var localOptionalCancellable: AnyCancellable?
private let dispatchQueue = DispatchQueue(label: "LocalQueue-\(UUID())")
func misbehavingFunction() {
self.dispatchQueue.async {
self.localOptionalCancellable = Just(())
.setFailureType(to: Error.self)
.receive(on: self.dispatchQueue)
.handleEvents(
receiveCancel: {
// Simultaneous accesses to 0x600000364e10, but modification requires exclusive access.
// Can be fixed by wrapping in self.dispatchQueue.async {}
self.localOptionalCancellable = nil
}
)
.sink(
receiveCompletion: { _ in },
receiveValue: { _ in
self.localOptionalCancellable = nil
}
)
}
}
}
Example().misbehavingFunction()
Stack Trace:
Simultaneous accesses to 0x600000364e10, but modification requires exclusive access.
Previous access (a modification) started at (0x10eeaf12a).
Current access (a modification) started at:
0 libswiftCore.dylib 0x00007fff2ff7be50 swift_beginAccess + 568
3 Combine 0x00007fff4ba73a40 Publishers.HandleEvents.Inner.cancel() + 71
4 Combine 0x00007fff4ba74230 protocol witness for Cancellable.cancel() in conformance Publishers.HandleEvents<A>.Inner<A1> + 16
5 Combine 0x00007fff4b9f10c0 Subscribers.Sink.cancel() + 652
6 Combine 0x00007fff4b9f1500 protocol witness for Cancellable.cancel() in conformance Subscribers.Sink<A, B> + 16
7 Combine 0x00007fff4b9dd2d0 AnyCancellable.cancel() + 339
8 Combine 0x00007fff4b9dd5f0 AnyCancellable.__deallocating_deinit + 9
9 libswiftCore.dylib 0x00007fff2ff7da20 _swift_release_dealloc + 16
13 Combine 0x00007fff4b9f0da0 Subscribers.Sink.receive(_:) + 54
14 Combine 0x00007fff4b9f14c0 protocol witness for Subscriber.receive(_:) in conformance Subscribers.Sink<A, B> + 16
15 Combine 0x00007fff4ba73ed0 Publishers.HandleEvents.Inner.receive(_:) + 129
16 Combine 0x00007fff4ba74170 protocol witness for Subscriber.receive(_:) in conformance Publishers.HandleEvents<A>.Inner<A1> + 16
17 Combine 0x00007fff4ba26440 closure #1 in Publishers.ReceiveOn.Inner.receive(_:) + 167
18 libswiftDispatch.dylib 0x000000010e97cad0 thunk for #escaping #callee_guaranteed () -> () + 14
19 libdispatch.dylib 0x00007fff20105323 _dispatch_call_block_and_release + 12
20 libdispatch.dylib 0x00007fff20106500 _dispatch_client_callout + 8
21 libdispatch.dylib 0x00007fff2010c12e _dispatch_lane_serial_drain + 715
22 libdispatch.dylib 0x00007fff2010cde1 _dispatch_lane_invoke + 403
23 libdispatch.dylib 0x00007fff20117269 _dispatch_workloop_worker_thread + 782
24 libsystem_pthread.dylib 0x00007fff6116391b _pthread_wqthread + 290
25 libsystem_pthread.dylib 0x00007fff61162b68 start_wqthread + 15
Fatal access conflict detected.

According to Apple, receive(on:options:) runs callbacks on a given queue.
Not exactly. Here's what the documentation actually says:
You use the receive(on:options:) operator to receive results and completion on a specific scheduler, such as performing UI work on the main run loop. In contrast with subscribe(on:options:), which affects upstream messages, receive(on:options:) changes the execution context of downstream messages.
(Emphasis added.) So receive(on:) controls the Scheduler used to call a Subscriber's receive(_:) and receive(completion:) methods. It does not control the Scheduler used to call the Subscription's request(_:) or cancel() methods.
To control the Scheduler used to call the Subscription's cancel() method, you need to use the subscribe(on:options:) operator downstream of the handleEvents operator, like this:
self.localOptionalCancellable = Just(())
.setFailureType(to: Error.self)
.receive(on: self.dispatchQueue)
.handleEvents(
receiveCancel: {
// Simultaneous accesses to 0x600000364e10, but modification requires exclusive access.
// Can be fixed by wrapping in self.dispatchQueue.async {}
self.localOptionalCancellable = nil
}
)
.subscribe(on: self.dispatchQueue)
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.sink(
receiveCompletion: { _ in },
receiveValue: { _ in
self.localOptionalCancellable = nil
}
)

Related

Crash (SIGABRT) when writing data to UserDefaults after Sheet disappears

I got three similar crash reports that I can't reproduce (all on iOS 14.4). The stracktrace says the following (I only pasted the part where my app is starting):
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Triggered by Thread: 0
Thread 0 name:
Thread 0 Crashed:
0 libsystem_kernel.dylib 0x00000001c077d414 __pthread_kill + 8
1 libsystem_pthread.dylib 0x00000001de2d8b50 pthread_kill + 272 (pthread.c:1392)
2 libsystem_c.dylib 0x000000019bc5bb74 abort + 104 (abort.c:110)
3 libswiftCore.dylib 0x0000000196795f20 swift::fatalError(unsigned int, char const*, ...) + 60 (Errors.cpp:393)
4 libswiftCore.dylib 0x0000000196796078 swift::swift_abortRetainUnowned(void const*) + 36 (Errors.cpp:460)
5 libswiftCore.dylib 0x00000001967e5844 swift_unknownObjectUnownedLoadStrong + 76 (SwiftObject.mm:895)
6 SwiftUI 0x00000001992b0cdc ViewGraph.graphDelegate.getter + 16 (ViewGraph.swift:234)
7 SwiftUI 0x00000001997e4d58 closure #1 in GraphHost.init(data:) + 80
8 SwiftUI 0x00000001997e6550 partial apply for closure #1 in GraphHost.init(data:) + 40 (<compiler-generated>:0)
9 AttributeGraph 0x00000001bbcc9b88 AG::Graph::Context::call_update() + 76 (ag-closure.h:108)
10 AttributeGraph 0x00000001bbcca1a0 AG::Graph::call_update() + 56 (ag-graph.cc:176)
11 AttributeGraph 0x00000001bbccfd70 AG::Subgraph::update(unsigned int) + 92 (ag-graph.h:709)
12 SwiftUI 0x00000001997e1cdc GraphHost.runTransaction() + 172 (GraphHost.swift:491)
13 SwiftUI 0x00000001997e4e1c GraphHost.runTransaction(_:) + 92 (GraphHost.swift:471)
14 SwiftUI 0x00000001997e37a8 GraphHost.flushTransactions() + 176 (GraphHost.swift:459)
15 SwiftUI 0x00000001997e2c78 specialized GraphHost.asyncTransaction<A>(_:mutation:style:) + 252 (<compiler-generated>:0)
16 SwiftUI 0x00000001993bd2fc AttributeInvalidatingSubscriber.invalidateAttribute() + 236 (AttributeInvalidatingSubscriber.swift:89)
17 SwiftUI 0x00000001993bd1f8 AttributeInvalidatingSubscriber.receive(_:) + 100 (AttributeInvalidatingSubscriber.swift:53)
18 SwiftUI 0x00000001993bd914 protocol witness for Subscriber.receive(_:) in conformance AttributeInvalidatingSubscriber<A> + 24 (<compiler-generated>:0)
19 SwiftUI 0x000000019956ba34 SubscriptionLifetime.Connection.receive(_:) + 100 (SubscriptionLifetime.swift:195)
20 Combine 0x00000001a6e67900 ObservableObjectPublisher.Inner.send() + 136 (ObservableObject.swift:115)
21 Combine 0x00000001a6e670a8 ObservableObjectPublisher.send() + 632 (ObservableObject.swift:153)
22 Combine 0x00000001a6e4ffdc PublishedSubject.send(_:) + 136 (PublishedSubject.swift:82)
23 Combine 0x00000001a6e76994 specialized static Published.subscript.setter + 388 (Published.swift:0)
24 Combine 0x00000001a6e75f74 static Published.subscript.setter + 40 (<compiler-generated>:0)
25 MyApp 0x00000001005d1228 counter.set + 32 (Preferences.swift:0)
26 MyApp 0x00000001005d1228 Preferences.counter.modify + 120 (Preferences.swift:0)
27 MyApp 0x00000001005ca440 MyView.changeCounter(decrease:) + 344 (MyView.swift:367)
28 MyApp 0x00000001005cf110 0x100584000 + 307472
29 MyApp 0x00000001005e65d8 thunk for #escaping #callee_guaranteed () -> () + 20 (<compiler-generated>:0)
30 MyApp 0x00000001005a8828 closure #2 in MySheet.body.getter + 140 (MySheet.swift:0)
What is happening is, that I have a Sheet with a button and when clicking on it the sheet disappears and in the onDisappear the changeCounter method in the main View MyView is called to change the counter. The method changeCounter is passed to the Sheet from MyView when calling/opening the Sheet.
This is the .sheet method in MyView:
.sheet(item: $activeSheet) { item in
switch item {
case .MY_SHEET:
MySheet(changeCounter: {changeCounter(decrease: true)}, changeTimer, item: $activeSheet)
}
}
This is the (important part of the) sheet:
struct MySheet: View {
var changeCounter: () -> Void
var changeTimer: () -> Void
#Binding var item: ActiveSheet?
#State var dismissAction: (() -> Void)?
var body: some View {
GeometryReader { metrics in
VStack {
Button(action: {
self.dismissAction = changeCounter
self.item = nil
}, label: {
Text("change_counter")
})
Button(action: {
self.dismissAction = changeTimer
self.item = nil
}, label: {
Text("change_timer")
})
}.frame(width: metrics.size.width, height: metrics.size.height * 0.85)
}.onDisappear(perform: {
if self.dismissAction != nil {
self.dismissAction!()
}
})
}
}
Here is changeCounter with the preferences object:
struct MyView: View {
#EnvironmentObject var preferences: Preferences
var body: some View {...}
func changeCounter(decrease: Bool) {
if decrease {
preferences.counter -= COUNTER_INTERVAL
}
}
}
The Preferences is an ObservableObject with the counter variable:
class Preferences: ObservableObject {
let userDefaults: UserDefaults
init(_ userDefaults: UserDefaults) {
self.userDefaults = userDefaults
self.counter = 0
}
#Published var counter: Int {
didSet {
self.userDefaults.set(counter, forKey: "counter")
}
}
}
It changes a value in the userDefaults that are UserDefaults.standard.
Anyone has an idea how that crash can happen and in what situations? Because it only happened three times now on users devices and I can't reproduce it.
Let's analyze
Button(action: {
self.dismissAction = changeCounter 1)
self.item = nil 2)
}, label: {
Line 1) changes internal sheet state initiating update of sheet's view
Line 2) changes external state initiating close of sheet (and probably update of parent view).
It even sounds as two conflicting process (even if there are no dependent flows, but looking at your code second depends on result of first). So, this is very dangerous logic and should be avoided.
In general, as I wrote in comment, changing two states in one closure is always risky, so I would rewrite logic to have something like (sketch):
Button(action: {
self.result = changeCounter // one external binding !!
}, label: {
, ie. the one state change that initiates some external activity...
Possible workaround for your code (if for any reason you cannot change logic) is to separate changes of those states in time, like
Button(action: {
self.dismissAction = changeCounter // updates sheet
DispatchQueue.main.async { // or after some min delay
self.item = nil // closes sheet after (!) update
}
}, label: {

Run-time crash BridgeFromObjectiveC when format date string to Foundation.Date

We've got kind of a weird bug in our app that only one user encounters so far (out of thousands) and I can't figure it out how to solve this, so maybe you can help me further.
It's about the following crash (run-time):
#0. Crashed: com.apple.main-thread
0 libswiftFoundation.dylib 0x10e51ec _TZFV10Foundation4Date36_unconditionallyBridgeFromObjectiveCfGSqCSo6NSDate_S0_ + 68
1 XXX 0x14d804 specialized static Loader._save(NSDictionary, Double, moc : NSManagedObjectContext) -> () (Loader.swift:149)
2 XXX 0x14d804 specialized static Loader._save(NSDictionary, Double, moc : NSManagedObjectContext) -> () (Loader.swift:149)
3 XXX 0x14286c static Loader.(registerUser(NSManagedObjectContext) -> ()).(closure #1) (Loader.swift)
4 Alamofire 0x5aea58 specialized DataRequest.(response<A where ...> (queue : DispatchQueue?, responseSerializer : A, completionHandler : (DataResponse<A.SerializedObject>) -> ()) -> Self).(closure #1).(closure #1) (ResponseSerialization.swift)
5 libdispatch.dylib 0x1b911797 _dispatch_call_block_and_release + 10
6 libdispatch.dylib 0x1b911783 _dispatch_client_callout + 22
7 libdispatch.dylib 0x1b915d05 _dispatch_main_queue_callback_4CF + 902
8 CoreFoundation 0x1c1ffd69 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 8
9 CoreFoundation 0x1c1fde19 __CFRunLoopRun + 848
10 CoreFoundation 0x1c1510ef CFRunLoopRunSpecific + 470
11 CoreFoundation 0x1c150f11 CFRunLoopRunInMode + 104
12 GraphicsServices 0x1d8fbb41 GSEventRunModal + 80
13 UIKit 0x214d5e83 UIApplicationMain + 150
14 XXX 0xb7b1c main (AppDelegate.swift:26)
15 libdyld.dylib 0x1b93e4eb start + 2
Line 149 of Loader.swift is:
let createDate : Foundation.Date = dateFormatter.date(from: eventCreated)!
It looks like it's an internal problem inside libswiftFoundation.dylib.
I've researched a lot about this problem and the only thing I could find on this was from Apple:
Some Objective-C methods in the SDKs may incorrectly annotate or assume a type to be nonnull rather than nullable. A type that Swift treats as a struct, such as NSURL (Foundation.URL) or NSDate (Foundation.Date), results in a run-time crash in a method with a name like bridgeFromObjectiveC; in other cases, it can lead to crashes or undefined behavior in user code. If you identify such a method, please file a report at bugreport.apple.com. As a workaround, add a trampoline function in Objective-C with the correct nullability annotations. For example, the following function will allow you to call FileManager'senumerator(at:includingPropertiesForKeys:options:errorHandler:) method with an error handler that accepts nil URLs:
https://developer.apple.com/library/content/releasenotes/DeveloperTools/RN-Xcode/Chapters/Introduction.html (under known issues)
With an example that looks like this:
static inline NSDirectoryEnumerator<NSURL *> * _Nullable
fileManagerEnumeratorAtURL(NSFileManager *fileManager, NSURL * _Nonnull url, NSArray<NSURLResourceKey> * _Nullable keys, NSDirectoryEnumerationOptions options, BOOL (^ _Nullable errorHandler)(NSURL * _Nullable errorURL, NSError * _Nonnull error)) {
return [fileManager enumeratorAtURL:url includingPropertiesForKeys:keys options:options errorHandler:^(NSURL * _Nonnull errorURL, NSError * _Nonnull error) {
return errorHandler(errorURL, error);
}];
}
I've already submitted a bug report for this issue, but still haven't got a response from Apple (after 7 days). I think this could fix the problem, but I'm not sure on how to port the given trampoline function (example) to a valid/working NSDate/DateFormatter version. Does someone know more about this problem and/or got it solved?

iOS app rejected by Apple due to IPV6 issue but crash report indicates assertion failed

My iOS app does not use any networking and works fine on my iPhone, but Apple rejected it supposedly due to IPv6 incompatibility issues.
2. 1 PERFORMANCE: APP COMPLETENESS
Performance - 2.1
Your app crashes on iPhone running iOS 10.0.3 connected to an IPv6 network when we:
Specifically, we found your app crashes when we tap “Analyze.”
We have attached detailed crash logs to help troubleshoot this issue.
However when I symbolicated the crash report they sent, it shows a particular thread crashed. The backtrace is below.
Thread 5 name: Dispatch queue: com.apple.HealthKit.HKHealthStore.client.0x1700f6d00
Thread 5 Crashed:
0 libswiftCore.dylib 0x0000000100273ae8 _assertionFailed(StaticString, String, StaticString, UInt, flags : UInt32) -> Never (__hidden#14874_:167)
1 libswiftCore.dylib 0x0000000100273ae8 _assertionFailed(StaticString, String, StaticString, UInt, flags : UInt32) -> Never (__hidden#14874_:167)
2 PickerTest 0x00000001000c1d1c type metadata accessor for String? (PyschicBrain.swift:0)
3 PickerTest 0x00000001000bfe78 PsychicBrain.(isHeartRateInfoAvailableForDates() -> Bool).(closure #1) (PyschicBrain.swift:0)
4 libdispatch.dylib 0x000000018f98d200 _dispatch_call_block_and_release + 24
5 libdispatch.dylib 0x000000018f98d1c0 _dispatch_client_callout + 16
6 libdispatch.dylib 0x000000018f99b444 _dispatch_queue_serial_drain + 928
7 libdispatch.dylib 0x000000018f9909a8 _dispatch_queue_invoke + 652
8 libdispatch.dylib 0x000000018f99b940 _dispatch_queue_override_invoke + 360
9 libdispatch.dylib 0x000000018f99d38c _dispatch_root_queue_drain + 572
10 libdispatch.dylib 0x000000018f99d0ec _dispatch_worker_thread3 + 124
11 libsystem_pthread.dylib 0x000000018fb952c8 _pthread_wqthread + 1288
12 libsystem_pthread.dylib 0x000000018fb94db4 start_wqthread + 4
Stack frame #3 (above) says the closure in the function isHeartRateInfoAvailableForDates() failed, which means the HKSampleQuery failed.
My question: I'm assuming this is because no heart-rate data was found for that interval. On my phone, in this scenario, samples.count() returns 0 and hence the function returns FALSE. When Apple folks test it, obviously they get a different error. I've asked Apple for console logs but I would like to know a more elegant way of error handling.
func isHeartRateInfoAvailableForDates() -> Bool {
predicate = HKQuery.predicateForSamples(withStart: pvtStartDate, end: pvtEndDate, options: HKQueryOptions())
guard let sampleType = HKSampleType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) else {
fatalError("HKSampleType.quantityTypeForIdentifier failed")
}
let dispatchGrp = DispatchGroup()
// Entering query block
//
dispatchGrp.enter()
let query = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: Int(HKObjectQueryNoLimit), sortDescriptors: nil) {
query, results, error in
guard let samples = results as? [HKQuantitySample] else {
fatalError("An error occured fetching the user's heart rate: \(error?.localizedDescription)");
}
self.pvtSampleCount = samples.count
// leave group once query results are available
dispatchGrp.leave()
}
pvtHealthStore.execute(query)
// Now wait for the query to complete
//
dispatchGrp.wait(timeout: DispatchTime.distantFuture)
return pvtSampleCount > 0
}

Watch app starts with error clientIdentifier for interfaceControllerID not found

I'm having a smartwatch app on watchos2. The app always worked but now when it starts I immediately get this error:
Lop_WatchKit_Extension[17535:7854201] *********** ERROR -[SPRemoteInterface _interfaceControllerClientIDForControllerID:] clientIdentifier for interfaceControllerID:447E0002 not found
I found some topics here on stackoverflow but nothing solved the problem.
In my case, this was due to a retain cycle in one InterfaceController of mine.
If you get the logs similar to:
[default] -[SPRemoteInterface
_interfaceControllerClientIDForControllerID:]:0000: ComF: clientIdentifier for interfaceControllerID:XXXXXXXX not found
&/or...
[default] _SendRecordedValues:000: ComF:<-Plugin controller ID
XXXXXXXX has no client identifier
First, figure out which InterfaceController has the controller ID XXXXXXXX.
Have this in awake(withContext:)
override func awake(withContext context: Any?) {
//...
if let id = self.value(forKey: "_viewControllerID") as? NSString {
let strClassDescription = String(describing: self)
print("\(strClassDescription) has the Interface Controller ID \(id)")
}
//...
}
This logs:
[Target.Classname: 0xYYYYYYYY] has the Interface Controller ID
XXXXXXXX
Once you identify the InterfaceController causing these logs, you can continue to debug.
It could be different in your case but in mine I had created a retain cycle with self in one of my closures within which took awhile to locate but I eventually broke the retain cycle with a [weak self] capture.
Basically, the error logs appear when an InterfaceController is trying to execute some code but it has already been released.
What I already had:
DispatchQueue.main.async {
self.doSomethingThatDoesSomethingAsync()
}
What I fixed:
DispatchQueue.main.async { [weak self] in
self?.doSomethingThatDoesSomethingAsync()
}
If you use didSet on any IBOutlets it will also throw this error in the logs.
class MyInterfaceController: WKInterfaceController {
#IBOutlet var myLabel: WKInterfaceLabel! {
didSet {
myLabel.setTitle("Test")
}
}
How #nickromano sad, it's happens when you use didSet with IBOutlets. Cause it's calls before awake(withContext context: Any?)
We can suppress this error if wrap it in DispatchQueue.main.async
#IBOutlet var statusLabel: WKInterfaceLabel! {
didSet {
DispatchQueue.main.async {
self.statusLabel.setHidden(true)
}
}
This has happened to me a few times and more times than not, it is because of a timer that is still firing in a just-previously dismissed WKInterfaceController that I did not catch.
Best thing to do aside from comparing ID's like in #staticVoidMan's answer is to read the call stack. In my case I was able to identify that the old timer was still firing based off these hints:
8 Foundation 0x00007fff214be867 __NSFireTimer + 67
9 CoreFoundation 0x00007fff207a8e3f __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
10 CoreFoundation 0x00007fff207a8912 __CFRunLoopDoTimer + 926
Here is the original call stack (for reference):
<MyApp.InterfaceController: 0x7fb3e4d2d020> has the Interface Controller ID 1EB00002
2021-05-26 14:44:06.632758-0600 MyApp WatchKit Extension[73392:3546879] [default] -[SPRemoteInterface _interfaceControllerClientIDForControllerID:]:2464: ComF: clientIdentifier for interfaceControllerID:1EB00007 not found. callStack:(
0 WatchKit 0x00007fff38d1a268 -[SPRemoteInterface _interfaceControllerClientIDForControllerID:] + 220
1 WatchKit 0x00007fff38d1bfff __54+[SPRemoteInterface setController:key:property:value:]_block_invoke + 340
2 WatchKit 0x00007fff38d12323 spUtils_dispatchAsyncToMainThread + 30
3 WatchKit 0x00007fff38d1be60 +[SPRemoteInterface setController:key:property:value:] + 179
4 WatchKit 0x00007fff38d057af -[WKInterfaceObject _sendValueChanged:forProperty:] + 706
5 WatchKit 0x00007fff38d2a5f8 -[WKInterfaceObject _setImage:forProperty:] + 50
6 MyApp WatchKit Extension 0x000000010955531d $s26MyApp_WatchKit_Extension25ActivityIndicatorDelegateC06handleE5TimeryyF + 813
7 MyApp WatchKit Extension 0x000000010955537a $s26MyApp_WatchKit_Extension25ActivityIndicatorDelegateC06handleE5TimeryyFTo + 42
8 Foundation 0x00007fff214be867 __NSFireTimer + 67
9 CoreFoundation 0x00007fff207a8e3f __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
10 CoreFoundation 0x00007fff207a8912 __CFRunLoopDoTimer + 926
11 CoreFoundation 0x00007fff207a7ec5 __CFRunLoopDoTimers + 265
12 CoreFoundation 0x00007fff207a2546 __CFRunLoopRun + 1949
13 CoreFoundation 0x00007fff207a18be CFRunLoopRunSpecific + 567
14 GraphicsServices 0x00007fff25b49fd3 GSEventRunModal + 139
15 UIKitCore 0x00007fff43290f24 -[UIApplication _run] + 917
16 UIKitCore 0x00007fff43295c0b UIApplicationMain + 101
17 WatchKit 0x00007fff38d0de65 WKExtensionMain + 800
18 libdyld.dylib 0x00007fff20202db5 start + 1
19 ??? 0x0000000000000001 0x0 + 1
)
Have you changed the name of your module? If this is the case then you have to go through your storyboard and update it manually for all the Interfaces you have.
Edit with steps to fix:
Go to the storyboard and for each interface open the Identity inspector, then delete what's in Module and press enter, the new module should get auto-filled.

Realm object has been deleted or invalidated

When I start my app, I perform an API call to see whether there's new data available. The data is stored in my local Realm database, and some of it is displayed in the initial table view controller.
Once the API call is finished, I check if some conditions are met that require me to delete a bunch of the previous data from the database and then create new objects. However, when I delete the old data, my app crashes with the following exception:
2015-08-06 11:56:32.057 MSUapp[19754:172864] *** Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'
*** First throw call stack:
(
0 CoreFoundation 0x000000010660cc65 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x00000001083bdbb7 objc_exception_throw + 45
2 Realm 0x0000000105b78e95 _ZL17RLMVerifyAttachedP13RLMObjectBase + 85
3 Realm 0x0000000105b7878d _ZL10RLMGetLinkP13RLMObjectBasemP8NSString + 29
4 Realm 0x0000000105b7c23e ___ZL17RLMAccessorGetterP11RLMProperty15RLMAccessorCodeP8NSString_block_invoke_12 + 46
5 MSUapp 0x0000000105764867 _TFFC6MSUapp29FavoriteLeaguesViewController18generateLeagueListFS0_FT_T_U_FTCS_6LeagueS1__Sb + 39
6 MSUapp 0x00000001057648eb _TTRXFo_oC6MSUapp6LeagueoS0__dSb_XFo_iS0_iS0__dSb_ + 27
7 libswiftCore.dylib 0x0000000108674ae2 _TFSs14_insertionSortUSs21MutableCollectionType_USs13GeneratorType__Ss22BidirectionalIndexType_Ss18_SignedIntegerType_Ss33_BuiltinIntegerLiteralConvertible____FTRQ_GVSs5RangeQQ_5Index_RFTQQQ_9Generator7ElementS7__Sb_T_ + 1570
8 libswiftCore.dylib 0x0000000108676682 _TFSs14_introSortImplUSs21MutableCollectionType_USs13GeneratorType__Ss21RandomAccessIndexType_Ss18_SignedIntegerType_Ss33_BuiltinIntegerLiteralConvertible_Ss16SignedNumberType_S3_____FTRQ_GVSs5RangeQQ_5Index_RFTQQQ_9Generator7ElementS8__SbSi_T_ + 1250
9 libswiftCore.dylib 0x0000000108676172 _TFSs10_introSortUSs21MutableCollectionType_USs13GeneratorType__Ss21RandomAccessIndexType_Ss18_SignedIntegerType_Ss33_BuiltinIntegerLiteralConvertible_Ss16SignedNumberType_S3_____FTRQ_GVSs5RangeQQ_5Index_FTQQQ_9Generator7ElementS8__Sb_T_ + 1058
10 libswiftCore.dylib 0x00000001085ec947 _TFSs4sortUSs21MutableCollectionType_USs13GeneratorType__Ss21RandomAccessIndexType_Ss18_SignedIntegerType_Ss33_BuiltinIntegerLiteralConvertible_Ss16SignedNumberType_S3_____FTRQ_FTQQQ_9Generator7ElementS6__Sb_T_ + 471
11 libswiftCore.dylib 0x00000001086a8d9e _TPA__TFFSa4sortU__FRGSaQ__FFTQ_Q__SbT_U_FRGVSs26UnsafeMutableBufferPointerQ__T_ + 222
12 libswiftCore.dylib 0x00000001086a8e18 _TPA__TTRG0_R_XFo_lGVSs26UnsafeMutableBufferPointerq___dT__XFo_lGS_q___iT__42 + 56
13 libswiftCore.dylib 0x00000001085f7fda _TFSa30withUnsafeMutableBufferPointerU__fRGSaQ__U__FFRGVSs26UnsafeMutableBufferPointerQd___Q_Q_ + 522
14 libswiftCore.dylib 0x00000001085f7db4 _TFSa4sortU__fRGSaQ__FFTQ_Q__SbT_ + 132
15 MSUapp 0x0000000105761709 _TFC6MSUapp29FavoriteLeaguesViewController18generateLeagueListfS0_FT_T_ + 1097
16 MSUapp 0x000000010576354b _TFC6MSUapp29FavoriteLeaguesViewController27numberOfSectionsInTableViewfS0_FCSo11UITableViewSi + 59
17 MSUapp 0x00000001057635fa _TToFC6MSUapp29FavoriteLeaguesViewController27numberOfSectionsInTableViewfS0_FCSo11UITableViewSi + 58
18 UIKit 0x000000010737cac3 -[UITableViewRowData _updateNumSections] + 84
19 UIKit 0x000000010737d4b4 -[UITableViewRowData invalidateAllSections] + 69
20 UIKit 0x00000001071c873b -[UITableView _updateRowData] + 217
21 UIKit 0x00000001071de2b7 -[UITableView noteNumberOfRowsChanged] + 112
22 UIKit 0x00000001071dd9f5 -[UITableView reloadData] + 1355
23 MSUapp 0x00000001057647c6 _TFFC6MSUapp29FavoriteLeaguesViewController11viewDidLoadFS0_FT_T_U_FTO10RealmSwift12NotificationCS1_5Realm_T_ + 166
24 RealmSwift 0x0000000105f37210 _TFF10RealmSwift41rlmNotificationBlockFromNotificationBlockFFT12notificationOS_12Notification5realmCS_5Realm_T_bTSSCSo8RLMRealm_T_U_FTSSS2__T_ + 224
25 RealmSwift 0x0000000105f372af _TTRXFo_oSSoCSo8RLMRealm_dT__XFdCb_dCSo8NSStringdS__dT__ + 111
26 Realm 0x0000000105c0645a -[RLMRealm sendNotifications:] + 986
27 Realm 0x0000000105c068e6 -[RLMRealm commitWriteTransaction] + 262
28 Realm 0x0000000105c06a48 -[RLMRealm transactionWithBlock:] + 120
29 RealmSwift 0x0000000105f34250 _TFC10RealmSwift5Realm5writefS0_FFT_T_T_ + 176
30 MSUapp 0x00000001056d46db _TZFC6MSUapp14DatabaseHelper23removeForSportAndSeasonfMS0_FTCS_5Sport6seasonSS_T_ + 603
31 MSUapp 0x0000000105710d22 _TFFFC6MSUapp11AppDelegate14loadRemoteDataFS0_FT_T_U_FGSaCS_5Sport_T_U_FGSaCS_6League_T_ + 866
32 MSUapp 0x0000000105710dc7 _TTRXFo_oGSaC6MSUapp6League__dT__XFo_iGSaS0___iT__ + 23
33 MSUapp 0x00000001057103d1 _TPA__TTRXFo_oGSaC6MSUapp6League__dT__XFo_iGSaS0___iT__ + 81
34 MSUapp 0x000000010575de90 _TTRXFo_iGSaC6MSUapp6League__iT__XFo_oGSaS0___dT__ + 32
35 MSUapp 0x000000010575ddeb _TFZFC6MSUapp9APIHelper11loadLeaguesFMS0_FTSi18shouldWriteToRealmSb10completionGSqFGSaCS_6League_T___T_U_FCSo6NSDataT_ + 2763
36 MSUapp 0x00000001056f4a0e _TTSf2n_n_n_n_n_d_i_n_n_n___TFFC6MSUapp14JSONDataSource18loadRemoteJsonDataFS0_FTSSCS_19GETParameterBuilderFCSo6NSDataT__T_U_FTCSo12NSURLRequestGSqCSo17NSHTTPURLResponse_GSqS2__GSqCSo7NSError__T_ + 2302
37 MSUapp 0x00000001056f2d59 _TPA__TTSf2n_n_n_n_n_d_i_n_n_n___TFFC6MSUapp14JSONDataSource18loadRemoteJsonDataFS0_FTSSCS_19GETParameterBuilderFCSo6NSDataT__T_U_FTCSo12NSURLRequestGSqCSo17NSHTTPURLResponse_GSqS2__GSqCSo7NSError__T_ + 249
38 Alamofire 0x00000001059e7599 _TTRXFo_oCSo12NSURLRequestoGSqCSo17NSHTTPURLResponse_oGSqCSo6NSData_oGSqCSo7NSError__dT__XFo_oS_oGSqS0__iGSqS1__oGSqS2___dT__ + 25
39 Alamofire 0x00000001059e7461 _TFFFC9Alamofire7Request8responseFDS0_US_18ResponseSerializer___FT5queueGSqCSo8NSObject_18responseSerializerQ_17completionHandlerFTCSo12NSURLRequestGSqCSo17NSHTTPURLResponse_GSqQ0__GSqCSo7NSError__T__DS0_U_FT_T_U_FT_T_ + 737
40 Alamofire 0x00000001059e690e _TPA__TFFFC9Alamofire7Request8responseFDS0_US_18ResponseSerializer___FT5queueGSqCSo8NSObject_18responseSerializerQ_17completionHandlerFTCSo12NSURLRequestGSqCSo17NSHTTPURLResponse_GSqQ0__GSqCSo7NSError__T__DS0_U_FT_T_U_FT_T_ + 206
41 Alamofire 0x00000001059a89d7 _TTRXFo__dT__XFdCb__dT__ + 39
42 libdispatch.dylib 0x000000010938b186 _dispatch_call_block_and_release + 12
43 libdispatch.dylib 0x00000001093aa614 _dispatch_client_callout + 8
44 libdispatch.dylib 0x0000000109392a1c _dispatch_main_queue_callback_4CF + 1664
45 CoreFoundation 0x00000001065741f9 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
46 CoreFoundation 0x0000000106535dcb __CFRunLoopRun + 2043
47 CoreFoundation 0x0000000106535366 CFRunLoopRunSpecific + 470
48 GraphicsServices 0x000000010cc17a3e GSEventRunModal + 161
49 UIKit 0x00000001070f08c0 UIApplicationMain + 1282
50 MSUapp 0x000000010570f857 main + 135
51 libdyld.dylib 0x00000001093df145 start + 1
52 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
This call stack lets me assume, that it's because of my write access in FavoriteLeaguesViewController's generateLeagueList method. The following is its body:
var favorites = FavoritesHelper.sharedInstance.favoriteLeagues
favorites.sort { $0.sport < $1.sport }
for favorite in favorites {
// Add to array, which we can later use for cellForRowAtIndexPath
}
favorites is of the type [League], where League is a Realm Object. I'd assume the exception occurs because I'm accessing properties of the League objects, which have been deleted from the Realm database in the mean time (because the API call that has been started in the AppDelegate is now finished).
My question then is: How can I prevent this from happening? How can I make sure that there's no more writing/reading-access to any of the League objects prior to deleting them?
You can check if an object has been deleted from the Realm by calling object.invalidated -- if it returns true, then it has been deleted or the Realm has manually invalidated.
I got a really nice way to catch a RLMException within Swift.
Currently Swift doesn't show where a RLMException happened.
In Realm/RLMUtil.mm:266, there is the definition for RLMException.
If you change the code to generate a swift error,
Xcode now can show you where the exception occurred.
Now it is a part of Swift.
// Realm/RLMUtil.mm:266
static NSException *RLMException(NSString *reason, NSDictionary *additionalUserInfo) {
// add some code to generate a swift error. E.g. division-by-zero.
int a = 0;
if (reason == nil) {
a = 1;
}
NSLog(#"calculating 1 / %d = %f", a, 1 / a);
... remainder of the original code...
}
I've just place breakpoint inside method:
// Realm/RLMUtil.mm:193
static NSException *RLMException(NSString *reason, NSDictionary *additionalUserInfo) {
// ...
}
And on left panel you can check stack trace, here you can find where error throws.
The issue was in my FavoritesHelper class. It had both a favoriteLeagueIDs and favoriteLeagues property. I always set both of them and used the IDs for internal usage and the other property for whenever I want some data from these leagues.
This meant, that all favorite leagues were constantly referenced by the favoriteLeagues property (of the type [League]), thus crashing the app when I wanted to fetch them after they're invalidated.
What I've done to fix this, was to change the property favoriteLeagues to a computed property as follows:
var favoriteLeagues: [League] {
get {
var leagues = [League]()
for id in favoriteLeagueIDs {
if let league = realm.objectForPrimaryKey(League.self, key: id) {
leagues.append(league)
}
}
return leagues
}
}
Now the leagues are no longer referenced and just get loaded from the database when I need to read them. Invalidated or deleted objects don't get loaded because of the if let statement (the Realm.objectForPrimaryKey(:key:) method returns nil in such a case).
I was unable to place breakpoints within the Realm framework itself as others suggested, but instead placed an exception breakpoint on my entire project:
This allowed me to catch a proper stack trace when the exception was thrown, and find my bug.
you can calling isInvalidated or invalidated to judge the object is invalid (YES) or not (NO).
you can also write a a custom method to define what is real invalidate
look for the document ,we will see a property:
/**
Indicates if the object can no longer be accessed because it is now invalid.
An object can no longer be accessed if the object has been deleted from the Realm that manages it, or
if `invalidate` is called on that Realm.
*/
#property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated;
the invalidated is readonly but what does isInvalidated mean?
it is equals to - (BOOL)invalidated { return invalidated; }
it means that you can write a a custom method to define what is real invalidate you want.
Try to create the element on realm instead of add
So:
try! realm.write {
realm.add(...)
}
Replace with:
try! realm.write {
realm.create(...)
}
And then after the delete operation realm should work as expected!
In my experience, if you trying to use target object (which you wanna delete) after delete, application will crash.
If you wanna trigger some code blocks after removing realm object, just trying to trigger that block right before the object removing in the memory. Trying to usage that object after removed from memory, will make some problem and will crash the app.
For example:
try! realm.write {
print("deleted word: \(targetObject.word)")
realm.delete(targetObject)
// targetObject was removed, so don't try to access it otherwise you gonna got the 'nil' value instead of object.
}
after spent a day, i figure out with the remove DispatchQueue.main.async in my realm.delete() function and finally it worked.
DispatchQueue.main.async {
realm.delete()
}
to
realm.delete()
In my case I was deleting data from 2 tables at once.. one with a foreign to the other.
let itemToDelete = counters[indexPath.row]
let realm = try! Realm()
try! realm.write {
realm.delete(itemToDelete)
let predicate = NSPredicate(format: "counterid = \(c.id)")
let children = realm.objects(Details.self).filter(predicate)
realm.delete(children)
}
But the problem was that I was trying to delete the children of the item that does not exist anymore. Switching the order, solved it!
let itemToDelete = counters[indexPath.row]
let realm = try! Realm()
try! realm.write {
let predicate = NSPredicate(format: "counterid = \(c.id)")
let children = realm.objects(Details.self).filter(predicate)
realm.delete(children)
realm.delete(itemToDelete) //this should be deleted after
}
Hope this helps someone else!
Here are some of the reasons based on my past experiences.
Overriding NSObject's isEqual(_ object: Any?), then returning identifier which was just deleted by Realm.
How did I fix it?
Check if object is invalidated if so return false otherwise proceed with using your identifier
Example:
extension YourModel {
override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? YourModel,
!self.isInvalidated else {
return false
}
return object.id == self.id
}
}
Using Diffing tools
For those using diffing tools like RxDataSources, IGListKit etc.. you have to perform the same check as mentioned above before trying to access the indentifier.
Here is a work round if you are using RxDataSources that prevent the crash caused by adopting to IdentifiableType protocol.
Check if the Realm object was invalidated. If so, just return a random unique value, like so:
var identity: String {
return isInvalidated ? "deleted-object-\(UUID().uuidString)" : objectId
}
Source: - https://github.com/RxSwiftCommunity/RxDataSources/issues/172#issuecomment-427675816
What you can do is observe the Results<> object returned from your initial Realm query (the one used to populate your list/table view) and update that list in the observer callback method whenever there is a change in the database.
Just make sure that you use the same Realm instance to delete/modify the object as the one used for the initial query.
EDIT:
Some code examples:
let realm = Realm()
let results = realm.objects(Favourite.self)
let notificationToken = results.observe { [weak self] (changes) in
guard let tableView = self?.tableView else { return }
tableView.reloadData()
}
// Somewhere else
try? realm.write {
if let favourite = results.first {
realm.delete(favourite)
}
}
// OR
if let objectRealm = results.first?.realm {
try? objectRealm.write {
objectRealm.delete(results.first!)
}
}
// Don't forget to stop observing in deinit{}
// notificationToken.invalidate()
You can also use the #ObservedRealmObject or #ObservedResults property wrapper, you implicitly open a realm and retrieve the specified objects or results. You can then pass those objects to a view further down the hierarchy.
And use the .onDelete(perform: ) method. In example: .onDelete(perform: $dogs.remove) see below:
(SwiftUI)
struct DogsView: View {
#ObservedResults(Dog.self) var dogs
/// The button to be displayed on the top left.
var leadingBarButton: AnyView?
var body: some View {
NavigationView {
VStack {
// The list shows the dogs in the realm.
// The ``#ObservedResults`` above implicitly opens a realm and retrieves
// all the Dog objects. We can then pass those objects to views further down the
// hierarchy.
List {
ForEach(dogs) { dog in
DogRow(dog: dog)
}.onDelete(perform: $dogs.remove) **//<- Deletion of the Object**
}.listStyle(GroupedListStyle())
.navigationBarTitle("Dogs", displayMode: .large)
.navigationBarBackButtonHidden(true)
.navigationBarItems(
leading: self.leadingBarButton,
// Edit button on the right to enable rearranging items
trailing: EditButton())
}.padding()
}
}
}
Souce: MongoDB - CRUD Swift SDK

Resources