{"id":43414,"date":"2024-05-28T14:03:38","date_gmt":"2024-05-28T18:03:38","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=43414"},"modified":"2025-12-15T23:37:24","modified_gmt":"2025-12-16T04:37:24","slug":"dynamic-swift-predicates-in-macos-14-and-ios-17","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2024\/05\/28\/dynamic-swift-predicates-in-macos-14-and-ios-17\/","title":{"rendered":"Dynamic Swift Predicates in macOS 14 and iOS 17"},"content":{"rendered":"<p><a href=\"https:\/\/mastodon.social\/@helge\/110724291661760686\">Helge He&szlig;<\/a>:<\/p>\n<blockquote cite=\"https:\/\/mastodon.social\/@helge\/110724291661760686\"><p>The new Foundation\/#Swiftlang <code>Predicate<\/code>s (and its expressions) seem a little weird because they can&rsquo;t be constructed dynamically?<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/www.reddit.com\/r\/iOSProgramming\/comments\/18t2gc1\/so_is_it_really_just_impossible_to_have_dynamic\/\">Fly0strich<\/a>:<\/p>\n<blockquote cite=\"https:\/\/www.reddit.com\/r\/iOSProgramming\/comments\/18t2gc1\/so_is_it_really_just_impossible_to_have_dynamic\/\">\n<p>However, when I try to use that method inside of a <code>#Predicate<\/code> closure, it gives an error saying that the method is not supported by this predicate.<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/mastodon.social\/@dgoldsmith\/110726324252045480\">Debbie Goldsmith<\/a>:<\/p>\n<blockquote cite=\"https:\/\/mastodon.social\/@dgoldsmith\/110726324252045480\">\n<p>If you want to construct a <code>Predicate<\/code> dynamically, you need to build it up from <code>PredicateExpression<\/code> pieces rather than use the <code>#Predicate<\/code> macro (similar to building an <code>NSPredicate<\/code> from <code>NSExpression<\/code>). Expand the macro in Xcode and you can see how the pieces are put together.<\/p>\n<\/blockquote>\n\n<p>It&rsquo;s a lot more complicated than <code>NSPredicate<\/code> due to the static typing, and there&rsquo;s no way to convert between the two types if you&rsquo;re using both Core Data and SwiftData.<\/p>\n\n<p><a href=\"https:\/\/fatbobman.com\/en\/posts\/how-to-dynamically-construct-complex-predicates-for-swiftdata\/\">Fatbobman<\/a>:<\/p>\n<blockquote cite=\"https:\/\/fatbobman.com\/en\/posts\/how-to-dynamically-construct-complex-predicates-for-swiftdata\/\">\n<p><code>NSCompoundPredicate<\/code> allows developers to combine multiple <code>NSPredicate<\/code> objects into a single compound predicate. This mechanism is particularly suited for scenarios that require data filtering based on multiple criteria. However, in the new Foundation framework restructured with Swift, the direct functionality corresponding to <code>NSCompoundPredicate<\/code> is missing. This change poses a significant challenge for developers who wish to build applications using SwiftData. This article aims to explore how to dynamically construct complex predicates that meet the requirements of SwiftData, utilizing <code>PredicateExpression<\/code>, under the current technical conditions.<\/p>\n<p>[&#8230;]<\/p>\n<p>The issue lies in the <code>expression<\/code> property being of the type <code>any StandardPredicateExpression&lt;Bool&gt;<\/code>, which doesn&rsquo;t contain sufficient information to identify the specific <code>PredicateExpression<\/code> implementation type. Since <code>Conjunction<\/code> requires the exact types of the left and right sub-expressions for initialization, we are unable to use the <code>expression<\/code> property directly to dynamically construct new combined expressions.<\/p>\n<p>[&#8230;]<\/p>\n<p>Although we cannot directly utilize the <code>expression<\/code> attribute of Swift Predicate, there are still alternative ways to achieve the goal of dynamically constructing predicates. The key lies in understanding how to extract or independently create expressions from existing predicates and utilize expression builders such as <code>build_Conjunction<\/code> or <code>build_Disjunction<\/code> to generate new predicate expressions.<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/forums.swift.org\/t\/pitch-swift-predicates\/62000\/50\">Jeremy Schonfeld<\/a>:<\/p>\n<blockquote cite=\"https:\/\/forums.swift.org\/t\/pitch-swift-predicates\/62000\/50\">\n<p>If you need to dynamically create a predicate while analyzing what should go into the predicate, you can do so by manually constructing the expression tree. Unfortunately, since this is a more advanced use case you wouldn&rsquo;t be able to use the macro to help here, but you could write something along the lines of the following[&#8230;]<\/p>\n<p>[&#8230;]<\/p>\n<p>In short, we create a list of the conditions that need to be met and build up the list based on which parameters are specified to the <code>makePredicate<\/code> function. We can then <code>reduce<\/code> this array into a single tree of conjunctions to ensure that all of the conditions are met. There are a few small hoops to jump through here in order to satisfy the type-checker with the use of generics such as the <code>closure<\/code> and separate <code>buildConjunction<\/code> function, but this allows you to just append to <code>conditions<\/code> for each new property rather than needing to work with a combinatorial explosion of conditions using the macro.<\/p>\n<\/blockquote>\n<p><a href=\"https:\/\/github.com\/NoahKamara\/CompoundPredicate\">Noah Kamara<\/a>:<\/p>\n<blockquote cite=\"https:\/\/github.com\/NoahKamara\/CompoundPredicate\">\n<p>CompoundPredicate aims to improve the <code>Predicate<\/code> system to enable combining multiple predicates after constructing them[&#8230;]<\/p>\n<\/blockquote>\n\n<p>This looks like a huge improvement. It&rsquo;s not clear to me whether there are still limitations compared with <code>NSPredicate<\/code>.<\/p>\n\n<p><a href=\"https:\/\/fatbobman.com\/en\/posts\/how-to-dynamically-construct-complex-predicates-for-swiftdata\/\">Fatbobman<\/a>:<\/p>\n<blockquote cite=\"https:\/\/fatbobman.com\/en\/posts\/how-to-dynamically-construct-complex-predicates-for-swiftdata\/\">\n<p>This new strategy abandons the previous reliance on a custom <code>StandardPredicateExpression<\/code> implementation, opting instead for a type-casting strategy that effectively concretizes the information of <code>expression<\/code>. This improvement means developers can avoid the cumbersome process of manually extracting and combining expressions.<\/p>\n<p>[&#8230;]<\/p>\n<p>This method enables the automatic acquisition of the exact type of expressions inside the <code>Predicate<\/code> during the predicate combination process, facilitating an automated and efficient combination of predicates.<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/mastodon.social\/@helge\/111777268297457751\">Helge He&szlig;<\/a>:<\/p>\n<blockquote cite=\"https:\/\/mastodon.social\/@helge\/111777268297457751\"><p>You know how Foundation (in part to support SwiftData) now has the <code>Predicate<\/code> macro? Well, RealityKit has its own generic <code>QueryPredicate<\/code>  And guess what, they don&rsquo;t need a macro to build them, looks like overloading the operators <code>||<\/code> and <code>&amp;&amp;<\/code> is fine there.<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/mastodon.social\/@dgoldsmith\/111655164123820836\">Debbie Goldsmith<\/a>:<\/p>\n<blockquote cite=\"https:\/\/mastodon.social\/@dgoldsmith\/111655164123820836\">\n<p>Operator overloads not only cause longer build times for <code>Predicate<\/code>, but for <em>other<\/em> uses of that operator.<\/p>\n<\/blockquote>\n\n<p>But even <a href=\"https:\/\/fatbobman.com\/en\/posts\/how-to-dynamically-construct-complex-predicates-for-swiftdata\/\">with macros<\/a>:<\/p>\n<blockquote cite=\"https:\/\/fatbobman.com\/en\/posts\/how-to-dynamically-construct-complex-predicates-for-swiftdata\/\">\n<p>In this example, even minor code changes can cause the compilation time for this file to exceed 10 seconds. This delay can also occur when generating expressions using closures.<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/mastodon.social\/@andyfinnell\/111664499832747430\">Andy Finnell<\/a>:<\/p>\n<blockquote cite=\"https:\/\/mastodon.social\/@andyfinnell\/111664499832747430\">\n<p>Using generics to create SwiftData <code>Predicate<\/code>s leads to crashy times.<\/p>\n<p>[&#8230;]<\/p>\n<p>FYI, I solved this by writing another macro.<\/p>\n<p>This is my new mantra: code not working? You don&rsquo;t have enough macros.<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/forums.swift.org\/t\/predicate-to-filter-against-array-of-items\/70986\/2\">\nJeremy Schonfeld<\/a>:<\/p>\n<blockquote cite=\"https:\/\/forums.swift.org\/t\/predicate-to-filter-against-array-of-items\/70986\/2\">\n<p>Since <code>Predicate<\/code> is both <code>Codable<\/code> and <code>Sendable<\/code>, it requires that everything the predicate captures (i.e. all instances captured by the closure) are also <code>Codable<\/code> and <code>Sendable<\/code>.<\/p>\n<\/blockquote>\n\n<p>Previously:<\/p>\n<ul>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2023\/08\/03\/building-testing-and-scaling-with-swiftui\/\">Building, Testing, and Scaling With SwiftUI<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2023\/06\/12\/swiftdata\/\">SwiftData<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2023\/05\/12\/nspredicate-core-data-and-null\/\">NSPredicate, Core Data, and NULL<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2022\/12\/13\/swift-pitch-predicates\/\">Swift Pitch: Predicates<\/a><\/li>\n<\/ul>","protected":false},"excerpt":{"rendered":"<p>Helge He&szlig;: The new Foundation\/#Swiftlang Predicates (and its expressions) seem a little weird because they can&rsquo;t be constructed dynamically? Fly0strich: However, when I try to use that method inside of a #Predicate closure, it gives an error saying that the method is not supported by this predicate. Debbie Goldsmith: If you want to construct a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"apple_news_api_created_at":"2024-05-28T18:03:42Z","apple_news_api_id":"118f6ec1-79a1-4290-8e41-682fa6138b73","apple_news_api_modified_at":"2025-12-16T04:37:30Z","apple_news_api_revision":"AAAAAAAAAAAAAAAAAAAAAQ==","apple_news_api_share_url":"https:\/\/apple.news\/AEY9uwXmhQpCOQWgvphOLcw","apple_news_coverimage":0,"apple_news_coverimage_caption":"","apple_news_is_hidden":false,"apple_news_is_paid":false,"apple_news_is_preview":false,"apple_news_is_sponsored":false,"apple_news_maturity_rating":"","apple_news_metadata":"\"\"","apple_news_pullquote":"","apple_news_pullquote_position":"","apple_news_slug":"","apple_news_sections":"\"\"","apple_news_suppress_video_url":false,"apple_news_use_image_component":false,"footnotes":""},"categories":[4],"tags":[31,2321,46,30,2385,941,74,71,1774,2200,901,2404],"class_list":["post-43414","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-ios","tag-ios-17","tag-languagedesign","tag-mac","tag-macos-14-sonoma","tag-macros","tag-opensource","tag-programming","tag-swift-codable","tag-swift-concurrency","tag-swift-programming-language","tag-swiftdata"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/43414","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/comments?post=43414"}],"version-history":[{"count":4,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/43414\/revisions"}],"predecessor-version":[{"id":50452,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/43414\/revisions\/50452"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=43414"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=43414"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=43414"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}