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
仅当
Element是Equatable时生效
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 子类化
| 特性 | 扩展 | 继承 |
|---|---|---|
| 添加存储属性 | No | Yes |
| 重写方法 | No | Yes |
| 修改原类型 | No | No |
| 支持值类型 | Yes (struct, enum) | No |
| 支持系统类型 | Yes | No |
| 协议默认实现 | Yes | No |
推荐:优先使用扩展
五、实际项目中的扩展应用
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 |
| 避免继承 | 优先扩展 |
常见面试题
- 扩展能添加存储属性吗?
→ 不能。可用计算属性或关联对象(objc_Association)。 - 如何为
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) }
}
}
- 扩展和协议扩展的区别?
→ 扩展:为具体类型添加功能
→ 协议扩展:为所有遵守者提供默认实现 - 以下代码编译通过吗?
extension Array {
mutating func append(_ item: Element) { ... }
}
→ 不通过!不能重写已有方法
- 如何让扩展只在 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 扩展实战?欢迎继续提问!