Swift
中的扩展有点类似于OC
中的分类(Category
)。
扩展(Extension)可以为枚举、结构体、类、协议添加新功能(方法、计算属性、下标、初始化器(类只能扩展便捷初始化器)、嵌套类型、协议等等)。
扩展不能做的事情:
- 不能覆盖原有的功能
- 不能添加存储属性、不能向已有的属性添加属性观察器
- 不能添加父类
- 不能添加指定初始化器,不能添加反初始化器
- ……
一、结构体
1.1. Double
为Double
添加距离单位:
extension Double {
var km: Double {
self * 1_000.0
}
var m: Double {
self
}
var cm: Double {
self / 100.0
}
var mm: Double {
self / 1_000.0
}
}
var d = 100.0.mm
print(d) // 输出:0.1
print(d.km) // 输出:100.0
print(d.m) // 输出:0.1
print(d.cm) // 输出:0.001
1.2. 数组下标
为数组添加下标安全约束,防止数组越界程序崩溃:
extension Array {
subscript(nullable idx: Int) -> Element? {
if (startIndex..<endIndex).contains(idx) {
return self[idx]
}
return nil
}
}
let numbers = [10, 20, 30, 40, 50]
var i1 = numbers[nullable: -1]
print(i1 as Any) // 输出:nil
var i2 = numbers[nullable: 3]
print(i2 as Any) // 输出:Optional(40)
startIndex
:数组开始索引,总是为0endIndex
:数组结束索引,总是大于数组最大下标值,但实际取值不包含该下标值,因此配合..<
操作符使用时,它总是安全的。如果数组为nil
,它的值和startIndex
相等。
1.3. Int
Int
添加扩展功能:
extension Int {
// 重复执行
func repeats(task: () -> Void) {
for _ in 0..<self {
task()
}
}
// 求平方
mutating func square() -> Int {
self = self * self
return self
}
// 嵌套枚举类型
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
}
}
// 获取最大位数的值(例如:1234[3],结果:1)
subscript(digitIndex: UInt) -> Int {
var decimalBase = 1
for _ in 0..<digitIndex {
decimalBase *= 10
}
return (self / decimalBase) % 10
}
}
3.repeats {
print("repeat print")
}
/*
输出:
repeat print
repeat print
repeat print
*/
var i1 = 10
print(i1.square()) // 输出:100
var i2 = -10
print(i2.kind) // 输出:negative
var i3 = 12345
print(i3[3]) // 输出:2
1.4. 结构体
如果希望自定义初始化器的同时,编译器也能够生成默认初始化器,可以在扩展中编写自定义初始化器。
struct Point {
var x: Int = 0
var y: Int = 0
}
extension Point {
init(_ point: Point) {
self.init(x: point.x, y: point.y)
}
}
var p1 = Point()
var p2 = Point(x: 10)
var p3 = Point(y: 20)
var p4 = Point(x: 10, y: 20)
var p5 = Point(p4)
注意:扩展限定的指定初始化器是针对类,因为只有类才有指定初始化器的概念。
二、类
类添加扩展需要注意:不能添加指定初始化器。
class Person {
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
extension Person : Equatable {
static func == (left: Person, right: Person) -> Bool {
left.age == right.age && left.name == right.name
}
convenience init() {
self.init(age: 0, name: "")
}
}
注意:类遵守协议实现的
required
初始化器,不能写在扩展中。
三、协议
如果一个类型已经实现了协议的所有要求,但是还没有声明它遵守了这个协议,可以通过扩展来让它遵守这个协议。
示例代码:
protocol TestProtocol {
func test()
}
class TestClass {
func test() {
print("test")
}
}
extension TestClass : TestProtocol {
}
3.1. 编写一个函数,判断一个整数是否为奇数
示例代码:
func isOdd(_ i: Int) -> Bool {
i % 2 != 0
}
let i = 3
print(isOdd(i)) // 输出:true
如果把变量类型换成UInt
就报错了:
如果把函数修改成泛型函数就无法限制传入的参数是整数类型。怎么解决呢?其实所有的整数都遵守了BinaryInteger
协议,加个泛型约束即可。
func isOdd<T: BinaryInteger>(_ i: T) -> Bool {
i % 2 != 0
}
let i = UInt(3)
print(isOdd(i)) // 输出:true
但是这样写成全局函数并不好,最好的方法就是把函数写到只有整数才能调用的地方。
extension BinaryInteger {
func isOdd() -> Bool {
self % 2 != 0
}
}
let i = Int8(10)
print(i.isOdd()) // 输出:false
print(10.isOdd()) // 输出:false
print((-3).isOdd()) // 输出:true
注意:负数一定要加上括号,否则编译器会把后面的小数点一起作为
Double
类型,最终编译报错。
3.2. 注意点
- 扩展可以给协议提供默认实现,也间接实现可选协议的效果
- 扩展可以给协议扩充协议中从未声明过的方法
示例代码一:
protocol TestProtocol {
func test1()
}
extension TestProtocol {
func test1() {
print("TestProtocol test1")
}
func test2() {
print("TestProtocol test2")
}
}
class TestClass : TestProtocol {
}
var cls = TestClass()
cls.test1() // 输出:TestProtocol test1
cls.test2() // 输出:TestProtocol test2
由于扩展协议已经实现了协议,所以类遵守协议时不需要再次实现协议内容。
示例代码二:
如果遵守协议的类内部也实现了协议,那么优先执行类中的协议内容。
protocol TestProtocol {
func test1()
}
extension TestProtocol {
func test1() {
print("TestProtocol test1")
}
func test2() {
print("TestProtocol test2")
}
}
class TestClass : TestProtocol {
func test1() {
print("TestClass test1")
}
func test2() {
print("TestClass test2")
}
}
var cls = TestClass()
cls.test1() // 输出:TestClass test1
cls.test2() // 输出:TestClass test2
示例代码三:
protocol TestProtocol {
func test1()
}
extension TestProtocol {
func test1() {
print("TestProtocol test1")
}
func test2() {
print("TestProtocol test2")
}
}
class TestClass : TestProtocol {
func test1() {
print("TestClass test1")
}
func test2() {
print("TestClass test2")
}
}
var cls: TestProtocol = TestClass()
cls.test1() // 输出:TestClass test1
cls.test2() // 输出:TestProtocol test2
为什么test2
的输出是协议中的呢?
-
由于在协议中没有声明
test2
,所以编译器不能确定将来指向的实例对象是否有test2
的实现。 -
因此把实例
cls
定义为TestProtocol
协议类型后,调用test2
时,编译器会认为test2
在实例里面可能是不存在的,所以直接去协议里优先找。 -
调用类中的
test1
函数的原因是:因为协议中是有声明test1
函数的,而协议默认规定类遵守协议必须实现协议内容,所以会从类中调用test1
四、泛型
在扩展中仍然可以使用原类型中的泛型类型。扩展时也可以对泛型附加约束条件。
示例代码:
class Stack<E> {
var elements = [E]()
func push(_ element: E) {
elements.append(element)
}
func pop() -> E {
elements.removeLast()
}
func size() -> Int {
elements.count
}
}
extension Stack {
func top() -> E? {
elements.last
}
}
extension Stack : Equatable where E : Equatable {
static func == (left: Stack, right: Stack) -> Bool {
left.elements == right.elements
}
}
转载:https://blog.csdn.net/yangbenben8866/article/details/116259808