简体   繁体   中英

Issue with fetching Core Data managed object using NSPredicate by object's enum attribute

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

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)") }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM