Highlight
Foundation turns
Localefrom a reference type into a value type, and the#Predicatemacro moves property-name and type checks for predicate queries to compile time.
Core content
The main thread of this Foundation update is paying down design debt left over from the Objective-C era. Turning Locale from a reference type into a value type sounds like an internal detail, but it kills off a whole class of multithreading bugs: two threads read different locale values the instant the user switches the system language, and formatting results disagree. These bugs are nearly impossible to reproduce; in the past you could only add locks or pray. Now Locale.current returns a snapshot at the moment of the call, and the value in your hands will not change.
The rewrite under JSONEncoder and JSONDecoder brings a performance boost, a free win for apps that handle a lot of API responses. Another practical change is OutputFormatting.withoutEscapingSlashes, which finally lets you control how / is escaped — some backends have always tripped over \/.
The #Predicate macro is the headline. NSPredicate writes "age >= 18" as a string, and you only learn about a typo at runtime; #Predicate lets the Swift compiler check types and property names at build time. Combined with the chainable SortDescriptor API, Core Data queries can finally be type-safe end to end.
Details
#Predicate turns string queries into Swift closures, with the compiler in charge of types. Here is a typical use:
struct Person {
var name: String
var age: Int
}
let adults = #Predicate<Person> { person in
person.age >= 18
}
let sortByName = SortDescriptor(\Person.name)
Key points:
#Predicate<Person>binds the predicate to thePersontype, and the compiler checks every field access on the closure parameterperson.person.age >= 18is just a Swift expression; misspell the property name and Xcode flags it at once, with no runtime string parsing to worry about.SortDescriptor(\Person.name)picks the sort field with a KeyPath, sidestepping the old typo-prone string keys.- You can pass the predicate to a
FetchDescriptorin SwiftData or Core Data for end-to-end type safety.
JSONEncoder output options can be combined with outputFormatting:
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .withoutEscapingSlashes]
Key points:
.prettyPrintedadds indentation to make output easier to debug..withoutEscapingSlashesturns off escaping of/, so you gethttp://example.cominstead ofhttp:\/\/example.com.- The two options stack with
OptionSetsyntax and do not conflict.
Takeaways
-
What to do: audit every
Locale.currentcall in your project.- Why it pays off: locale drift across threads causes formatting glitches that are nearly impossible to reproduce, a classic source of production issues.
- How to start: use Xcode’s Find Navigator to search for
Locale.currentacross the project, and pay extra attention to uses inside GCD queues andasynccontexts.
-
What to do: migrate
NSPredicatestrings in new code to the#Predicatemacro.- Why it pays off: compile-time checks catch most typos and type mismatches, cutting down on production crashes.
- How to start: begin with new query code, and migrate the rest opportunistically as you touch related modules.
-
What to do: rerun your JSON parsing performance baseline after bumping the deployment target.
- Why it pays off: the rewrites under
JSONEncoderandJSONDecoderneed no code changes, so the gain is free. - How to start: use XCTest’s
measureblock to benchmark large JSON payloads, and compare timings before and after the upgrade.
- Why it pays off: the rewrites under
Related sessions
- What’s new in Swift — a roundup of Swift language updates that pairs well with Foundation.
- Embracing Swift concurrency — best practices for the value-type
Localein concurrent code. - SwiftData: Dive into inheritance and schema migration — the full picture of
#Predicatein SwiftData queries. - Improve memory usage and performance with Swift — a Swift performance angle for understanding the trade-offs behind Foundation’s improvements.
Comments
GitHub Issues · utterances