Swift 学习笔记:extension 逆向建模

在 Swift 中,扩展(Extension)是一种向已存在的类、结构体、枚举或者协议类型添加新功能的方式。这包括在没有权限获取原始源代码的情况下扩展类型的能力(即逆向建模)。扩展和 Objective-C 中的分类 Category 类似。(与 Objective-C 不同的是,Swift 的扩展没有名字。)

Swift 中的扩展可以:

  • 添加计算型属性和计算型类型属性
  • 定义实例方法和类型方法
  • 提供新的构造器
  • 定义下标
  • 定义和使用新的嵌套类型
  • 使一个已有类型符合某个协议

在 Swift 中,你甚至可以扩展协议,以提供协议需要的实现或添加额外的功能,可以被协议采纳的类型使用。

一、添加计算型属性和计算型类型属性

在 Swift 中,扩展可以添加计算实例属性计算类型属性。但是,扩展不能添加存储属性,也不能向已有的属性添加属性观察者。

计算实例属性

计算实例属性是一种特殊的属性,它并不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性和值。

下面的例子为 Swift 的内置 Double 类型添加了五个计算实例属性,以提供基本的单位转换:

swift 复制代码
extension Double {
    var km: Double { return self * 1_000.0 }
    var m: Double { return self }
    var cm: Double { return self / 100.0 }
    var mm: Double { return self / 1_000.0 }
    var ft: Double { return self / 3.28084 }
}

let oneInch = 25.4.mm
print("One inch is \(oneInch) meters") // 输出 "One inch is 0.0254 meters"

计算类型属性

计算类型属性是通过类型本身调用的属性,而不是通过类型的实例调用的。在为类、结构体和枚举定义计算类型属性时,需要使用 static 关键字。对于类,还可以使用 class 关键字来允许子类重写父类的实现。

下面的例子定义了一个扩展,为 Int 类型添加了一个计算类型属性 random,返回一个随机整数:

swift 复制代码
extension Int {
    static var random: Int {
        return Int(arc4random())
    }
}

print(Int.random) // 输出一个随机整数

在这个例子中,random 是一个计算类型属性,返回 Int(arc4random()) 的结果。当调用 Int.random 时,返回的是一个随机整数。

扩展为什么不能添加存储属性?

扩展不能添加存储属性是因为存储属性需要内存来存储值,而扩展并不允许添加新的存储空间。扩展只能添加计算属性,即提供 getter 和可选的 setter 来间接获取和设置值,而不是直接存储值。这是为了确保类型的一致性和内存布局的可预测性。

二、定义实例方法和类型方法

在 Swift 中,扩展可以添加新的实例方法和类型方法到已存在的类型中。这样你就可以把自定义的方法和修改的方法添加到公共或者自定义类型中。

实例方法

实例方法是属于某个特定类、结构体或者枚举实例的函数。实例方法必须写在类型的作用域内部。

下面的例子为 Int 类型添加了一个实例方法 repetitions,用于执行某个任务多次:

swift 复制代码
extension Int {
    func repetitions(task: () -> Void) {
        for _ in 0..<self {
            task()
        }
    }
}

3.repetitions {
    print("Hello!")
}
// 输出 "Hello!" 三次

在这个例子中,repetitions 是一个实例方法,接受一个闭包 task 作为参数,并执行 self 次。

类型方法

类型方法是被类型本身调用的方法。你可以通过在函数前面加上关键字 static 来定义类型方法。类还可以使用关键字 class 来允许子类重写父类对类型方法的实现。

下面的例子为 Int 类型添加了一个类型方法 random,用于生成一个随机整数:

swift 复制代码
extension Int {
    static func random() -> Int {
        return Int(arc4random())
    }
}

print(Int.random()) // 输出一个随机整数

在这个例子中,random 是一个类型方法,返回 Int(arc4random()) 的结果。当调用 Int.random() 时,返回的是一个随机整数。

三、提供新的构造器

在 Swift 中,扩展可以为已存在的类型添加新的构造器。这可以让你扩展其他类型,以接受你自己的自定义类型作为构造器的参数。你也可以定义便利构造器来调用同一类型的指定构造器,并为其参数提供默认值。

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 centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(width: 3.0, height: 3.0))

在这个例子中,新的 Rect 构造器首先根据提供的中心点和大小计算出原点的坐标。然后调用 self.init(origin:size:) 构造器来将新的原点和大小值赋值到对应的属性中。

四、定义下标

在 Swift 中,扩展可以为已存在的类型添加新的下标。下标可以定义在类、结构体和枚举中,是访问集合、列表或序列中元素的快捷方式。

下面的例子为 Swift 的内置类型 Int 添加了一个下标,该下标接受一个 Int 类型的数组作为输入,返回数组中对应索引的元素:

swift 复制代码
extension Int {
    subscript(digitIndex: [Int]) -> Int {
        var decimalBase = 1
        for _ in 0..<digitIndex.count {
            decimalBase *= 10
        }
        return (self / decimalBase) % 10
    }
}

let numbers = [3, 42, 1729]
print(numbers[0]) // 输出 "3"
print(numbers[1]) // 输出 "42"
print(numbers[2]) // 输出 "1729"

在这个例子中,新的 Int 下标首先计算出十进制的基数,然后返回 self 除以基数的余数。这样就可以通过下标直接访问 Int 数组中的元素。

五、定义和使用新的嵌套类型

在 Swift 中,扩展可以为已存在的类、结构体和枚举添加新的嵌套类型。

嵌套类型是在枚举类型、类或者结构体中定义的新类型。这样可以使你的代码更加清晰和易于维护,因为你可以将相关的功能组织在一起。

下面的例子为 Int 类型添加了一个新的嵌套枚举 Kind,用于表示整数的种类:

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
        }
    }
}

print(0.kind) // 输出 ".zero"
print(123.kind) // 输出 ".positive"
print((-123).kind) // 输出 ".negative"

在这个例子中,Kind 是一个嵌套在 Int 中的枚举类型,有三个可能的值:.negative、.zero 和 .positive。kind 是一个计算属性,根据 Int 的值返回对应的 Kind。

六、使一个已有类型符合某个协议

以下是一个示例,我们将使 String 类型符合一个新的协议 Printable:

swift 复制代码
protocol Printable {
    func printValue()
}

extension String: Printable {
    func printValue() {
        print(self)
    }
}

let myString = "Hello, Swift!"
myString.printValue() // 输出 "Hello, Swift!"
相关推荐
Keya20 小时前
lipo 命令行指南
ios·xcode·swift
zhangmeng20 小时前
SwiftUI中如何实现子视图向父视图传递数据?
ios·swiftui·swift
Saafo20 小时前
迁移至 Swift Actors
ios·swift
杂雾无尘2 天前
告别构建错误, iOS 开发架构难题全面解析, 避免 CPU 架构陷阱
ios·swift·客户端
大熊猫侯佩2 天前
探秘 WWDC 25 全新 #Playground 宏:提升 Swift 开发效率的超级神器
xcode·swift·wwdc
移动端小伙伴3 天前
10.推送的扩展能力 — 打造安全的通知体验
swift
移动端小伙伴3 天前
推送的扩展能力 — 打造个性化的通知体验
swift
移动端小伙伴3 天前
远程推送(Remote Push Notification)
swift
移动端小伙伴3 天前
本地通知的精准控制三角:时间、位置、情境
swift
移动端小伙伴3 天前
本地通知内容深度解析 — 打造丰富的通知体验
swift