Swift: برنامج Swift التعليمي للتسلسل الاختياري وتحويل النوع

Optional chaining lets you access optional values layer by layer like peeling an onion -- if any layer is nil, the entire chain gracefully returns nil instead of crashing.

1. What You'll Learn


2. A Full-Stack Developer's Real Story

(1) Pain Point: Painful Layer-by-Layer JSON Unwrapping

Charlie was building a weather app and needed to extract the city temperature from an API's JSON response. Parsing without optional chaining required 5 layers of nested if-let:

SWIFT
if let data = json["data"] as? [String: Any] {
    if let city = data["city"] as? [String: Any] {
        if let weather = city["weather"] as? [String: Any] {
            if let temperature = weather["temperature"] as? [String: Any] {
                if let current = temperature["current"] as? Double {
                    print("Temperature: \(current)")
                }
            }
        }
    }
}

5 layers of nested if-let, an indentation disaster! Every layer requires manual unwrapping, and failure at any layer renders the whole block useless.

(2) The Optional Chaining Solution

SWIFT
// Simplified version using optional chaining + optional binding
if let data = json["data"] as? [String: Any],
   let city = data["city"] as? [String: Any],
   let weather = city["weather"] as? [String: Any],
   let temperature = weather["temperature"] as? [String: Any],
   let current = temperature["current"] as? Double {
    print("Temperature: \(current)")
}

Better approach: First model the JSON structure with Codable, then use optional chaining for scattered access to non-critical paths.

(3) Benefits: From 10 Lines of Indentation to 2 Lines

Dimension if-let Nesting Optional Chaining + Codable
Lines of code 10 lines 2-3 lines
Indentation depth 5 levels 0-1 level
nil handling Manual check per layer nil automatically propagates
Readability Pyramid code Chained calls

3. Optional Chaining

Optional chaining adds ? after an optional value. When the value is nil, subsequent operations are skipped and nil is returned.

(1) Basic Syntax

100%
graph LR
    A["person?.name"] --> B["person != nil"]
    A --> C["person == nil"]
    B --> D["Returns the value of name"]
    C --> E["Returns nil (no crash)"]
Syntax Meaning Return Type
person?.name Safe property access String?
person?.sayHello() Safe method call Void?
person?.info?["age"] Chained subscript access Any??
array?.first?.name Multi-level optional chain String?

▶ Example: Accessing Properties and Methods with Optional Chaining

SWIFT
// ============================================
// Basic usage of optional chaining
// ============================================
class Address {
    let city: String
    let street: String?
    init(city: String, street: String?) {
        self.city = city
        self.street = street
    }
}
class Person {
    let name: String
    var address: Address?
    init(name: String, address: Address?) {
        self.name = name
        self.address = address
    }
    func greeting() -> String { "Hello, I'm \(name)" }
}
let alice = Person(name: "Alice", address: Address(city: "Tokyo", street: nil))
let bob = Person(name: "Bob", address: nil)
print(alice.address?.city ?? "Unknown city")
print(bob.address?.city ?? "Unknown city")
print(bob.address?.street ?? "No street info")
print(bob.greeting())

Output:

TEXT 📖 للعرض فقط
Tokyo
Unknown city
No street info
Hello, I'm Bob

(2) Multi-Level Optional Chains

If any level is nil, the entire expression returns nil.

▶ Example: Simulating Multi-Level JSON Data Access

SWIFT
// ============================================
// Simulating multi-level nested optional access like JSON
// ============================================
struct Team {
    let name: String
    let leader: Person?
}
struct Company {
    let name: String
    let team: Team?
}
let company = Company(
    name: "TechCorp",
    team: Team(
        name: "iOS Team",
        leader: Person(name: "Charlie", address: nil)
    )
)
let leaderCity = company.team?.leader?.address?.city ?? "None"
print("Leader city: \(leaderCity)")
let noTeamCompany = Company(name: "Startup", team: nil)
let result = noTeamCompany.team?.leader?.address?.city ?? "None"
print("No team company: \(result)")

