Swift 扩展(Extension)指南——给现有类型“加外挂”的正规方式

什么是 Extension

  1. 定义

    extension 是 Swift 提供的一种纵向扩展机制:"不修改原始代码、不创建子类"的前提下,给任意类型(class / struct / enum / protocol)追加功能。

  2. 与 OC Category 的区别

    • OC Category 需要名字,Swift 扩展无名字。
    • OC Category 能"声明"属性但不能"实现"存储属性;Swift 扩展同样只能写计算属性,但编译期直接报错而非运行时崩溃。
    • Swift 扩展支持协议遵守、泛型 where 约束,OC 做不到。

语法模板

swift 复制代码
extension 已有类型 [:协议1, 协议2] {
    // 新增功能
}

注意:

  • 扩展体里不能写 stored property(存储属性)。
  • 扩展体里不能给类新增deinit designated init
  • 扩展会全局生效,只要模块被 import,功能就可见;因此务必把"只内部用"的扩展标记为internalprivate

7 大能力逐一拆解

  1. 计算属性(只读 & 读写)
swift 复制代码
extension Double {
    // 以下都是"计算型属性",底层无存储,每次实时计算
    var m: Double { self }              // 米
    var km: Double { self * 1_000.0 }   // 千米 → 米
    var ft: Double { self / 3.28084 }   // 英尺 → 米
    var cm: Double { self / 100.0 }     // 厘米 → 米
}

// 用法:链式调用、参与运算
let runWay = 3.5.km + 200.m           // 3 700 米
print("跑道长度:\(runWay)m")

常见坑:

  • set 时必须同时提供 get
  • 计算属性如果算法复杂,考虑用方法替代,避免"看起来像属性却耗时"的歧义。
  1. 方法(实例 & 类型)
swift 复制代码
extension Int {
    /// 将当前数值作为次数,重复执行无参闭包
    func repetitions(task: () -> Void) {
        for _ in 0..<self { task() }
    }
}

3.repetitions {
    print("Hello extension")
}

可变方法(mutating)

扩展里修改值类型自身时,必须加 mutating

swift 复制代码
extension Int {
    mutating func square() {
        self = self * self
    }
}

var num = 9
num.square()        // 81
  1. 便利构造器(convenience init)

规则:

  • 只能给类加 convenience init
  • 必须横向调用同类中的 designated init
  • 值类型(struct/enum)扩展可写任意 init,只要"所有属性有默认值"或"最终横向调到原 init"。
swift 复制代码
struct Size { var width = 0.0, height = 0.0 }
struct Point { var x = 0.0, y = 0.0 }

struct Rect {
    var origin = Point()
    var size = Size()
}

extension Rect {
    /// 通过中心点和尺寸创建矩形
    init(center: Point, size: Size) {
        let originX = center.x - size.width / 2
        let originY = center.y - size.height / 2
        // 横向调用原成员构造器
        self.init(origin: Point(x: originX, y: originY), size: size)
    }
}

let rect = Rect(center: Point(x: 5, y: 5), size: Size(width: 20, height: 10))
  1. 下标(Subscript)
swift 复制代码
extension Int {
    subscript(digitIndex: Int) -> Int {
        // 位数不足左侧补 0
        var decimal = 1
        for _ in 0..<digitIndex { decimal *= 10 }
        return (self / decimal) % 10
    }
}

123456789[0]   // 9
123456789[3]   // 6
  1. 嵌套类型
swift 复制代码
extension Int {
    enum Kind { case negative, zero, positive }
    
    var kind: Kind {
        switch self {
        case 0: return .zero
        case let x where x > 0: return .positive
        default: return .negative
        }
    }
}

// 使用
let nums = [-3, 0, 5]
for n in nums {
    print(n.kind)
}
  1. 协议遵守(Retroactive Modeling)

场景:第三方库定义了 User,你需要让 User 支持 Codable,但源码不可改。

做法:写扩展即可。

swift 复制代码
// 假设 User 是别人的类型
struct User { let name: String }

// 我让它直接支持 Codable
extension User: Codable { }

// 现在可以
let data = try JSONEncoder().encode(User(name: "Kim"))
  1. 扩展泛型 + where 约束
