swiftcore-datansfetchrequest

Swift Core Data - handling empty fetch result


I have a simple entity, a stationID and a type, both Strings

I use this method to search for a type for a given stationID,

 func returnStationType(stationTypeId: String) -> PersistantStationType {
        
        let context = container.viewContext
        
        let request = PersistantStationType.fetchRequest() as NSFetchRequest<PersistantStationType>
        request.predicate = NSPredicate(format: "%K == %@", #keyPath(PersistantStationType.stationId), stationTypeId as CVarArg)
        
        do {
            
            let result = try context.fetch(request)
            if result.count != 0 {
                
                let fetchedStationType = result.first!
                
                return fetchedStationType
                
            } else { print("4 Fetch result was empty for specified stationid: \(String(describing: stationTypeId))")
                
            }
            
        } catch { print("Fetch on goal id: \(String(describing: stationTypeId)) failed. \(error)") }
        
        
        return PersistantStationType.init()
    
    }

I call the method here:

let persistentStationType = persistenceController.returnStationType(stationTypeId: closestStation.id)
                
                let stationType = persistentStationType.type?.lowercased().capitalized
                
                let realName = helper.returnRealName(stationType: stationType ?? "None")
                
                let imageName = helper.returnStationTypeImage(stationType: stationType ?? "None")

If a stationId is not found - so empty result I get a crash in the code(Bad Exec/ Access) where I call the method - not in the method itself. (I'm not surprised by this but i'm not sure how to handle it)

I can't use if let on the method .. .it's not an optional. Should I be returning an empty object and check for It when the fetch result is empty?

Thanks for any help.


Solution

  • I would declare the function to return an optional and return nil when nothing is found

    func returnStationType(stationTypeId: String) -> PersistantStationType? {
        let context = container.viewContext
    
        let request: NSFetchRequest<PersistantStationType> = PersistantStationType.fetchRequest()
        request.predicate = NSPredicate(format: "%K == %@", #keyPath(PersistantStationType.stationId), stationTypeId)
    
        do {
            let result = try context.fetch(request)
    
            if result.count != 0 {
                return result[0]
            } else {
                print("4 Fetch result was empty for specified stationid: \(String(describing: stationTypeId))")
                return nil
            }
        } catch {
            print("Fetch on goal id: \(String(describing: stationTypeId)) failed. \(error)")
            return nil
        }
    }
    

    If on the error hand it is considered an error if more than one objects (or none) exists for an id then it would be better to throw an error in those situations

    func returnStationType(stationTypeId: String) throws -> PersistantStationType {
        let context = container.viewContext
    
        let request: NSFetchRequest<PersistantStationType> = PersistantStationType.fetchRequest()
    
        request.predicate = NSPredicate(format: "%K == %@", #keyPath(PersistantStationType.stationId), stationTypeId)
    
        do {
            let result = try context.fetch(request)
    
            switch result.count {
            case 0:
                throw FetchError("No station type found for id \(stationTypeId)")
            case 1:
                return result[0]
            default:
                throw FetchError("Multiple station types found for id \(stationTypeId)")
            }
        } catch let error as NSError {
            throw FetchError(error.localizedDescription)
        }
    }
    
    struct FetchError: LocalizedError {
        private let message: String
    
        var errorDescription: String? {
            message
        }
    
        init(_ message: String) {
            self.message = message
        }
    }