【iOS】Swift系列二十二 - 扩展

Swift中的扩展有点类似于OC中的分类(Category)。

扩展(Extension)可以为枚举、结构体、类、协议添加新功能(方法、计算属性、下标、初始化器(类只能扩展便捷初始化器)、嵌套类型、协议等等)。

扩展不能做的事情:

  • 不能覆盖原有的功能
  • 不能添加存储属性、不能向已有的属性添加属性观察器
  • 不能添加父类
  • 不能添加指定初始化器,不能添加反初始化器
  • ……

一、结构体

1.1. Double

Double添加距离单位:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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. 数组下标

为数组添加下标安全约束,防止数组越界程序崩溃:

1
2
3
4
5
6
7
8
9
10
11
12
13
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:数组开始索引,总是为0
  • endIndex:数组结束索引,总是大于数组最大下标值,但实际取值不包含该下标值,因此配合..<操作符使用时,它总是安全的。如果数组为nil,它的值和startIndex相等。

1.3. Int

Int添加扩展功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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. 结构体

如果希望自定义初始化器的同时,编译器也能够生成默认初始化器,可以在扩展中编写自定义初始化器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)

注意:扩展限定的指定初始化器是针对,因为只有才有指定初始化器的概念。

二、类

类添加扩展需要注意:不能添加指定初始化器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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初始化器,不能写在扩展中。

三、协议

如果一个类型已经实现了协议的所有要求,但是还没有声明它遵守了这个协议,可以通过扩展来让它遵守这个协议。

示例代码:

1
2
3
4
5
6
7
8
9
protocol TestProtocol {
func test()
}
class TestClass {
func test() {
print("test")
}
}
extension TestClass : TestProtocol { }

3.1. 编写一个函数,判断一个整数是否为奇数

示例代码:

1
2
3
4
5
func isOdd(_ i: Int) -> Bool {
i % 2 != 0
}
let i = 3
print(isOdd(i)) // 输出:true

如果把变量类型换成UInt就报错了:

如果把函数修改成泛型函数就无法限制传入的参数是整数类型。怎么解决呢?其实所有的整数都遵守了BinaryInteger协议,加个泛型约束即可。

1
2
3
4
5
func isOdd<T: BinaryInteger>(_ i: T) -> Bool {
i % 2 != 0
}
let i = UInt(3)
print(isOdd(i)) // 输出:true

但是这样写成全局函数并不好,最好的方法就是把函数写到只有整数才能调用的地方。

1
2
3
4
5
6
7
8
9
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. 注意点

  • 扩展可以给协议提供默认实现,也间接实现可选协议的效果
  • 扩展可以给协议扩充协议中从未声明过的方法

示例代码一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

由于扩展协议已经实现了协议,所以类遵守协议时不需要再次实现协议内容。

示例代码二:

如果遵守协议的类内部也实现了协议,那么优先执行类中的协议内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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

示例代码三:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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的输出是协议中的呢?

  1. 由于在协议中没有声明test2,所以编译器不能确定将来指向的实例对象是否有test2的实现。

  2. 因此把实例cls定义为TestProtocol协议类型后,调用test2时,编译器会认为test2在实例里面可能是不存在的,所以直接去协议里优先找。

  3. 调用类中的test1函数的原因是:因为协议中是有声明test1函数的,而协议默认规定类遵守协议必须实现协议内容,所以会从类中调用test1

四、泛型

在扩展中仍然可以使用原类型中的泛型类型。扩展时也可以对泛型附加约束条件。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
}
}