How do you compare strings for UI versus for identifiers?
Predict first
What does this print?
let composed = "é"
let decomposed = "e\u{301}"
print(composed == decomposed)
- true
- false
Mental model. For humans, “café” and “cafe” should probably match in a search. For a database key, they must not.
UI / search: use localized, human-facing comparison — localizedStandardCompare, localizedLowercase, formatted(), and .diacriticInsensitive search options. This respects the user's locale and collation.
Identifiers / caches / file names: use canonical equality (== on normalized strings), and avoid locale-dependent transforms — a Turkish locale changes the lowercase of I, which will break your key.
They are different operations with different correctness requirements. Do not reuse the UI comparator for keys.
Swift compares strings by canonical equivalence, so these are equal even though their bytes differ. For identifiers you usually want to normalise first and compare bytes.
Common traps
- Using
lowercased()for identifiers. It is locale-insensitive but still does full Unicode case folding;folding(options: .caseInsensitive, locale: nil)or a stable hash is usually better. - Assuming
==is byte equality. Swift compares canonically equivalent strings as equal — good for users, but it means two different byte sequences can compare equal. - Localizing file names or keys. Locale-dependent transformations make data unreachable when the user changes region.
They might follow up with
- What is
localizedStandardComparefor? Finder-style ordering where “file 2” sorts before “file 10”. - How do you build a stable cache key? Normalize explicitly (e.g. NFC), then hash the UTF-8 bytes — do not use locale-aware transforms.