Output:

TEXT 📖 للعرض فقط
Leader city: None
No team company: None

4. Type Casting

Swift is a strongly typed language, but type casting lets you safely cast up and down the type hierarchy.

(1) The is Operator: Type Checking

is returns a Bool, checking whether an instance belongs to a certain type.

(2) as? and as!: Downcasting

100%
graph TB
    A["Any / Superclass type"] --> B["as? safe cast"]
    A --> C["as! forced cast"]
    B --> D["Success -> Optional<T>"]
    B --> E["Failure -> nil"]
    C --> F["Success -> T"]
    C --> G["Failure -> crash"]
Syntax Meaning On Success On Failure
value is String Type check true false
value as? String Safe cast String? nil
value as! String Forced cast String Runtime crash
value as String Upcast String Compile-time check

▶ Example: Type Casting in a Media Library

SWIFT
// ============================================
// Type casting: upcasting and downcasting in a media library
// ============================================
class MediaItem {
    let title: String
    init(title: String) { self.title = title }
}
class Movie: MediaItem {
    let director: String
    init(title: String, director: String) {
        self.director = director
        super.init(title: title)
    }
}
class Song: MediaItem {
    let artist: String
    init(title: String, artist: String) {
        self.artist = artist
        super.init(title: title)
    }
}
let library: [MediaItem] = [
    Movie(title: "Inception", director: "Nolan"),
    Song(title: "Bohemian Rhapsody", artist: "Queen"),
    Movie(title: "Interstellar", director: "Nolan"),
]
var movieCount = 0
var songCount = 0
for item in library {
    if item is Movie { movieCount += 1 }
    else if item is Song { songCount += 1 }
}
print("Movies: \(movieCount), Songs: \(songCount)")
for item in library {
    if let movie = item as? Movie {
        print("Movie: \(movie.title), Dir: \(movie.director)")
    } else if let song = item as? Song {
        print("Song: \(song.title), Artist: \(song.artist)")
    }
}

Output:

TEXT 📖 للعرض فقط
Movies: 2, Songs: 1
Movie: Inception, Dir: Nolan
Song: Bohemian Rhapsody, Artist: Queen
Movie: Interstellar, Dir: Nolan

5. Any/AnyObject and Opaque Types

Swift provides special types to hold arbitrary values, as well as opaque types to hide concrete types.

(1) Any and AnyObject

Type Can Hold Use Case
Any Any type (value/reference/function) Heterogeneous arrays, JSON parsing
AnyObject Any class instance Reference types only, Obj-C interop

(2) The Never Type

Never indicates that a function never returns -- used with fatalError, preconditionFailure, etc.

(3) Opaque Types with some

Opaque types hide the concrete return type, exposing only the protocol constraint. They are "symmetric" to generics.

Approach Caller decides Implementer decides Example
Generics Type func f<T>(...) -> T
Opaque Type func f() -> some Equatable

▶ Example: Any Array and Type Casting

SWIFT
// ============================================
// Any array storing mixed types + safe retrieval
// ============================================
var mixedArray: [Any] = []
mixedArray.append(42)
mixedArray.append("Hello")
mixedArray.append(3.14)
mixedArray.append(true)
mixedArray.append([1, 2, 3])
for (index, item) in mixedArray.enumerated() {
    switch item {
    case let num as Int:
        print("[\(index)] Int: \(num)")
    case let str as String:
        print("[\(index)] String: \(str)")
    case let d as Double:
        print("[\(index)] Double: \(d)")
    case let b as Bool:
        print("[\(index)] Bool: \(b)")
    case let arr as [Int]:
        print("[\(index)] [Int]: \(arr)")
    default:
        print("[\(index)] Unknown")
    }
}

Output:

TEXT 📖 للعرض فقط
[0] Int: 42
[1] String: Hello
[2] Double: 3.14
[3] Bool: true
[4] [Int]: [1, 2, 3]

▶ Example: Opaque Return Types