swift 复制代码
extension Array where Element == Int {
    /// 仅当数组元素是 Int 时可用
    func sum() -> Int { reduce(0, +) }
}

[1, 2, 3].sum()   // 6
["a", "b"].sum()  // 编译错误,方法不可见

扩展不能做的事

  1. 不能写 stored property(不会分配内存)。
  2. 不能给类新增 deinit / designated init
  3. 不能覆盖(override)已有方法------但可以重载(overload)或使用协议默认实现"屏蔽"。
  4. 扩展中声明的私有属性/方法,作用域遵循 Swift 访问级别规则;跨模块扩展时,默认无法访问 internal 成员,除非使用 @testable

实践总结

  1. 命名与作用域

    • 扩展文件统一命名 类型+功能.swift,例如 Double+Distance.swift
    • 只在本文件使用的扩展,用 private extension 包起来,避免"全局污染"。
  2. 计算属性 vs 方法

    • 无副作用、O(1) 返回,用属性;
    • 有 IO、算法复杂、可能抛异常,用方法。
  3. 协议优先

    如果功能具备"通用性",先定义协议,再用扩展提供默认实现,例如:

    swift 复制代码
    protocol ReusableView: AnyObject { static var reuseID: String { get } }
    extension ReusableView {
        static var reuseID: String { String(describing: self) }
    }
    // 所有 UITableViewCell 一键获得 reuseID
  4. 避免"上帝扩展"

    一个文件里动辄几百行的扩展,后期维护成本极高。按"能力维度"拆文件:

    UIView+Shadow.swift

    UIView+Gradient.swift

    UIView+Snapshot.swift

可落地的 3 个业务场景

  1. 路由参数解析

    URL 扩展计算属性,快速取 query 值:

    swift 复制代码
    extension URL {
        var queryParameters: [String: String] {
            guard let q = query else { return [:] }
            return q.split(separator: "&").reduce(into: [:]) { result, pair in
                let kv = pair.split(separator: "=", maxSplits: 1)
                result[String(kv[0])] = kv.count > 1 ? String(kv[1]) : ""
            }
        }
    }
  2. 错误日志统一

    Error 扩展 log() 方法,一键上报:

    swift 复制代码
    extension Error {
        func log(file: String = #file, line: Int = #line) {
            let msg = "\(Self.self) in \(file.split(separator: "/").last ?? ""):\(line) → \(localizedDescription)"
            Logger.shared.error(msg)
        }
    }
  3. 商城 SKU 模型

    后端返回的 SKU 结构体缺少"是否缺货"字段,用扩展追加计算属性,避免改原始模型:

    swift 复制代码
    extension SKU {
        var isOutOfStock: Bool { stock <= 0 }
    }

结语

扩展是 Swift "开闭原则"的最佳注脚:

  • 对修改封闭(不动源码),对扩展开放(任意追加)。

  • 用好扩展,可以让主类型保持简洁、让功能按"维度"聚类、让团队协作不打架。

但切记:

  • "能力越大,责任越大"------不加节制地全局扩展,会让调用链难以追踪、命名冲突概率增大。

  • 先想清楚"这是共性能力还是业务补丁",再决定"用扩展、用包装器、用继承还是用组合"。

相关推荐
HarderCoder6 小时前
【Swift 错误处理全解析】——从 throw 到 typed throws,一篇就够
swift
HarderCoder7 小时前
【Swift 并发编程入门】——从 async/await 到 Actor,一文看懂结构化并发
swift
HarderCoder1 天前
Swift 中的不透明类型与装箱协议类型:概念、区别与实践
swift
HarderCoder1 天前
Swift 泛型深度指南 ——从“交换两个值”到“通用容器”的代码复用之路
swift
东坡肘子1 天前
惊险但幸运,两次!| 肘子的 Swift 周报 #0109
人工智能·swiftui·swift
胖虎11 天前
Swift项目生成Framework流程以及与OC的区别
framework·swift·1024程序员节·swift framework
songgeb2 天前
What Auto Layout Doesn’t Allow
swift
YGGP2 天前
【Swift】LeetCode 240.搜索二维矩阵 II
swift
YGGP3 天前
【Swift】LeetCode 73. 矩阵置零
swift