Swift Bridging Types: String ↔︎ NSString and Value Bridging
Swift bridging lets values move between Swift-native types and Foundation types without manual conversion in many cases. This matters whenever your Swift code touches APIs that expect Foundation objects, because bridging determines how data is passed, copied, stored, and compared.
Quick answer: Swift can automatically bridge many common types such as String and NSString. In most day-to-day code, you can treat them as compatible, but bridging is not the same as identity, and some types bridge in one direction more naturally than the other.
Difficulty: Intermediate
You'll understand this better if you know: basic Swift types, value versus reference semantics, and the role of Foundation in Swift.
1. What Is Bridging Types?
Bridging is the process Swift uses to convert between a Swift type and a Foundation or Objective-C-compatible type so they can work together. For example, a Swift String can often be used where an NSString is expected, and an NSString can usually be treated as a Swift String.
- It makes Swift and Foundation APIs easier to use together.
- It happens automatically in many common cases.
- It affects strings, numbers, arrays, dictionaries, sets, and dates.
- It is about interoperability, not about changing the underlying meaning of the value.
For strings, bridging is the most visible example. A Swift String is a native value type, while NSString is a Foundation reference type. Swift can move between them when needed, but the two are still conceptually different.
2. Why Bridging Matters
Bridging matters because many existing APIs in Apple platforms use Foundation types, while modern Swift code prefers Swift-native types. If you ignore bridging, you may be confused by type mismatches, unnecessary copies, or comparisons that do not behave the way you expect.
It is especially important when you:
- call older APIs that still use Foundation objects
- pass Swift values into collections that store Objective-C-compatible objects
- receive values from Objective-C or Cocoa APIs
- debug unexpected behavior around equality, casting, or type identity
Bridging helps you write mostly Swift code without needing to manually convert every value at the boundary.
3. Basic Syntax or Core Idea
Bridging often feels invisible because Swift performs it automatically. Still, it helps to know the explicit conversions that may appear in code.
String to NSString
A Swift string can be assigned to an NSString variable. Swift will bridge the value for you.
import Foundation
let swiftText = "Hello, Swift"
let foundationText: NSString = swiftTextThe variable foundationText receives a bridged Foundation string, even though the source value started as a Swift String.
NSString to String
You can also move in the opposite direction.
import Foundation
let foundationText: NSString = "Hello, Foundation"
let swiftText: String = foundationTextSwift converts the Foundation object into a native string value for you.
What bridging is doing
Bridging is not the same as simple text formatting. It is a compatibility layer that lets types from two ecosystems interoperate while preserving their expected behavior as much as possible.
4. Step-by-Step Examples
Example 1: Passing a Swift string to a Foundation API
Many Foundation APIs accept NSString or String-compatible parameters. Here, Swift bridges the argument automatically.
import Foundation
let message = "Bridge me"
let nsMessage = message as NSString
print(nsMessage)This example makes the conversion explicit with as NSString, which can be useful when you want to emphasize the Foundation representation.
Example 2: Using a bridged value in a collection
Swift collections can bridge to Foundation collections when the elements are bridgeable. This matters when values cross API boundaries.
import Foundation
let names = ["Ava", "Noah", "Mia"]
let foundationArray = names as NSArray
print(foundationArray)The array becomes a Foundation array because each element can be bridged.
Example 3: Receiving a Foundation string and using it as Swift String
Bridging is also common when values come back from a Foundation API.
import Foundation
let rawValue: NSString = "Welcome"
let greeting = rawValue as String
print(greeting.uppercased())Once bridged into a Swift string, you can use Swift string APIs naturally.
Example 4: Optional bridging
Optional values often appear at bridging boundaries. Swift can bridge an optional Foundation string when it is non-nil.
import Foundation
let source: NSString? = "Optional text"
if let text = source {
let swiftText = text as String
print(swiftText)
}This shows that bridging still respects optional handling. You must unwrap the optional before using it as a non-optional value.
5. Practical Use Cases
- Calling Foundation APIs that expect NSString, NSArray, or NSDictionary.
- Reading data from older Cocoa APIs and using it in Swift-native code.
- Passing Swift values into system frameworks that were designed before Swift.
- Converting model data at the boundary between Swift and Objective-C-compatible code.
- Working with Cocoa text, file paths, URLs, and user defaults values that may be represented with Foundation types.
In practice, bridging saves you from writing repeated conversion code while still allowing Swift to keep its own type system.
6. Common Mistakes
Mistake 1: Assuming bridged values are the same type
A bridged value may behave compatibly, but that does not mean the types are identical. This matters when you compare types, inspect dynamic types, or rely on reference identity.
Problem: The code treats a Swift String and an NSString as if they were literally the same type, which can lead to confusion when debugging or performing type checks.
import Foundation
let swiftText = "Hello"
let foundationText: NSString = swiftText
print(type(of: swiftText))
print(type(of: foundationText))Fix: Treat bridging as interoperability, not type equality. Check the type you actually need at the boundary.
import Foundation
let swiftText: String = "Hello"
let foundationText: NSString = swiftText as NSString
if foundationText is NSString {
print("Foundation-compatible")
}The corrected version works because it uses the proper type at each boundary and avoids assuming the bridged forms are identical in every sense.
Mistake 2: Forgetting that optional values still need unwrapping
Bridging does not remove optional safety. A missing value is still missing, and you must handle it explicitly.
Problem: This code tries to bridge an optional value without checking whether it is nil, which can trigger a compile-time error when a non-optional result is expected.
import Foundation
let rawName: NSString? = nil
let name: String = rawName as StringFix: Unwrap the optional before bridging, or provide a default value.
import Foundation
let rawName: NSString? = nil
let name = rawName?.isEqual("") == true ? "" : (rawName ?? "Guest")A more direct approach is to use optional binding when you need a real value.
import Foundation
let rawName: NSString? = nil
if let name = rawName {
let swiftName = name as String
print(swiftName)
}The fixed version works because it handles the optional before using the bridged value.
Mistake 3: Expecting every Swift collection to bridge cleanly
Many Swift collections bridge to Foundation collections only when their contents are bridgeable. If an element cannot bridge, the conversion may fail or require a different design.
Problem: The code assumes any Swift array can become an NSArray without considering whether every element is compatible with Foundation bridging.
import Foundation
struct Token {
let value: String
}
let tokens = [Token(value: "A")]
let foundationTokens = tokens as NSArrayFix: Convert to a Foundation-friendly representation first, such as strings or dictionaries of bridgeable values.
import Foundation
struct Token {
let value: String
}
let tokens = [Token(value: "A")]
let foundationTokens = tokens.map { $0.value } as NSArrayThe corrected version works because the array now contains values Foundation can bridge directly.
7. Best Practices
Prefer Swift types inside your own code
Use Swift-native types like String unless you are at a Foundation boundary. This keeps your code more expressive and reduces accidental dependence on Objective-C-style APIs.
let username: String = "maria"This is easier to read and keeps your app code aligned with Swift conventions.
Bridge as late as possible and as early as needed
Keep Swift values in Swift form for as long as you can, then bridge only when a Foundation API requires it. This reduces type noise and makes your data flow easier to follow.
import Foundation
let message: String = "Ready to save"
let storedMessage = message as NSStringDelaying bridging makes the code easier to test and less dependent on Foundation types.
Use explicit conversions at API boundaries
Even when Swift can bridge implicitly, an explicit conversion can make your intent clearer in code reviews and debugging sessions.
import Foundation
func logLegacyMessage(_ message: NSString) {
print(message)
}
let text = "Explicit is clearer"
logLegacyMessage(text as NSString)The explicit cast shows the reader exactly where the bridging happens.
8. Limitations and Edge Cases
- Not every Swift type bridges to Foundation. Custom structs and enums usually do not bridge automatically.
- Bridging works best for Foundation-compatible types such as strings, numbers, dates, arrays, dictionaries, and sets.
- Some conversions are one-way in practice because the target API expects a specific representation.
- Bridging can hide copies and temporary wrappers, so it is not always free.
- Type identity is not the same as value compatibility. A bridged value may compare equal while still being a different runtime representation.
- When bridging collections, every element must also be bridgeable.
One common surprise is that a value can be interchangeable at the API level but still not behave like the exact same type in debugging tools or type checks.
9. Practical Mini Project
Let’s build a tiny text formatter that accepts Swift strings internally, then prepares a Foundation-compatible value for a legacy-style boundary.
import Foundation
struct MessageFormatter {
func makeDisplayMessage(name: String) -> String {
return "Hello, \(name)!"
}
func makeLegacyMessage(name: String) -> NSString {
let message = makeDisplayMessage(name: name)
return message as NSString
}
}
let formatter = MessageFormatter()
let swiftMessage = formatter.makeDisplayMessage(name: "Taylor")
let foundationMessage = formatter.makeLegacyMessage(name: "Taylor")
print(swiftMessage)
print(foundationMessage)This mini project keeps the formatting logic in Swift String, then bridges only when a Foundation result is needed. That pattern is easy to maintain and scales well when you later replace the legacy boundary with a pure-Swift one.
10. Key Points
- Bridging lets Swift and Foundation types work together without constant manual conversion.
- String and NSString are compatible, but they are still different kinds of types.
- Swift handles many bridging conversions automatically, especially at API boundaries.
- Optional values, collections, and custom types need extra attention.
- Prefer Swift-native types internally and bridge only when necessary.
11. Practice Exercise
Build a small function that takes a Swift String, converts it to NSString, and returns a formatted message.
- Create a function named formatForLegacyAPI.
- Accept one parameter named input of type String.
- Return an NSString that includes the original text inside square brackets.
- Print the result for the input "Bridge".
Expected output: [Bridge]
Hint: Build the formatted string first, then bridge the final value to NSString.
import Foundation
func formatForLegacyAPI(input: String) -> NSString {
let formatted = "[\(input)]"
return formatted as NSString
}
let result = formatForLegacyAPI(input: "Bridge")
print(result)12. Final Summary
Swift bridging types are the compatibility layer that makes Swift and Foundation feel like one ecosystem. For strings, the most common example is String and NSString, but the same idea applies to several other Foundation-friendly types. Bridging often happens automatically, which makes legacy APIs much easier to use from Swift.
The key idea is to understand where bridging helps and where it can mislead you. Use Swift-native types inside your own code, bridge at the boundary, and remember that compatibility is not the same as type identity. If you keep that model in mind, you will avoid most of the confusion around interoperability.
Next, it is worth learning how bridging works for collections such as Array and Dictionary, since those patterns build directly on the same ideas.