Swift 进阶(十二)高级运算符、扩展、访问控制

596 阅读12分钟

高级运算符

溢出运算符(Overflow Operator)

Swift的算数运算符出现溢出时会抛出运行时错误

Swift有溢出运算符&+、&-、&*,用来支持溢出运算

var min = UInt8.min
print(min &- 1) // 255, Int8.max

var max = UInt8.max
print(max &+ 1) // 0, Int8.min
print(max &* 2) // 254, 等价于 max &+ max

计算方式

类似于一个循环,最大值255再+1,就会回到0;最小值0再-1,就会回到255

max &* 2就等于max &+ max,也就是255 + 1 + 254,255 + 1会变为0,那么最后的值就是254

-w596

运算符重载(Operator Overload)

类、结构体、枚举可以为现有的运算符提供自定义的实现,这个操作叫做运算符重载

struct Point {
    var x: Int, y: Int
}

func + (p1: Point, p2: Point) -> Point {
    Point(x: p1.x + p2.x, y: p1.y + p2.y)
}

let p = Point(x: 10, y: 20) + Point(x: 11, y: 22)
print(p) // Point(x: 21, y: 42) Point(x: 11, y: 22)

一般将运算符重载写在相关的结构体、类、枚举的内部

struct Point {
    var x: Int, y: Int
    
    // 默认就是中缀运算符
    static func + (p1: Point, p2: Point) -> Point {
        Point(x: p1.x + p2.x, y: p1.y + p2.y)
    }
    
    static func - (p1: Point, p2: Point) -> Point {
        Point(x: p1.x - p2.x, y: p1.y - p2.y)
    }
    
    // 前缀运算符
    static prefix func - (p: Point) -> Point {
        Point(x: -p.x, y: -p.y)
    }
    
    static func += (p1: inout Point, p2: Point) {
        p1 = p1 + p2
    }
    
    static prefix func ++ (p: inout Point) -> Point {
        p += Point(x: 1, y: 1)
        return p
    }
    
    // 后缀运算符
    static postfix func ++ (p: inout Point) -> Point {
        let tmp = p
        p += Point(x: 1, y: 1)
        return tmp
    }
    
    static func == (p1: Point, p2: Point) -> Bool {
        (p1.x == p2.x) && (p1.y == p2.y)
    }
}

var p1 = Point(x: 10, y: 20)
var p2 = Point(x: 11, y: 22)
print(p1 + p2) // Point(x: 21, y: 42)
print(p2 - p1) // Point(x: 1, y: 2)
print(-p1) // Point(x: -10, y: -20)

p1 += p2
print(p1, p2) // Point(x: 21, y: 42) Point(x: 11, y: 22)

p1 = ++p2
print(p1, p2) // Point(x: 12, y: 23) Point(x: 12, y: 23)

p1 = p2++
print(p1, p2) // Point(x: 12, y: 23) Point(x: 13, y: 24)
print(p1 == p2) // false

Equatable

要想得知两个实例是否等价,一般做法是遵守Equatable协议,重载==运算符

于此同时,等价于!=运算符

class Person: Equatable {
    var age: Int
    
    init(age: Int) {
        self.age = age
    }
    
    static func == (lhs: Person, rhs: Person) -> Bool {
        lhs.age == rhs.age
    }
}

var p1 = Person(age: 10)
var p2 = Person(age: 20)
print(p1 == p2) // false
print(p1 != p2) // true

如果没有遵守Equatable协议,使用!=就会报错

-w640

如果没有遵守Equatable协议,只重载==运算符,也可以使用p1 == p2的判断,但是遵守Equatable协议是为了能够在有限制的泛型函数中作为参数使用

func equal<T: Equatable>(_ t1: T, _ t2: T) -> Bool {
    t1 == t2
}

print(equal(p1, p2)) // false

Swift为以下类型提供默认的Equatable实现

没有关联类型的枚举

enum Answer {
    case right, wrong
}

var s1 = Answer.right
var s2 = Answer.wrong
print(s1 == s2)

只拥有遵守Equatable协议关联类型的枚举

系统很多自带类型都已经遵守了Equatable协议,类似Int、Double

enum Answer: Equatable {
    case right, wrong(Int)
}

var s1 = Answer.wrong(20)
var s2 = Answer.wrong(10)
print(s1 == s2)

关联类型没有遵守Equatable协议的就会报错

-w640

只拥有遵守Equatable协议存储属性的结构体

struct Point: Equatable {
    var x: Int, y: Int
}

