How do I write this #Predicate?

I have two Models which are defined (simplified) as follows:

@Model final class Book {
    var id: UUID = UUID()
    var title: String = ""
    var authors: [Author]? = []
}

@Model final class Author {
    var id: UUID = UUID()
    var name: String ""
    @Relationship(inverse: \Book.authors) var books: [Book]? = []
}

Both Book.authors and Author.books are defined as optionals to satisfy the requirements of CloudKit, although it is clear from the above that neither is ever actually nil.

I am trying to write a #Predicate that finds objects of type Book with a title that matches a specified title and with an author that matches a specified author. The function I'd like to write looks something like this:

func find(title: String, author: Author, 
          with context: ModelContext) -> Book? {
    let same = ComparisonResult.orderedSame
    let predicate = #Predicate<Book> { book in 
        book.title.caseInsensitiveCompare(title) == same &&
        book.authors!.contains(where: { $0.id == author.id })
    }
    let descriptor = FetchDescriptor<Book>(predicate: predicate)
    return try? context.fetch(descriptor).first
}

This function fails to compile inside the expansion of the #Predicate macro with a very long error message:

Cannot convert value of type 
(a long 'PredicateExpresions....')
(aka
(another log 'PredicateExpressions....'))
to closure result type 'any StandardPredicateExpression<Bool>'

I don't understand. How can I write this predicate so that it will both compile and work? I know how to work around this problem, but would like to both deepen my understanding of Swift and SwiftData and to avoid the (inelegant) workaround.

Answered by DTS Engineer in 902952022

Your code example looks good to me... Would you mind to share your full code example so I can reproduce the error?

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Accepted Answer

Your code example looks good to me... Would you mind to share your full code example so I can reproduce the error?

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

I thought it looked good to me too — that's why I posted the note. Well, I think (just maybe) that I encountered an Xcode problem (with 27.0 beta 5).

First I changed the function as follows:

func find(title: String, author: Author, 
          with context: ModelContext) -> Book? {
    let authorID = author.id    // <== change
    let same = ComparisonResult.orderedSame
    let predicate = #Predicate<Book> { book in 
        book.title.caseInsensitiveCompare(title) == same &&
        book.authors!.contains(where: { $0.id == authorID})    <== change
    }
    let descriptor = FetchDescriptor<Book>(predicate: predicate)
    return try? context.fetch(descriptor).first
}

and tried rebuilding. That failed with the same error messages as before. So I did a clean on the project and tried again. And it worked.

I think that somewhere in macro processing, there is a cache of the macro's result-code that is not being cleared when it should be.

And that implies that this post is actually a red herring. Sorry...

How do I write this #Predicate?
 
 
Q