小言_互联网的博客

Swift系列二十二 - 扩展

270人阅读  评论(0)

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:数组开始索引,总是为0
  • endIndex:数组结束索引,总是大于数组最大下标值,但实际取值不包含该下标值,因此配合..<操作符使用时,它总是安全的。如果数组为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的输出是协议中的呢?

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

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

  3. 调用类中的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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场