var p1 = Point(x: 10, y: 20)
var p2 = Point(x: 11, y: 22)
print(p1 == p2) // false
print(p1 != p2) // true

引用类型比较存储的地址值是否相等(是否引用着同一个对象),使用恒等运算符===、!==

class Person {
    
}

var p1 = Person()
var p2 = Person()
print(p1 === p2) // false
print(p1 !== p2) // true

Comparable

要想比较两个实例的大小,一般做法是遵守Comparable协议,重载相应的运算符

struct Student: Comparable {
    var age: Int
    var score: Int
    init(score: Int, age: Int) {
        self.score = score
        self.age = age
    }
    
    static func < (lhs: Student, rhs: Student) -> Bool {
        (lhs.score < rhs.score) || (lhs.score == rhs.score && lhs.age > rhs.age)
    }
    
    static func > (lhs: Student, rhs: Student) -> Bool {
        (lhs.score > rhs.score) || (lhs.score == rhs.score && lhs.age < rhs.age)
    }
    
    static func <= (lhs: Student, rhs: Student) -> Bool {
        !(lhs > rhs)
    }
    
    static func >= (lhs: Student, rhs: Student) -> Bool {
        !(lhs < rhs)
    }
}

var stu1 = Student(score: 100, age: 20)
var stu2 = Student(score: 98, age: 18)
var stu3 = Student(score: 100, age: 20)

print(stu1 > stu2) // true
print(stu1 >= stu2) // true
print(stu1 >= stu3) // true
print(stu1 <= stu3) // true
print(stu2 < stu1) // true
print(stu2 <= stu1) // true

自定义运算符(Custom Operator)

可以自定义新的运算符:在全局作用域使用operator进行声明

prefix operator 前缀运算符
postfix operator 后缀运算符
infix operator 中缀运算符:优先级组

precedencegroup 优先级组 {
    associativity: 结合性(left\right\none)
    higherThan: 比谁的优先级更高
    lowerThan: 比谁的优先级低
    assignment: true代表在可选链操作中拥有跟赋值运算符一样的优先级
}

自定义运算符的使用示例如下

prefix operator +++

prefix func +++ (_ i: inout Int) {
    i += 2
}

var age = 10
+++age
print(age) // 12
infix operator +-: PlusMinusPrecedence
precedencegroup PlusMinusPrecedence {
    associativity: none
    higherThan: AdditionPrecedence
    lowerThan: MultiplicationPrecedence
    assignment: true
}

struct Point {
    var x = 0, y = 0
    static func +- (p1: Point, p2: Point) -> Point {
        Point(x: p1.x + p2.x, y: p1.y - p2.y)
    }
}

var p1 = Point(x: 10, y: 20)
var p2 = Point(x: 5, y: 10)

print(p1 +- p2) // Point(x: 15, y: 10)

优先级组中的associativity的设置影响

associativity对应的三个选项

left: 从左往右执行,可以多个进行结合
right: 从右往左执行,可以多个进行结合
none: 不支持多个结合

如果再增加一个计算就会报错,因为我们设置的associativitynone

-w643

我们把associativity改为left或者right,再运行就可以了

infix operator +-: PlusMinusPrecedence
precedencegroup PlusMinusPrecedence {
    associativity: left
    higherThan: AdditionPrecedence
    lowerThan: MultiplicationPrecedence
    assignment: true
}

var p3 = Point(x: 5, y: 10)
print(p1 +- p2 +- p3) // Point(x: 20, y: 0)

优先级组中的assignment的设置影响

我们先看下面的示例代码

class Person {
    var age = 0
    var point: Point = Point()
}

var p: Person? = nil

print(p?.point +- Point(x: 10, y: 20))

设置assignmenttrue的意思就是如果在运算中,前面的可选项为nil,那么运算符后面的代码就不会执行了

Apple文档参考链接: developer.apple.com/documentati…

另一个: docs.swift.org/swift-book/…

扩展(Extension)

基本概念

Swift中的扩展,类似于OC中的Category

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

扩展不能做到以下这几项

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

计算属性、方法、下标、嵌套类型

extension Double {
    var km: Double { self * 1_000.0 }
    var m: Double { self }
    var dm: Double { self / 10.0 }
    var cm: Double { self / 100.0 }
    var mm: Double { self / 1_000.0 }
}
extension Array {
    subscript(nullable idx: Int) -> Element? {
        if (startIndex..<endIndex).contains(idx) {
            return self[idx]
        }
        return nil
    }
}
extension Int {
    func repetitions(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
        }
    }
    
    subscript(digitIndex: Int) -> Int {
        var decimalBase = 1
        for _ in 0..<digitIndex { decimalBase += 10 }
        return (self / decimalBase) % 10
    }
}

