I have following function in a swift file. I am calling this from Obj C file with NSDate in place of startDate. And quite often, not every time, my app crashes with
Date._unconditionallyBridgeFromObjectiveC(NSDate?)
How can I fix this?
func trackMeetingEnded(_ name: String, startDate: Date, backgroundTime: TimeInterval) {}
Stack trace
Crashed: com.apple.main-thread
0 libswiftFoundation.dylib 0x102061e98 static Date._unconditionallyBridgeFromObjectiveC(NSDate?) -> Date + 72
1 Acid 0x10017ece4 @objc static ClusteredMixpanel.trackMeetingEnded(String, startDate : Date, backgroundTime : Double) -> () (MixpanelMeeting.swift)
2 Acid 0x10073e1bc __56-[MeetingLifeCycleViewController stateInitialization]_block_invoke.221 (MeetingLifeCycleViewController.m:267)
3 Acid 0x1001ee5c4 partial apply for thunk (StateMachine.swift)
4 Acid 0x1001ea70c specialized State.willLeaveState(State) -> () (StateMachine.swift:238)
5 Acid 0x1001ead90 specialized StateMachine.transitionToState(State) -> Bool (StateMachine.swift)
6 Acid 0x1001e1f18 @objc StateMachine.transitionToState(State) -> Bool (StateMachine.swift)
7 Acid 0x10073ace0 -[MeetingLifeCycleViewController dismissCall] (MeetingLifeCycleViewController.m:538)
8 Acid 0x10086d648 -[InMeetingViewController hangup] (InMeetingViewController.m:531)
enter code here
I believe NSDate to Date conversion is done by OS in this case. The issue is seen only after migration to Swift 3. Are there any known issues around this? I could not find anything online :(
You probably need to double check that the NSDate
coming from Objective-C is actually non-nil
, since there's nothing that's actively enforcing that on the Obj-C side the way there is on the Swift side.
I wound up converting as many Date
s to Date?
s as I could find being called from Obj-C, then doing a whole lot of guard let
checks.
You can also throw in an assertionFailure
to yell at you when you're in development about where those nil
dates you're not expecting are coming from. For example:
guard let date = passedInDate else {
assertionFailure("Turns out the passed-in date was nil!")
return
}
Then take a look at your stack trace when that gets hit to see if you can get a better grip on why you're getting an unexpected nil
value there.
Update: Here's where the crash is occurring in the Swift source code.
This answer is meant for those that face this issue with Core Data and Swift:
In a Core Data NSManagedObject
, you have to be careful when representing types marked as non-optional in your data model in Swift.
Core Data objects are dynamic in nature and values in memory are fulfilled dynamically at runtime by design. This isn't compatible with Swift's concept of truly non-optional types unless you define a default value in your data model.
Note that the auto generated class by default always resorts to an optional even if the attribute is marked as non-optional in the data model. To properly support non-optionals, you have to define a default value in the model.
You may think this is a mistake in Apple's part, but it isn't. A Core Data object can be deleted and you can still have a reference to it somewhere in the code (see the isDeleted
property), therefore things that are marked as non-optional can be gone at runtime since Core Data can never fulfill its dynamic expectation.
While String
and Int
, for example, will "unconditionally bridge" to "" and 0
when nil
, Date
will crash as seen in this question.
Note that this isn't the case if you mark it as NSDate
(it will return nil even if marked as non-optional), but this is not meant to be a workaround and is an implementation detail.
Test case:
Assuming your data model has date1
, and date2
, both non-optional and without default values.
@NSManaged public var date1: Date?
@NSManaged public var date2: Date
let myObject = ...
// Assuming your object is valid, inserted, and the context is saved
managedObjectContext.delete(myObject)
try managedObjectContext.save()
// This will be nil
NSLog("\(myObject.date1)")
// This will crash
NSLog("\(myObject.date2)")
In conclusion, you can use non-optional types without default values in Core Data, just be careful, as at runtime they may disappear and crash in the case of Date, or be represented in something you don't expect as in the case of String and Int.
Note: you should still make use of the ability to mark an attribute as non-optional, even if in Swift it is represented as an optional. This is taken into account into Core Data's validation code for objects.
Extending Marc Etcheverry answer for swift & coreData + multithreading environment.
Be aware that NSFetchRequest
has a property called returnsObjectsAsFaults
that is true
by default (apple doc) this means that you will retrieve your NSManagedObject
but its properties won't actually be filled until you access them, so if by any chance a different thread removed that instance from core data by the moment you're accessing the property (Date in this case Date._unconditionallyBridgeFromObjectiveC
) it can be nil and your non-optional Date
can crash the app.
Picture this scenario:
Thread 1
- [step 1] let values = Fetch [NSManagedObject]
- [step 3] let values[0].someDate // here app will crash
Since returnsObjectsAsFaults
is true
[by default] someDate
property was not fetched until you tried to access it.
Thread 2
- [step 2] update coreData - hence removing objects fetched on Thread 1 - step 1
Solution: if you do need all your properties from the beginning from your NSManagedObject
make sure to set on your fetchRequest:
let request = NSFetchRequest<T>(entityName: "someEntity")
...
request.returnsObjectsAsFaults = false
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With