Swift: امتدادات Swift والتحكم في الوصول

Extensions let you add new functionality to existing classes, structs, enums, or protocols. Access control makes your code interfaces clearer and encapsulation tighter. This lesson combines both to help you write more elegant Swift code.

1. What You'll Learn


2. A Backend Developer's Real Story

(1) Pain Point: Can't Add Functionality to Foundation's String

While working on date formatting, Bob realized that Date had no method to convert to a human-readable "how long ago" format. He wrote a utility function:

SWIFT
func timeAgo(from date: Date) -> String {
    let seconds = Date().timeIntervalSince(date)
    if seconds < 60 { return "\(Int(seconds))s ago" }
    if seconds < 3600 { return "\(Int(seconds / 60))m ago" }
    return "\(Int(seconds / 3600))h ago"
}
// Must pass the date as argument every time
print(timeAgo(from: someDate))

Every call requires passing the argument and invoking a global function -- the code doesn't feel natural. Bob wanted Date to return this format on its own -- just like a built-in method.

(2) The extension Solution

SWIFT
extension Date {
    func timeAgo() -> String {
        let seconds = Date().timeIntervalSince(self)
        if seconds < 60 { return "\(Int(seconds))s ago" }
        if seconds < 3600 { return "\(Int(seconds / 60))m ago" }
        return "\(Int(seconds / 3600))h ago"
    }
}
// Looks like a built-in Date method
print(someDate.timeAgo())

Extensions give Date a new method -- no inheritance, no modifying the source.

(3) Benefits: Natural Syntax + High Cohesion

Dimension Global Function Extension
Call style timeAgo(from: date) date.timeAgo()
Code organization Scattered everywhere Tightly grouped with type
Discoverability Must manually import utility module Xcode autocomplete
Maintainability Global namespace pollution Type-specific namespace

3. Extensions

(1) Extension Syntax

Extensions can add the following to existing types:

Can Add Example Limitation
Computed properties var area: Double { ... } Cannot add stored properties
Instance/type methods func reversed() -> String Allowed
Initializers init?(json: [String: Any]) Cannot add deinit
Subscripts subscript(idx: Int) -> T Allowed
Nested types enum Status { ... } Allowed
Protocol conformance extension Type: Protocol Allowed
100%
graph TB
    A["extension SomeType {"] --> B["Computed Properties"]
    A --> C["Methods"]
    A --> D["Initializers"]
    A --> E["Subscripts"]
    A --> F["Nested Types"]
    A --> G["Protocol Conformance"]
    A --> H["}"]

▶ Example: Adding Extensions to String

SWIFT
// ============================================
// Adding practical extensions to String
// ============================================
extension String {
    // Computed property -- check if this is a valid email
    var isValidEmail: Bool {
        return contains("@") && contains(".")
    }
    // Method -- truncate to first N characters
    func prefix(_ maxLength: Int) -> String {
        guard count > maxLength else { return self }
        return String(self.prefix(maxLength)) + "..."
    }
    // Method -- reverse the string
    func reversed() -> String {
        return String(self.reversed())
    }
}
let email = "alice@example.com"
print("\(email) is valid: \(email.isValidEmail)")
let longText = "This is a very long string that needs truncation"
print(longText.prefix(20))
print("Hello".reversed())

Output:

TEXT 📖 للعرض فقط
alice@example.com is valid: true
This is a very lon...
olleH

Note: Extensions cannot override existing methods. If an extension defines a method with the same name as an existing one, the compiler will report an error.

(2) Conforming to Protocols via Extensions

Separate protocol conformance from type definition by implementing it in an extension:

SWIFT
// ============================================
// Conforming to protocols via extensions
// ============================================
// 1. Define the base type
struct User {
    let id: Int
    let name: String
    let email: String
}
// 2. Conform to Hashable via extension (for Set and Dictionary keys)
extension User: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}
// 3. Conform to Equatable via extension
extension User: Equatable {
    static func == (lhs: User, rhs: User) -> Bool {
        return lhs.id == rhs.id
    }
}
// 4. Usage
let users: Set<User> = [
    User(id: 1, name: "Alice", email: "alice@example.com"),
    User(id: 1, name: "Alice", email: "alice@example.com"),  // Duplicate -- deduplicated
    User(id: 2, name: "Bob", email: "bob@example.com")
]
print("Users count: \(users.count)")  // 2

Output:

TEXT 📖 للعرض فقط
Users count: 2

Tip: Placing protocol conformance in extensions is for code organization -- the type definition contains only core data, and behaviors are added via extensions. This is a common pattern in the Swift standard library.


4. Access Control

(1) Five Access Levels

Swift provides five access control levels, from most open to most closed:

