What does bridging cost, and where does it show up in a profile?
Predict first
What does this print?
print(("👍" as NSString).length, "👍".count)
- 1 1
- 2 1
- 2 2
Mental model. Each trip through the adapter is cheap, but doing it ten thousand times per frame adds up.
Many conversions are O(1) wrappers, but not all. A Swift String that came from an NSString may keep the Objective-C storage and use slower UTF-16 paths. An Array of class elements can stay backed by an NSArray, converting elements lazily on access, which is why ContiguousArray exists for hot loops. And a conditional cast of a collection, as? [String: Int], checks every element, so it is O(n) every time you do it.
Habits that avoid the cost: convert once at the boundary and keep the Swift type inside; prefer APIs with lightweight generics; avoid repeated as? inside loops; and remember NSString.length counts UTF-16 units, so ("👍" as NSString).length is 2 while "👍".count is 1. In Instruments, bridging shows up as retain/release traffic and _bridgeToObjectiveC calls in a hot path.
NSString measures UTF-16 code units (the emoji needs two). Swift’s count measures characters (one).
Common traps
- Re-casting the same
NSDictionaryin every call. Cast once and pass the typed value. - Mixing
NSString.lengthandString.count. They count different units.
They might follow up with
- When is
ContiguousArrayuseful? When class elements are on a hot path and you want native storage with no bridging check. - Does bridging copy memory? Sometimes: mutable ObjC collections are copied into Swift values.
Related questions
- 2.1.4 How does an Array grow, and when do reserveCapacity and ContiguousArray help?
- 2.8.2 Why do ranges break when you move between String, AttributedString and NSAttributedString?
- 2.9.1 What is toll-free bridging, and how do you convert between Swift and Foundation types?
← Why does NSNumber confuse Bool, Int and Double, and how do you read… Which Foundation types are value types in Swift, which are classes… →