In a multi-language app I let users login via Sign In with Apple, Google or Facebook and then store their data (user id, given, last name, etc.) into UserEntity
:
When creating an entity I set the stamp
attribute to current epoch seconds.
Then in my custom view model I try to fetch at least one user with the following code and assign it to a @Published var
, so that my app knows if the user has already logged in:
class UserViewModel: ObservableObject {
@Published var myUser: UserEntity?
init(language: String) {
fetchMyUser(language: language)
}
func fetchMyUser(language:String) {
print("fetchMyUser language=\(language)")
guard let container = PersistenceController.shared[language]?.container else { return }
let request = NSFetchRequest<UserEntity>(entityName: "UserEntity")
request.sortDescriptors = [ NSSortDescriptor(keyPath: \UserEntity.stamp, ascending: false) ]
request.fetchLimit = 1
do {
myUser = try container.viewContext.fetch(request)[0]
} catch let error {
print("fetchMyUser fetch error: \(error)")
}
}
}
Note: please ignore the language
String parameter ("en", "de" or "ru"). It is being used to select the corresponding NSPersistentContainer
. This code works and is not related to my question.
My problem is that the do/try/catch
code fails with Fatal error: Index out of range
when there are no entities in the container yet (the user has never logged in):
Shouldn't do/try/catch
just catch this exception, why does my app crash and how to fix my code, so that 0 or 1 UserEntity
is fetched and assigned to myUser
?
The do/try/catch
mechanism in Swift catches Error
s which are throw
n somewhere else, see Error Handling for the details. It is not a general exception handler mechanism.
In particular, “index out of bounds” is a runtime error and cannot be caught. It will always terminate the program immediately.
viewContext.fetch(request)
throws an error if the fetch request could not be executed. Otherwise it returns an array or zero or more objects. In order to check if (at least) one object was returned, you have to check the count
or isEmpty
property of the return array.
Another option (as mentioned in the comments), is to use the first
property, which returns an Optional
: either the first element (if the array is not empty), or nil
. In your case that would be
do {
myUser = try container.viewContext.fetch(request).first
} catch let error {
print("fetchMyUser fetch error: \(error)")
}