SWIFT
// ============================================
// Using some to hide the concrete return type
// ============================================
protocol Shape {
    func area() -> Double
}
struct Circle: Shape {
    let radius: Double
    func area() -> Double { .pi * radius * radius }
}
struct Rectangle: Shape {
    let width: Double
    let height: Double
    func area() -> Double { width * height }
}
func makeShape(isCircle: Bool) -> some Shape {
    if isCircle {
        return Circle(radius: 5)
    } else {
        return Rectangle(width: 3, height: 4)
    }
}
let shape1 = makeShape(isCircle: true)
let shape2 = makeShape(isCircle: false)
print("Circle area: \(shape1.area())")
print("Rectangle area: \(shape2.area())")

Output:

TEXT 📖 للعرض فقط
Circle area: 78.53981633974483
Rectangle area: 12.0

6. Full Example: JSON Configuration Parser

SWIFT
// ============================================
// Full example: JSON configuration parser
// Uses optional chaining + type casting + Any
// ============================================
import Foundation
// 1. Simulated JSON data
let json: [String: Any] = [
    "app": [
        "name": "WeatherApp",
        "version": 2.1,
        "settings": [
            "theme": "dark",
            "units": "metric",
            "notifications": true
        ],
        "authors": ["Alice", "Bob"]
    ],
    "debug": nil
]
// 2. Safe parsing function
func parseConfig(_ json: [String: Any]) {
    // Optional chaining + type casting
    guard let app = json["app"] as? [String: Any] else {
        print("Invalid configuration")
        return
    }
    let name = app["name"] as? String ?? "Unknown"
    let version = app["version"] as? Double ?? 0.0
    let theme = (app["settings"] as? [String: Any])?["theme"] as? String ?? "light"
    let notifications = (app["settings"] as? [String: Any])?["notifications"] as? Bool ?? false
    let authors = app["authors"] as? [String] ?? []
    print("App: \(name) v\(version)")
    print("Theme: \(theme)")
    print("Notifications: \(notifications)")
    print("Authors: \(authors.joined(separator: ", "))")
    // is check for nil
    if json["debug"] is NSNull {
        print("Debug: disabled")
    }
}
parseConfig(json)

Output:

TEXT 📖 للعرض فقط
App: WeatherApp v2.1
Theme: dark
Notifications: true
Authors: Alice, Bob
Debug: disabled

❓ FAQ

س How do I choose between optional chaining and forced unwrapping !?
ج Always prefer optional chaining ? or optional binding if-let. Forced unwrapping ! should only be used when you are 100% certain the value is not nil; otherwise, it causes a runtime crash.
س When should I use as? vs. as!?
ج as? is safe -- failure returns nil. as! is dangerous -- failure crashes outright. Never use as! when as? will do. Only use as! when the compiler is certain about the from/to type (e.g., dequeuing a UITableViewCell).
س How do I choose between Any and generics?
ج Prefer generics -- they preserve type information and are checked at compile time. Any is a "last resort" -- use it when you genuinely need to store a heterogeneous collection of arbitrary types, then extract safely with as?.
س What's the difference between opaque types (some) and protocols as return types?
ج some Shape guarantees the function returns the same concrete type, allowing compiler optimization. Using the Shape protocol directly can return different types but has performance overhead. some was introduced in Swift 5.1+ and is closely tied to SwiftUI's View.
س When should I use the Never type?
ج Never marks functions that definitely never return (e.g., fatalError, preconditionFailure). It's primarily used as a control-flow "endpoint" so the compiler knows subsequent code is unreachable.

📖 Summary


📝 Exercises

  1. Basic: Define a User class (with name and an optional property spouse: User?), create a user chain Alice -> Bob -> Charlie, and use optional chaining to print the third person's name.
  2. Intermediate: Create a [Any] array containing one each of Int, String, Double, [String], and [Int: String]. Use switch + as pattern matching to print each value and its type.
  3. Challenge: Implement a safe JSON reading function readValue<T>(from json: [String: Any], keyPath: String) -> T? that supports dot-separated paths like "data.city.name", internally using optional chaining for level-by-level access.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%