初始化器

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: "")
    }
}

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

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: 10)
var p4 = Point(x: 10, y: 20)
var p5 = Point(p4)

required的初始化器也不能写在扩展中

-w634

协议

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

protocol TestProtocol {
    func test1()
}

class TestClass {
    func test1() {
        print("TestClass test1")
    }
}

extension TestClass: TestProtocol { }
extension BinaryInteger {
    func isOdd() -> Bool {self % 2 != 0 }
}

print(10.isOdd())

扩展可以给协议提供默认实现,也间接实现可选协议的结果

扩展可以给协议扩充协议中从未声明过的方法

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

var cls2: TestProtocol = TestClass()
cls2.test1() // TestProtocol test1
cls2.test2() // 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

var cls2: TestProtocol = TestClass()
cls2.test1() // TestClass test1
cls2.test2() // TestProtocol test2

泛型

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

访问控制(Access Control)

基本概念

在访问权限控制这块,Swift提供了5个不同的访问级别(以下是从高到低排列,实体指被访问级别修饰的内容)

  • open: 允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写(open只能用在类、类成员上)
  • public: 允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写
  • internal: 只允许在定义实体的模块中访问,不允许在其他模块中访问
  • fileprivate: 只允许在定义实体的源文件中访问
  • private: 只允许在定义实体的封闭声明中访问

绝大部分实体默认都是internal级别

访问级别的使用准则

一个实体不可以被更低访问级别的实体定义

变量\常量类型 ≥ 变量\常量

internal class Person {} // 变量类型
fileprivate var person: Person // 变量

-w635

参数类型、返回值类型 ≥ 函数

// 参数类型:Int、Double
// 函数:func test
internal func test(_ num: Int) -> Double {
    return Double(num)
}

父类 ≥ 子类

class Person {}
class Student: Person {}
public class Person {}
class Student: Person {}

-w645

父协议 ≥ 子协议

public protocol Sportable {}
internal protocol Runnalbe: Sportable {}

-w646

原类型 ≥ typealias

class Person {} // 原类型
private typealias MyPerson = Person

原始值类型\关联值类型 ≥ 枚举类型

typealias MyInt = Int
typealias MyString = String

enum Score {
    case point(MyInt)
    case grade(MyString)
}

-w640

定义类型A时用到的其他类型 ≥ 类型A

typealias MyString = String

struct Dog {}

class Person {
    var age: MyString = ""
    var dog: Dog?
}

-w645

元组类型

元组类型的访问级别是所有成员类型最低的那个

internal struct Dog { }
fileprivate class Person { }

// (Dog, Person)中更低的访问级别是fileprivate,所以元组的访问级别就是fileprivate
fileprivate var datal: (Dog, Person)
private var data2: (Dog, Person)

泛型类型

泛型类型的访问级别是类型的访问级别以及所有泛型类型参数的访问级别中最低的那个

internal class Car {}
fileprivate class Dog {}
public class Person<T1, T2> {}

// Person<Car, Dog>中比较的是Person、Car、Dog三个的访问级别最低的那个,也就是fileprivate,fileprivate就是泛型类型的访问级别
fileprivate var p = Person<Car, Dog>()

成员、嵌套类型

类型的访问级别会影响成员(属性、方法、初始化器、下标),嵌套类型的默认访问级别

一般情况下,类型为privatefileprivate,那么成员\嵌套类型默认也是privatefileprivate

fileprivate class FilePrivateClass { // fileprivate
    func f1() {} // fileprivate
    private func f2() {} // private
}

private class PrivateClass { // private
    func f() {} // private
}

一般情况下,类型为internalpublic,那么成员\嵌套类型默认是internal

public class PublicClass { // public
    public var p1 = 0 // public
    var p2 = 0 // internal
    fileprivate func f1() {} // fileprivate
    private func f2() {} // private
}

class InternalClass { // internal
    var p = 0 // internal
    fileprivate func f1() {} // fileprivate
    private func f2() {} // private
}

看下面几个示例,编译能否通过?

示例1

private class Person {}
fileprivate class Student: Person {}
class Test {
    private class Person {}
    fileprivate class Student: Person {}
}

结果是第一段代码编译通过,第二段代码编译报错

-w642

第一段代码编译通过,是因为两个全局变量不管是private还是fileprivate,作用域都是当前文件,所以访问级别就相同了