100%
graph TB
    A[Access Control Pyramid] --> B["open - Most open, anyone can access/inherit/override"]
    A --> C["public - Anyone can access, but cannot inherit/override"]
    A --> D["internal - Within the current module (default level)"]
    A --> E["fileprivate - Within the current file"]
    A --> F["private - Within the enclosing declaration's braces"]
Level Keyword Access Scope Usable On
Most open open Any module, can inherit and override Classes, class methods
public Any module, but cannot inherit/override Classes, methods, properties
Default internal Current module (App or Framework) All types
fileprivate Current file Types, methods, properties
Most closed private Current declaration scope (braces) Properties, methods

▶ Example: Access Control in Action

SWIFT
// ============================================
// Using the five access levels
// ============================================
// Public class available to all modules
public class BankAccount {
    // Publicly readable, privately writable
    public private(set) var balance: Double
    // Private field -- only accessible within this class
    private var accountNumber: String
    private var transactionHistory: [String] = []
    // Accessible within this file -- for logging
    fileprivate var lastTransactionDate: Date?
    // Accessible within this module -- for internal auditing
    internal var branchCode: String
    public init(accountNumber: String, balance: Double, branchCode: String) {
        self.accountNumber = accountNumber
        self.balance = balance
        self.branchCode = branchCode
    }
    public func deposit(amount: Double) {
        balance += amount
        transactionHistory.append("Deposit: \(amount)")
        lastTransactionDate = Date()
    }
    // Private helper method
    private func log(_ message: String) {
        print("[Private] \(message)")
    }
}
let account = BankAccount(accountNumber: "123-456", balance: 1000, branchCode: "BR-001")
print("Balance: \(account.balance)")    // public getter is readable
// account.balance = 2000               // Compile error! public private(set) prevents external writes
// print(account.accountNumber)         // Compile error! private property
// print(account.lastTransactionDate)   // Compile error! fileprivate, inaccessible from another file
print("Branch: \(account.branchCode)")  // internal, accessible within the same module

Output:

TEXT 📖 للعرض فقط
Balance: 1000.0
Branch: BR-001

(2) Access Control Principles

Principle Explanation
Least privilege Default to private, loosen only when needed
Stable interface Public APIs are contracts once published -- modify carefully
Module boundaries public for a framework defines the module boundary; internal is the internal implementation
getter/setter public private(set) pattern: publicly readable, privately writable

5. Extensions and Access Control

(1) private Sharing in the Same File

private members are accessible within extensions in the same file:

SWIFT
// ============================================
// Visibility of private in extensions
// ============================================
struct UserAccount {
    private var token: String = "secret-token"
    let name: String
    func getToken() -> String {
        return token
    }
}
// Extensions in the same file can access private members
extension UserAccount {
    func resetToken() {
        token = "new-token"  // Accessible! Same file
    }
}
var account = UserAccount(name: "Alice")
print(account.getToken())
account.resetToken()
print(account.getToken())

Output:

TEXT 📖 للعرض فقط
secret-token
new-token

Note: In Swift 4+, extensions in the same file can access private members. If you need cross-file sharing, use fileprivate.

(2) Best Organization Pattern with Extensions

SWIFT
// Core data definition
struct Product {
    let id: Int
    let name: String
    let price: Double
}
// MARK: - Equatable Conformance
extension Product: Equatable {
    static func == (lhs: Product, rhs: Product) -> Bool {
        return lhs.id == rhs.id
    }
}
// MARK: - Codable Conformance
extension Product: Codable { }
// MARK: - Utility Methods
extension Product {
    func formattedPrice() -> String {
        return "$\(String(format: "%.2f", price))"
    }
    var priceWithTax: Double {
        return price * 1.08
    }
}

▶ Example: Adding Nested Types and Convenience Initializers via Extensions

Extensions can add not only methods and computed properties, but also nested types and initializers:

SWIFT
// ============================================
// Adding nested types and convenience initializers via extensions
// ============================================
// Base type
struct Book {
    let title: String
    let author: String
    let year: Int
}
// Extension: add nested types and convenience initializers
extension Book {
    // Nested enum -- genres
    enum Genre: String {
        case fiction = "Fiction"
        case nonfiction = "Non-fiction"
        case science = "Science"
        case history = "History"
    }
    // Nested struct -- reading stats
    struct ReadingStats {
        var pagesRead: Int
        var totalPages: Int
        var progress: Double {
            totalPages > 0 ? Double(pagesRead) / Double(totalPages) * 100 : 0
        }
    }
    // Convenience initializer -- create from a JSON dictionary
    init?(from dict: [String: Any]) {
        guard let title = dict["title"] as? String,
              let author = dict["author"] as? String,
              let year = dict["year"] as? Int else {
            return nil
        }
        self.init(title: title, author: author, year: year)
    }
}
// Usage
let book1 = Book(title: "Swift Programming", author: "Apple", year: 2024)
let genre = Book.Genre.science
print("\(book1.title) - Genre: \(genre.rawValue)")
let stats = Book.ReadingStats(pagesRead: 120, totalPages: 350)
print("Reading progress: \(String(format: "%.1f", stats.progress))%")
// Create from dictionary
let dict = ["title": "Advanced Swift", "author": "Alice", "year": 2025] as [String: Any]
if let book2 = Book(from: dict) {
    print("Created from dictionary: \(book2.title) (\(book2.author), \(book2.year))")
}

