Static types describe what the compiler knows at a particular point. A value's runtime type can be more specific, especially through class inheritance or an Any container. Type checking and casting let you inspect that specificity, but they should be used at clear boundaries rather than replace a well-typed model throughout the application.
Review the optionals lesson before using conditional casts. This guide focuses on safe narrowing of uncertain values. Casting does not generally parse text, round a number or validate a domain rule. Those are separate operations with separate failure policies.
Check a type or request a narrower view
Is checks whether a value has a compatible runtime type and returns Bool. As? attempts a conditional cast and returns an optional result. As! performs a forced cast and can trap when the runtime type is incompatible. Prefer conditional casting when the boundary legitimately accepts several possible shapes.
An upcast from a subclass to its superclass does not create a new object. It gives a less-specific view of the same instance. A successful downcast also does not transform the instance into another object; it allows access to the more-specific interface that the instance already supports.
Do not confuse a cast with conversion. A String containing digits does not become an Int merely by using as?. Use Int(text) to parse it. Numeric conversion also has its own representability and truncation rules. An attempted cast answers a type-compatibility question, not whether the value could be interpreted usefully under a different domain meaning.
Any stores heterogeneous values
Any can represent values of many types, including value types, class instances and function values. AnyObject concerns class instances, though platform bridging can add subtleties. An Any container does not erase the underlying runtime value; it removes useful static knowledge from the current interface.
A heterogeneous array can be appropriate at a boundary or in a teaching demonstration. For a stable business model, an enum with associated values often expresses the allowed alternatives more clearly. A switch over such an enum can be exhaustive, whereas Any leaves open every imaginable runtime type.
Optional values inside Any require care. An absent optional is still an optional value when explicitly boxed as Any, rather than automatically becoming a missing array element. Avoid ambiguous boxing and document whether a boundary permits absence. Normalize the external representation into a typed optional or domain result promptly.
Pattern matching can narrow and validate together
A switch can combine type patterns with conditions, such as an Int case followed by a where range check. Keep each branch's meaning specific and provide a fallback for unknown types. Unknown input should not silently become a believable default unless that fallback is an explicit product requirement.
Foundation bridging can make casts involving NSNumber, NSString and Swift values behave differently from a simple pure-Swift example. Boolean and numeric bridging deserve special attention when consuming untyped JSON or Objective-C APIs. Prefer Codable or a library's typed interface when possible instead of guessing that every bridged number is a valid business integer.
At the boundary, return a typed model with validated invariants. Downstream code should not need to repeat the same cast in every function. This keeps uncertainty visible in one place and turns the rest of the program back into ordinary compile-time checked Swift.
For an untyped dictionary, distinguish a missing key from a present value with the wrong runtime type. Both may make a convenient cast return no usable result, but a form or API can need different diagnostics. Decode through a typed transport model when possible, or report those categories explicitly in the adapter. Do not interpret every failed cast as evidence that the entire record was absent.
Official references
Read the Swift type-casting guide and its official source. Audit casts by asking what proves the type, what legitimate alternatives exist, and where domain validation occurs. A clean boundary should expose typed results instead of asking every consumer to rediscover the same runtime assumptions.
Swift Type Casting, Any and AnyObject Examples
Each example shows the code, the output it produces and what each part does.
Class downcasting without forced assumptions
The array's element type is Item, but the first instance is a Book. The conditional cast preserves a normal alternative for the second item. If many subclasses require repeated branching everywhere, consider a polymorphic method or protocol instead of scattering downcast logic across the application.
class Item {
let name: String
init(name: String) { self.name = name }
}
final class Book: Item {
let pages: Int
init(name: String, pages: Int) {
self.pages = pages
super.init(name: name)
}
}
let items: [Item] = [Book(name: "Guide", pages: 120), Item(name: "Pen")]
for item in items {
if let book = item as? Book {
print("\(book.name):\(book.pages)")
} else {
print("\(item.name):not a book")
}
}Guide:120
Pen:not a bookSwift Type Casting, Any and AnyObject in Real Projects
Worked exercise: Accept a limited quantity representation
This pure-Swift adapter accepts an Int or a decimal String and requires a value from one through fifty. It deliberately rejects Double rather than silently rounding it. The source is not a Foundation JSON dictionary, so no NSNumber bridging contract is implied.
func quantity(from raw: Any) -> Int? {
let number: Int?
switch raw {
case let value as Int:
number = value
case let text as String:
number = Int(text)
default:
return nil
}
guard let number, (1...50).contains(number) else { return nil }
return number
}
print(quantity(from: 12) ?? -1)
print(quantity(from: "4") ?? -1)
print(quantity(from: 4.5) == nil)
print(quantity(from: "999") == nil)12
4
true
trueThe first branch recognizes a runtime type, the second parses a representation, and the final guard enforces the domain range. Test unknown types and integer overflow strings too. A typed request model can eliminate the Any boundary entirely when you control the input protocol.
Swift Type Casting, Any and AnyObject FAQ
When is as! reasonable?
Only when a stronger invariant already guarantees the runtime type and a failure should reveal a programming defect. Even then, prefer designs that encode the invariant in the static type so the cast is unnecessary. For input from users, servers or heterogeneous containers, conditional casting normally represents legitimate failure better. Repeated forced casts often indicate that uncertainty has spread too far through the model instead of being normalized at one boundary.
Does casting a superclass reference create a subclass?
No. A successful downcast reveals that the referenced object already has a compatible more-specific runtime type. It does not add subclass fields, run a subclass initializer or allocate a replacement instance. If the object is merely the superclass, the conditional cast fails. To create another representation, use an explicit initializer or mapping function that states how values are converted rather than expecting casting syntax to construct new behavior.
Why not use Any for every property?
Any removes useful compile-time guarantees and forces later code to inspect runtime types repeatedly. It can be necessary at a dynamic boundary, but a stable domain usually has known alternatives that a struct, protocol or enum can express. Typed models make invalid states harder to construct and give callers clearer contracts. Keep Any near the source of uncertainty, then validate and convert it into a type that supports the application's actual operations.
Can an Int cast parse a numeric string?
No. A string remains text even when its characters look numeric. Use Int(text), which returns an optional result because parsing or representability can fail. After parsing, check the domain's accepted range. This separates runtime type recognition, textual conversion and business validation. A cast that returns nil for "12" is behaving correctly: the original value was not an Int, regardless of how easily a separate parser can interpret its contents.
Swift Type Casting, Any and AnyObject Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
A stable field has exactly three supported alternatives. Which model can make handling exhaustive?
Swift Type Casting, Any and AnyObject Quiz
2 questions to check what you learned.
What does as? return when a runtime cast is incompatible?
- A rounded numeric value.
- A silently constructed object of the requested type.
- nil.
Which operation parses decimal text into an integer?
- Int(text).
- text as? Int.
- text is Int.