第二段代码的两个属性的作用域局限到类里面了,那访问级别就有差异了

示例2


private struct Dog {
    var age: Int = 0
    func run() {}
}

fileprivate struct Person {
    var dog: Dog = Dog()
    mutating func walk() {
        dog.run()
        dog.age = 1
    }
}
private struct Dog {
    private var age: Int = 0
    private func run() {}
}

fileprivate struct Person {
    var dog: Dog = Dog()
    mutating func walk() {
        dog.run()
        dog.age = 1
    }
}

结果是第一段代码编译通过,第二段代码编译报错

-w646

第一段代码编译通过,是因为两个结构体的访问级别都是该文件内,所以访问级别相同

第二段代码报错是因为Dog里的属性和方法的访问级别是更低的了,虽然两个结构体的访问级别相同,但从Person里调用Dog中的属性和方法是访问不到的

结论:直接在全局作用域下定义的private等于fileprivate

成员的重写

子类重写成员的访问级别必须 ≥ 子类的访问级别,或者 ≥ 父类被重写成员的访问级别

class Person {
    internal func run() {}
}

fileprivate class Student: Person {
    fileprivate override func run() {}
}

父类的成员不能被成员作用域外定义的子类重写

-w641

放到同一个作用域下

public class Person {
    private var age: Int = 0
    
    public class Student: Person {
        override var age: Int {
            set {}
            get { 10 }
        }
    }
}

getter、setter

getter、setter默认自动接收它们所属环境的访问级别

可以给setter单独设置一个比getter更低的访问级别,用以限制写的权限

fileprivate(set) public var num = 10
num = 10
print(num)

-w645

初始化器

如果一个public类想在另一个模块调用编译生成的默认无参初始化器,必须显式提供public的无参初始化器,因为public类的默认初始化器是internal级别

public class Person {
    // 默认生成的,因为是internal,所以外部无法调用到该初始化器
//    internal init() {
//
//    }
}

变成这样

public class Person {
    // 自己手动添加指定初始化器,并用public修饰,外部才能访问的到
    public init() {

    }
}

required初始化器 ≥ 它的默认访问级别

fileprivate class Person {
    internal required init() {}
}

当类是public的时候,它的默认初始化器就是internal级别的,所以不会报错

public class Person {
    internal required init() {}
}

-w639

如果结构体有private\fileprivate的存储实例属性,那么它的成员初始化器也是private\fileprivate,否则默认就是internal

-w641

结构体里有一个属性设置为private,带有其他属性的初始化器也没有了

-w642

枚举类型的case

不能给enum的每个case单独设置访问级别

-w641

每个case自动接收enum的访问级别

fileprivate enum Season {
    case spring // fileprivate
    case summer // fileprivate
    case autumn // fileprivate
    case winter // fileprivate
}

public enum定义的case也是public

public enum Season {
    case spring // public
    case summer // public
    case autumn // public
    case winter // public
}

协议

协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别

-w637

public协议定义的要求也是public

public protocol Runnable {
    func run()
}

协议实现的访问级别必须 ≥ 类型的访问级别,或者 ≥ 协议的访问级别

-w641 -w640

扩展

如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别

class Person {

}

private extension Person {
    func run() {} // private
}

如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别,跟直接在类型中定义的成员一样

private class Person {
    
}

extension Person {
    func run() {} // private
}

可以单独给扩展添加的成员设置访问级别

class Person {
    
}

extension Person {
    private func run() {} 
}

不能给用于遵守协议的扩展显式设置扩展的访问级别

-w645

在同一文件中的扩展,可以写成类似多个部分的类型声明

在原本的声明中声明一个私有成员,可以在同一个文件的扩展中访问它

在扩展中声明一个私有成员,可以在同一文件的其他扩展中、原本声明中访问它

public class Person {
    private func run0() {}
    private func eat0() {
        run1()
    }
}

extension Person {
    private func run1() {}
    private func eat1() {
        run0()
    }
}

extension Person {
    private func eat2() {
        run1()
    }
}

将方法赋值给var\let

方法也可以像函数那样,赋值给一个let或者var

struct Person {
    var age: Int
    func run(_ v : Int) { print("func run", age, v)}
    static func run(_ v: Int) { print("static func run", v)}
}

let fn1 = Person.run
fn1(10) // static func run 10

let fn2: (Int) -> () = Person.run
fn2(20) // static func run 20

let fn3: (Person) -> ((Int) -> ()) = Person.run
fn3(Person(age: 18))(30) // func run 18 30