Output:

TEXT 📖 للعرض فقط
Swift Programming - Genre: Science
Reading progress: 34.3%
Created from dictionary: Advanced Swift (Alice, 2025)

6. Full Example: User Management System (Extension + Access Control)

SWIFT
// ============================================
// Full example: User management system
// Features: comprehensive use of extension + access control
// ============================================
import Foundation
// 1. Core user type
public class User {
    public let id: Int
    public let name: String
    public let email: String
    // Publicly readable, internally writable
    public private(set) var score: Int = 0
    public private(set) var isActive: Bool = true
    // Accessible within the module
    internal var lastLogin: Date?
    // Private
    private var loginHistory: [Date] = []
    public init(id: Int, name: String, email: String) {
        self.id = id
        self.name = name
        self.email = email
    }
    public func login() {
        let now = Date()
        lastLogin = now
        loginHistory.append(now)
        isActive = true
    }
    public func addScore(_ points: Int) {
        // Internal validation
        guard points > 0 else { return }
        score += points
    }
}
// MARK: - Description Extension
extension User {
    public func description() -> String {
        return "User(\(id): \(name), score: \(score), active: \(isActive))"
    }
    // Determine based on last login time
    public var isOnline: Bool {
        guard let last = lastLogin else { return false }
        return Date().timeIntervalSince(last) < 300
    }
}
// MARK: - Activity Log Extension
extension User {
    // fileprivate -- accessible only within this file
    fileprivate func generateActivityReport() -> String {
        return """
        User Report - \(name)
        =================
        Score: \(score)
        Active: \(isActive)
        Last Login: \(lastLogin?.description ?? "Never")
        """
    }
    public func printReport() {
        print(generateActivityReport())
    }
}
// 3. Usage example
let user = User(id: 1, name: "Alice", email: "alice@example.com")
user.login()
user.addScore(50)
user.addScore(30)
print(user.description())
print("Online: \(user.isOnline)")
user.printReport()
// user.score = 100        // Compile error -- public private(set)
// user.loginHistory        // Compile error -- private

Output:

TEXT 📖 للعرض فقط
User(1: Alice, score: 80, active: true)
Online: true
User Report - Alice
=================
Score: 80
Active: true
Last Login: 2026-07-30 12:00:00 +0000

❓ FAQ

س What's the difference between extension and inheritance?
ج Extensions add functionality to existing types (including system types like String and Date) without creating a new type. Inheritance only works with your own classes and is used to change behavior. Extensions cannot add stored properties; inheritance can.
س Can I add stored properties in an extension?
ج No. Extensions can only add computed properties, methods, initializers, subscripts, and nested types. Stored properties must be in the original type definition. This is a Swift language design decision -- to avoid breaking the memory layout of existing types.
س What is the default access level?
ج internal. If you don't write any access control modifier, the default is internal -- accessible within the current module. For App development (single module), internal is equivalent to public. For Framework development, you must explicitly write public or open to expose to external modules.
س What's the difference between open and public?
ج public allows external module access but not inheritance or overriding. open allows external modules to inherit classes and override methods. Only classes and class methods can use open. Safety principle: don't use open unless necessary.
س When should I split code into extensions?
ج When a type definition becomes large (over 200 lines), group by functionality into multiple extensions (each with a MARK comment). Common pattern: core definition, protocol conformance, utility methods, convenience initializers. One concern per extension.

📖 Summary


📝 Exercises

  1. Basic: Add an extension to Int with a squared computed property and an isEven computed property. Test 4.squared (outputs 16) and 3.isEven (outputs false).
  2. Intermediate: Add an extension to Date with a formattedDate method (returns a "yyyy-MM-dd" formatted string) and an isToday computed property. Use extension to conform to Comparable if not already conformed.
  3. Challenge: Create a SecureStore class that uses access control to manage sensitive data. Requirements: public save(key:value:) and read(key:) methods; internal cache clearing method; private encryption/decryption logic; fileprivate logging method. Use extensions to group protocol conformance and utility methods into different MARK blocks.
Web-Tutorial.com

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

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

100%