What are NSNotFound and the other sentinel values, and how do they show up in Swift?
Predict first
What does this print?
let ns = "hello world" as NSString
let r = ns.range(of: "xyz")
print(r.location == NSNotFound, NSNotFound == Int.max)
print("hello world".range(of: "xyz") as Any)
print((["a", "b"] as NSArray).index(of: "z") == NSNotFound, ["a", "b"].firstIndex(of: "z") as Any)
- false false / nil / false nil
- true true / nil / true nil
- true true / Optional(0..<0) / true nil
Mental model. NSNotFound is a parking space numbered 9 223 372 036 854 775 807 that is always empty: if your ticket says that, you were not given a space.
Foundation predates optionals, so “not found” was written as a magic number. NSNotFound is NSIntegerMax, which is Int.max on 64-bit. APIs such as NSString.range(of:) return an NSRange whose location is NSNotFound, and NSArray.index(of:) returns it directly. A lookup that is not annotated for Swift hands you the sentinel, and if you use it as an index you read far out of bounds or trap.
Swift-native counterparts return optionals: String.range(of:) gives Range<String.Index>? and Array.firstIndex(of:) gives Int?. Prefer those, and when you must call the Objective-C form compare to NSNotFound before using the value. The same family includes NSNull (a real object standing for “no value” in collections and JSON, not nil), nil/Nil/NULL for object, class and C pointers, and NSOrderedSame-style comparison results. A nil message receiver returns 0 or nil, which is why a missed lookup in Objective-C can silently carry on.
The Foundation call returns the sentinel, and the Swift-native calls return nil. On a 64-bit platform the sentinel equals Int.max.
Common traps
- Treating the sentinel as an index.
array.object(at: idx)withNSNotFoundthrows an out-of-range exception. - Writing
if idx > 0to mean found. The sentinel is huge and positive, so the test passes.
They might follow up with
- How does an empty
NSRangefrom a failed search print? As{9223372036854775807, 0}on 64-bit. - Why does JSON
nullarrive asNSNull? A collection cannot holdnil, so the object stands in for it.
Related questions
- 2.9.2 Why does NSNumber confuse Bool, Int and Double, and how do you read untyped values safely?
- 2.9.1 What is toll-free bridging, and how do you convert between Swift and Foundation types?
← What does atomic actually guarantee for an Objective-C property, and… How do pointer and out-parameter conventions such as BOOL *stop… →