This cast crashes the app if nil is casted (that may happen if the object is not available in the context), and this crash if not catched, as this is a fatal crash.
existingObjectWithID
If the object cannot be fetched, or does not exist, or cannot be faulted, it returns nil.
Why not use "as?" instead?
A bit another, though related issue is if the object was never saved, then the app crashes with:
2016-09-29 12:19:17.898 LayfootakUserApp[55561:7950554] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'nil is not a valid object ID'
In my opinion it should not crash and just return nil.
Could easily be replacing it with this in singe object fetchExisting() methods:
Here I attach fixed sources that resolve these two issues: fixed-sources.zip
Originally created by @mkadan on GitHub (Sep 29, 2016).
please check DataStack+Querying.swift and BaseDataTransaction+Querying.swift
Four fetchExisting() methods there have the "as!" cast, e.g.:
> return (try self.context.existingObjectWithID(object.objectID) as! T)
This cast crashes the app if nil is casted (that may happen if the object is not available in the context), and this crash if not catched, as this is a fatal crash.
> existingObjectWithID
> If the object cannot be fetched, or does not exist, or cannot be faulted, it returns nil.
Why not use "as?" instead?
---
A bit another, though related issue is if the object was never saved, then the app crashes with:
> 2016-09-29 12:19:17.898 LayfootakUserApp[55561:7950554] **\* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'nil is not a valid object ID'
In my opinion it should not crash and just return nil.
Could easily be replacing it with this in singe object fetchExisting() methods:
> return (object.objectID.persistentStore != nil) ? (try self.context.existingObjectWithID(object.objectID) as? T) : nil
and with this in multi object fetchExisting() methods:
> return objects.flatMap { ($0.objectID.persistentStore != nil) ? (try? self.mainContext.existingObjectWithID($0.objectID)) as? T : nil }
---
Here I attach fixed sources that resolve these two issues:
[fixed-sources.zip](https://github.com/JohnEstropia/CoreStore/files/500483/fixed-sources.zip)
adam
added the user bug label 2025-12-29 15:24:14 +01:00
The as! T casting done by CoreStore just casts the non-optional NSManagedObject to T.
A bit another, though related issue is if the object was never saved, then the app crashes with:
2016-09-29 12:19:17.898 LayfootakUserApp[55561:7950554] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'nil is not a valid object ID'
None of CoreStore's fetchExisting() methods accepts an optional. So if you are passing nil there then Swift has nothing else to do but crash. I am guessing that a nil value from your Objective-C code is getting through Swift bridged code, so you should start to debug where that happens.
@JohnEstropia commented on GitHub (Sep 29, 2016):
@mkadan The documentation you are referring to is for Objective-C
```
If the object cannot be fetched, or does not exist, or cannot be faulted, it returns nil.
```
In Objective-C, errors are returned from methods by returning `nil` and populating the `error` parameter:
``` objc
- (__kindof NSManagedObject *)existingObjectWithID:(NSManagedObjectID *)objectID
error:(NSError * _Nullable *)error;
```
In Swift, this method does not return an optional, and so all `nil` returns will be bridged by swift as a `throw`:
``` swift
func existingObject(with objectID: NSManagedObjectID) throws -> NSManagedObject
```
`existingObject()` 's documentation:
https://developer.apple.com/reference/coredata/nsmanagedobjectcontext/1506686-existingobject
The `as! T` casting done by CoreStore just casts the non-optional `NSManagedObject` to `T`.
> A bit another, though related issue is if the object was never saved, then the app crashes with:
> 2016-09-29 12:19:17.898 LayfootakUserApp[55561:7950554] **\* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'nil is not a valid object ID'
None of CoreStore's `fetchExisting()` methods accepts an optional. So if you are passing `nil` there then Swift has nothing else to do but crash. I am guessing that a `nil` value from your Objective-C code is getting through Swift bridged code, so you should start to debug where that happens.
Ok, I see your point for the issue N1, and if it works like this, that's ok for me.
But please check this sample code for issue N2, it crashes, though in my opinion it should not:
CoreStore.beginAsynchronous {
(transaction) in
let user = User()
let user$ = transaction.fetchExisting(user)
}
It may happen not only if the instance was just created but also when it was deleted, like it happens in our app.
@mkadan commented on GitHub (Sep 29, 2016):
Ok, I see your point for the issue N1, and if it works like this, that's ok for me.
But please check this sample code for issue N2, it crashes, though in my opinion it should not:
> ```
> CoreStore.beginAsynchronous {
> (transaction) in
> let user = User()
> let user$ = transaction.fetchExisting(user)
> }
> ```
It may happen not only if the instance was just created but also when it was deleted, like it happens in our app.
You can't create NSManagedObject's directly through their default initializer. That object won't have a parent NSManagedObjectContext. Use the transaction's create method for this:
@JohnEstropia commented on GitHub (Sep 29, 2016):
You can't create `NSManagedObject`'s directly through their default initializer. That object won't have a parent `NSManagedObjectContext`. Use the `transaction`'s create method for this:
``` swift
CoreStore.beginAsynchronous {
(transaction) in
let user = transaction.create(Into<User>())
}
```
Ok thanks for your explanation. I tried to create a sample based on our code that fails, but seems it's not that easy to do, as it involves cascaded deletes. I'll provide it later when I extract the problem, of course if that's really a problem in CoreStore.
@mkadan commented on GitHub (Sep 29, 2016):
Ok thanks for your explanation. I tried to create a sample based on our code that fails, but seems it's not that easy to do, as it involves cascaded deletes. I'll provide it later when I extract the problem, of course if that's really a problem in CoreStore.
I was able to reproduce the problem on sample project:
Company has ToMany-DeleteCascade relation to User, User has ToOne-NullifyCascade relation to Company.
CoreStore.beginSynchronous {
(transaction) in
let user$ = transaction.create(Into(User))
let company$ = transaction.create(Into(Company))
user$.company = company$
transaction.commitAndWait()
}
let user = CoreStore.fetchOne(From(User))!
CoreStore.beginSynchronous {
(transaction) in
let user$ = transaction.fetchExisting(user)
transaction.delete(user$)
transaction.commitAndWait()
}
let company = user.company
// it's objectID is 00000000000000000000 and has no persistenceStore set
CoreStore.beginSynchronous {
(transaction) in
// crashes
let company$ = transaction.fetchExisting(company)
transaction.commitAndWait()
}
in fetchExisting() implementation fixes this crash
@mkadan commented on GitHub (Sep 29, 2016):
I was able to reproduce the problem on sample project:
Company has ToMany-DeleteCascade relation to User, User has ToOne-NullifyCascade relation to Company.
> ```
> CoreStore.beginSynchronous {
> (transaction) in
> let user$ = transaction.create(Into(User))
> let company$ = transaction.create(Into(Company))
>
> user$.company = company$
>
> transaction.commitAndWait()
> }
>
> let user = CoreStore.fetchOne(From(User))!
>
> CoreStore.beginSynchronous {
> (transaction) in
> let user$ = transaction.fetchExisting(user)
> transaction.delete(user$)
>
> transaction.commitAndWait()
> }
>
> let company = user.company
> // it's objectID is 00000000000000000000 and has no persistenceStore set
>
> CoreStore.beginSynchronous {
> (transaction) in
> // crashes
> let company$ = transaction.fetchExisting(company)
>
> transaction.commitAndWait()
> }
> ```
using
> return (object.objectID.persistentStore != nil) ? (try self.context.existingObjectWithID(object.objectID) as! T) : nil
in fetchExisting() implementation fixes this crash
Thats expected behavior. You accessed an object that is supposedly deleted. My guess is that user.community is supposed to return nil, but the Swift compiler cannot infer that for you during compile time, so you end up with a broken 0-object. In any case, CoreStore can't protect you from accessing deleted objects directly like this, as that is Core Data's internal behavior
@JohnEstropia commented on GitHub (Sep 29, 2016):
Thats expected behavior. You accessed an object that is supposedly deleted. My guess is that user.community is supposed to return nil, but the Swift compiler cannot infer that for you during compile time, so you end up with a broken 0-object. In any case, CoreStore can't protect you from accessing deleted objects directly like this, as that is Core Data's internal behavior
That's not exactly a Core Data bug. It is the responsibility of the app to design an architecture so this doesn't happen. If anything, CoreStore should be guarding against this with an assert(!object.isDeleted), because tolerating this will most likely just cause another problem somewhere else. On iOS 8 and below, your code will probably already crash on the line
let company = user.company
(iOS 9's new shouldDeleteInaccessibleFaults just keeps the object up for a while)
In any case, I suggest that this be handled on the app side.
@JohnEstropia commented on GitHub (Sep 29, 2016):
That's not exactly a Core Data bug. It is the responsibility of the app to design an architecture so this doesn't happen. If anything, CoreStore should be guarding against this with an `assert(!object.isDeleted)`, because tolerating this will most likely just cause another problem somewhere else. On iOS 8 and below, your code will probably already crash on the line
```
let company = user.company
```
(iOS 9's new `shouldDeleteInaccessibleFaults` just keeps the object up for a while)
In any case, I suggest that this be handled on the app side.
Thanks for your answer. I think we will try to review our code to exclude such cases from it, and if it is not possible for some reason, will write an extension method to CoreStore transaction to perform this check outside of the core library functionality and test it carefully for our app, as you have a point that it may break something else inside the CoreStore.
From your side, is it possible you include this assert with some reasonable message, so that users may know what actually caused the crash?
@mkadan commented on GitHub (Sep 29, 2016):
Thanks for your answer. I think we will try to review our code to exclude such cases from it, and if it is not possible for some reason, will write an extension method to CoreStore transaction to perform this check outside of the core library functionality and test it carefully for our app, as you have a point that it may break something else inside the CoreStore.
From your side, is it possible you include this assert with some reasonable message, so that users may know what actually caused the crash?
We could do that, but iOS 9 should already be giving you a warning log when this happens. Also, on iOS 8 your app will be crashing with the original exception anyway.
@JohnEstropia commented on GitHub (Sep 29, 2016):
We could do that, but iOS 9 should already be [giving you a warning log](http://stackoverflow.com/a/32503486/809614) when this happens. Also, on iOS 8 your app will be crashing with the original exception anyway.
I'll experiment for a while with that assert(). If it changes the current behavior of fetchExisting(), I might not put it in (example, if there are previous cases where isDeleted is marked but goes through fetchExisting() returning nil, adding an assert() will be a breaking change for those use cases)
@JohnEstropia commented on GitHub (Sep 29, 2016):
I'll experiment for a while with that `assert()`. If it changes the current behavior of fetchExisting(), I might not put it in (example, if there are previous cases where `isDeleted` is marked but goes through `fetchExisting()` returning nil, adding an `assert()` will be a breaking change for those use cases)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Originally created by @mkadan on GitHub (Sep 29, 2016).
please check DataStack+Querying.swift and BaseDataTransaction+Querying.swift
Four fetchExisting() methods there have the "as!" cast, e.g.:
This cast crashes the app if nil is casted (that may happen if the object is not available in the context), and this crash if not catched, as this is a fatal crash.
Why not use "as?" instead?
A bit another, though related issue is if the object was never saved, then the app crashes with:
In my opinion it should not crash and just return nil.
Could easily be replacing it with this in singe object fetchExisting() methods:
and with this in multi object fetchExisting() methods:
Here I attach fixed sources that resolve these two issues:
fixed-sources.zip
@JohnEstropia commented on GitHub (Sep 29, 2016):
@mkadan The documentation you are referring to is for Objective-C
In Objective-C, errors are returned from methods by returning
niland populating theerrorparameter:In Swift, this method does not return an optional, and so all
nilreturns will be bridged by swift as athrow:existingObject()'s documentation:https://developer.apple.com/reference/coredata/nsmanagedobjectcontext/1506686-existingobject
The
as! Tcasting done by CoreStore just casts the non-optionalNSManagedObjecttoT.None of CoreStore's
fetchExisting()methods accepts an optional. So if you are passingnilthere then Swift has nothing else to do but crash. I am guessing that anilvalue from your Objective-C code is getting through Swift bridged code, so you should start to debug where that happens.@mkadan commented on GitHub (Sep 29, 2016):
Ok, I see your point for the issue N1, and if it works like this, that's ok for me.
But please check this sample code for issue N2, it crashes, though in my opinion it should not:
It may happen not only if the instance was just created but also when it was deleted, like it happens in our app.
@JohnEstropia commented on GitHub (Sep 29, 2016):
You can't create
NSManagedObject's directly through their default initializer. That object won't have a parentNSManagedObjectContext. Use thetransaction's create method for this:@mkadan commented on GitHub (Sep 29, 2016):
Ok thanks for your explanation. I tried to create a sample based on our code that fails, but seems it's not that easy to do, as it involves cascaded deletes. I'll provide it later when I extract the problem, of course if that's really a problem in CoreStore.
@mkadan commented on GitHub (Sep 29, 2016):
I was able to reproduce the problem on sample project:
Company has ToMany-DeleteCascade relation to User, User has ToOne-NullifyCascade relation to Company.
using
in fetchExisting() implementation fixes this crash
@JohnEstropia commented on GitHub (Sep 29, 2016):
Thats expected behavior. You accessed an object that is supposedly deleted. My guess is that user.community is supposed to return nil, but the Swift compiler cannot infer that for you during compile time, so you end up with a broken 0-object. In any case, CoreStore can't protect you from accessing deleted objects directly like this, as that is Core Data's internal behavior
@mkadan commented on GitHub (Sep 29, 2016):
But is it possible to add this check to CoreStore just as a workaround for CoreData bug?
@JohnEstropia commented on GitHub (Sep 29, 2016):
That's not exactly a Core Data bug. It is the responsibility of the app to design an architecture so this doesn't happen. If anything, CoreStore should be guarding against this with an
assert(!object.isDeleted), because tolerating this will most likely just cause another problem somewhere else. On iOS 8 and below, your code will probably already crash on the line(iOS 9's new
shouldDeleteInaccessibleFaultsjust keeps the object up for a while)In any case, I suggest that this be handled on the app side.
@mkadan commented on GitHub (Sep 29, 2016):
Thanks for your answer. I think we will try to review our code to exclude such cases from it, and if it is not possible for some reason, will write an extension method to CoreStore transaction to perform this check outside of the core library functionality and test it carefully for our app, as you have a point that it may break something else inside the CoreStore.
From your side, is it possible you include this assert with some reasonable message, so that users may know what actually caused the crash?
@JohnEstropia commented on GitHub (Sep 29, 2016):
We could do that, but iOS 9 should already be giving you a warning log when this happens. Also, on iOS 8 your app will be crashing with the original exception anyway.
@JohnEstropia commented on GitHub (Sep 29, 2016):
I'll experiment for a while with that
assert(). If it changes the current behavior of fetchExisting(), I might not put it in (example, if there are previous cases whereisDeletedis marked but goes throughfetchExisting()returning nil, adding anassert()will be a breaking change for those use cases)