Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Date._unconditionallyBridgeFromObjectiveC(NSDate?) crash in Swift 3

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 :(

like image 377
Vinuta Avatar asked May 17 '17 17:05

Vinuta


3 Answers

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 Dates 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.

like image 54
DesignatedNerd Avatar answered Nov 01 '22 04:11

DesignatedNerd


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.

like image 15
Marc Etcheverry Avatar answered Nov 01 '22 06:11

Marc Etcheverry


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
like image 2
vberihuete Avatar answered Nov 01 '22 06:11

vberihuete