2

I have an issue with fetching a Core Data managed object which has an enum attribute with Int16 rawValues. The program crashes when calling NSPredicate(format:).

This is the code (3rd line) that results a crash:

func returnBook (bookType: BookType) -> Book? {
    let fetchRequest = NSFetchRequest<Book>(entityName: "Book")
    fetchRequest.predicate = NSPredicate(format: "%K = %@", #keyPath(Book.bookType), bookType.rawValue as CVarArg)
    fetchRequest.fetchLimit = 1
    let book = (try? fetchRequest.execute())?.first
    return book
}

Crash message is:

Thread 1: EXC_BAD_ACCESS (code=1, address=0x3)

When I change the 3rd line with the following, I get another kind of error.

This is the alternative code:

fetchRequest.predicate = NSPredicate(format: "%K = %@", #keyPath(Book.bookType), bookType as! CVarArg)

And this is the error resulted by that alternative code:

Thread 1: signal SIGABRT

And I see this in the debug output:

Could not cast value of type 'ProjectName.BookType' (0x10ac10098) to 'Swift.CVarArg' (0x10b698548).

Here are some other related code in the Project:

@objc(Book)
public class Book: NSManagedObject {
    // …
}

extension Book {

    @NSManaged public var bookType: BookType

}

@objc public enum BookType: Int16 {
    case fiction    = 0
    case nonFiction = 1
    case reference  = 2
}

My question: How can I properly fetch the Book object from my Core Data store by using its bookType attribute, which is in an @objc enum type with Int16 rawValues?

Update:

I have tried something else as suggested in a presentation by Jesse Squires:

  1. Removed @objc keyword from enum's definition
  2. Used private and public properties to access the enum's instances...

I have changed my code as suggested in the linked presentation, and I have changed the name of bookType attribute in Core Data's ".xcdatamodeld" file to bookTypeValue.

extension Book {

    public var bookType: BookType {
        get {
            return BookType(rawValue: self.bookTypeValue)!
        }
        set {
            self.bookTypeValue = newValue.rawValue
        }
    }

    @NSManaged private var bookTypeValue: Int16

}

public enum BookType: Int16 {
    case fiction    = 0
    case nonFiction = 1
    case reference  = 2
}

And I tried to fetch using the private value as suggested in the presentation linked above...:

fetchRequest.predicate = NSPredicate(format: "bookTypeValue == %@", bookType)

However I could not solve my problem with this method either... XCode complains immediately:

Argument type 'BookType' does not conform to expected type 'CVarArg'

XCode stops complaining if I use bookType.rawValue instead. However, program crashes during runtime with the same error above (first one).

Update 2:

I have applied the answers given to this question which is suggested by the community. To be more specific, I have tried the suggestion in the answer to that question which is using %i, or @ld instead of %@ like so:

fetchRequest.predicate = NSPredicate(format: "bookTypeValue == %i", bookType.rawValue)

and I have tried the other suggestion which is using String interpolation like so:

fetchRequest.predicate = NSPredicate(format: "bookTypeValue == %@", "\(bookType.rawValue)")

These code changes prevented crashes, but func returnBook returns nil, even if I am sure that the data is there. Fetch or comparison fails for some reason...

Luke
  • 965
  • 8
  • 21
  • I guess bookType must be defined as Transformable in the model editor? When you renamed it to bookTypeValue, did you change it to Integer? Predicates do not work well for Transformable attributes (at least not when fetching). – pbasdf Feb 10 '20 at 17:29
  • @pbasdf bookType was defined as Int16 in the model editor (as suggested here: https://stackoverflow.com/a/31681870/3780985 ), and after I have renamed it to bookTypeValue, it remained the same in the model editor as Int16. I only changed its type in the code from BookType to Int16. – Luke Feb 10 '20 at 19:59

1 Answers1

0

After struggling with this problem for many hours finally I have managed to solve it.

Here is what I have done to solve the problem in chronological order:

  1. I have changed the type of bookTypeValue from Int16 to Int32, because I could not see a format specifier for 16-bit integers in the official (archived) documentation. I have tested and I can confirm that if you use %@ here instead of correct format specifier, which is %d for signed 32-bit integers, you get the first error in the question:

Thread 1: EXC_BAD_ACCESS (code=1, address=0x3)

  1. I have changed the NSPredicate(format:) call like so:

    fetchRequest.predicate = NSPredicate(format: "bookTypeValue == %d", bookType.rawValue)

  2. I have changed the 5th line in returnBook func like so:

     do {
        let results = try coreDataStack.managedContext.fetch(fetchRequest)
        if results.count > 0 {
            book = results.first
            return book
        } 
     } catch let error as NSError {
        print("Fetch error: \(error) description: \(error.userInfo)")
     }
    
Luke
  • 965
  • 8
  • 21