Swift 扩展

Swift 中的扩展(Extension) 是一种强大而灵活的机制,允许你为已有类型(包括系统类型)添加新功能,而无需修改原始代码或继承。
它遵循 “开闭原则”:对扩展开放,对修改封闭。


一、扩展能做什么?

功能示例
添加计算属性var isEmpty: Bool
添加实例方法 / 类型方法func reversed() -> String
添加初始化器init(json: [String: Any])
添加下标subscript(index: Int)
添加嵌套类型enum Direction
让类型遵守协议extension Array: CustomStringConvertible

不能:添加存储属性、属性观察器、重写已有成员


二、基础语法

extension 类型名 {
    // 新功能...
}

三、核心示例

1. 为 Int 添加实用方法

extension Int {
    // 计算属性
    var isEven: Bool { self % 2 == 0 }
    var isOdd: Bool { !isEven }

    // 实例方法
    func squared() -> Int {
        return self * self
    }

    mutating func square() {
        self = squared()
    }

    // 类型方法
    static func random(in range: ClosedRange<Int>) -> Int {
        return Int.random(in: range)
    }
}

// 使用
let num = 5
print(num.isEven)      // false
print(num.squared())   // 25

var x = 3
x.square()
print(x)               // 9

print(Int.random(in: 1...10))  // 随机数

2. 为 String 添加验证功能

extension String {
    var isEmail: Bool {
        let regex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
        return NSPredicate(format: "SELF MATCHES %@", regex).evaluate(with: self)
    }

    func trimmed() -> String {
        return self.trimmingCharacters(in: .whitespacesAndNewlines)
    }
}

let email = " swift@example.com "
print(email.isEmail)      // true
print(email.trimmed())    // "swift@example.com"

3. 为 Array 添加安全下标

extension Array {
    subscript(safe index: Int) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

let array = [1, 2, 3]
print(array[safe: 1])   // Optional(2)
print(array[safe: 5])   // nil

4. 协议扩展 + 默认实现(强大!)

protocol JSONExportable {
    func toJSON() -> [String: Any]
}

// 为所有遵守的类型提供默认实现
extension JSONExportable {
    func toJSON() -> [String: Any] {
        return Mirror(reflecting: self).children.reduce(into: [String: Any]()) { result, child in
            if let label = child.label {
                result[label] = child.value
            }
        }
    }
}

struct User: JSONExportable {
    let name: String
    let age: Int
}

let user = User(name: "Alice", age: 25)
print(user.toJSON())  // ["name": "Alice", "age": 25]

5. 条件扩展(Conditional Extension)

extension Array where Element: Equatable {
    func containsAny(of items: [Element]) -> Bool {
        return items.contains { self.contains($0) }
    }
}

let nums = [1, 2, 3, 4]
print(nums.containsAny(of: [3, 5]))  // true

仅当 ElementEquatable 时生效


6. 初始化器扩展(便捷构造)

extension URL {
    init?(string: String?) {
        guard let string = string, !string.isEmpty else { return nil }
        self.init(string: string)
    }
}

let url = URL(string: "https://swift.org")

7. 嵌套类型扩展

extension BinaryInteger {
    enum Parity {
        case even, odd
    }

    var parity: Parity {
        return self % 2 == 0 ? .even : .odd
    }
}

print(5.parity)  // odd

四、扩展 vs 继承 vs 子类化

特性扩展继承
添加存储属性NoYes
重写方法NoYes
修改原类型NoNo
支持值类型Yes (struct, enum)No
支持系统类型YesNo
协议默认实现YesNo

推荐:优先使用扩展


五、实际项目中的扩展应用

1. UIKit 扩展(常用)

import UIKit

extension UIView {
    func addSubviews(_ views: UIView...) {
        views.forEach(addSubview)
    }

    func roundCorners(_ radius: CGFloat = 8) {
        layer.cornerRadius = radius
        clipsToBounds = true
    }
}

// 使用
let stack = UIStackView()
stack.addSubviews(label, button)
imageView.roundCorners(12)

2. SwiftUI 扩展

extension View {
    func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
        clipShape(RoundedCorner(radius: radius, corners: corners))
    }
}

3. 错误处理扩展

extension Error {
    var localizedMessage: String {
        return (self as? LocalizedError)?.errorDescription ?? "未知错误"
    }
}

六、扩展协议(Protocol Extension)

extension Collection where Element: Hashable {
    var frequency: [Element: Int] {
        return reduce(into: [:]) { $0[$1, default: 0] += 1 }
    }
}

print([1,2,2,3,3,3].frequency)  // [1: 1, 2: 2, 3: 3]

最佳实践总结

场景推荐
添加工具方法extension Int
安全访问subscript(safe:)
协议默认实现extension Protocol
条件功能where 约束
UI 通用行为extension UIView
避免继承优先扩展

常见面试题

  1. 扩展能添加存储属性吗?
    → 不能。可用计算属性或关联对象(objc_Association)。
  2. 如何为 String 添加存储属性?
private var key: UInt8 = 0
extension String {
    var customID: Int {
        get { objc_getAssociatedObject(self, &key) as? Int ?? 0 }
        set { objc_setAssociatedObject(self, &key, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    }
}
  1. 扩展和协议扩展的区别?
    → 扩展:为具体类型添加功能
    → 协议扩展:为所有遵守者提供默认实现
  2. 以下代码编译通过吗?
extension Array {
    mutating func append(_ item: Element) { ... }
}

不通过!不能重写已有方法

  1. 如何让扩展只在 iOS 15+ 可用?
@available(iOS 15, *)
extension MyView {
    func asyncMethod() async { ... }
}

小技巧

// 1. 全局工具扩展
extension BinaryInteger {
    var formatted: String {
        return Formatter.withCommas.string(from: NSNumber(value: Int(self))) ?? "\(self)"
    }
}

// 2. 链式调用
extension UIButton {
    @discardableResult
    func title(_ text: String) -> Self {
        setTitle(text, for: .normal)
        return self
    }
}

// 使用:button.title("登录").backgroundColor(.blue)

// 3. 私有扩展(文件内共享)
private extension MyClass {
    func helper() { ... }
}

高级话题(可继续提问)

  • 关联对象(Associated Objects)底层
  • 扩展与方法派发(vtable vs witness table)
  • SwiftUI @ViewBuilder 扩展
  • 泛型扩展 + where 子句高级用法
  • 运行时 extension 注入(Method Swizzling)

需要 完整项目扩展架构性能对比,或 自定义 DSL 扩展实战?欢迎继续提问!

文章已创建 2481

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部