从零学习Swift 12:可选项本质,高级运算符, Extension 扩展

总结

可选项是Swift的一大特色,那么可选项的本质是什么呢?

比如下面代码:

可选项

我们从打印结果可以看到,ageOptional类型,其实可选项本质就是Optional类型.?只是语法糖的一种写法.我们看看Optional类型的本质是什么:

Optional 本质

可以看到Optional的本质就是一个枚举类型,有两个成员值和一个初始化方法.既然知道可选项的本质是枚举类型,那上面的代码可以这样写:

还可以简化:

简化写法
可选项类型绑定

我么知道可选项和if组合使用可以使用可选项绑定来判断可选项中是否有值:

可选项绑定

可选项和if组合时,如果可选项包装有值,那么就进行解包,并把值赋值给a.

其实可选项还可以和switch语句组合,也可以使用可选项绑定:

可选项和 switch 组合

可选项和switch组合时有两个警告,我们先解决第二个警告:Case is already handled by previous patterns; consider removing it.这个警告的意思是说,上一个case已经包含了所有情况,当前的case永远也不会执行.其实上面代码的可选项绑定时无条件绑定.不管可选项有没有值都会赋值给a,所以下面代码就永远也不会执行.那么我们要想实现和if那种效果,有值的时候解包再赋值,没有值的时候不解包呢?只需要在a后面添加一个问号 ?就可以了:

这样一来,第一个警告也消除了.第一个警告就是直接打印可选项的时候的警告.现在使用?如果可选项有值就会解包,所以警告消除.

双重可选项

既然知道了可选项的本质就是枚举类型:有值是some,nilnone.那么双重可选项也是一样的:

双重可选项
高级运算符
溢出运算符 &+,&-,&*

我们知道UInt8的取值范围是0~255,Int8的取值范围是-128~127.那么如果经过运算后的结果超出了这个范围会怎么样呢?

溢出会发生运行时错误

可以看到,如果溢出后会产生运行时错误.所以Swift提供了溢出运算符&+,&-,&*

溢出运算符

溢出运算符的运算是循环的,比如说age的值是255, 加 1后就变成了0.

运算符重载

如果我们想为系统原有的运算符添加额外的功能,可以对原有的运算符进行重载.重载就是函数名相同,参数不同.功能不同.

系统的运算符的本质都是方法:

+

比如说+只能对基本数据类型进行加法运算,如果我们要对结构体类型进行运算加法运算呢?

对结构体进行加法运算

可以看到结构体是不支持 + 运算的.

所以我们只能对+进行重载,达到我们的目的:


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

func + (lhs: Point , rhs: Point) -> Point{
    Point(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}

var p1 = Point(x: 10, y: 20)
var p2 = Point(x: 11, y: 21)
var p3 = p1 + p2
print(p3)

因为我们是给 Point 提供的额外的运算功能,所以我们应该把重载的方法放到 Point 结构体里面:


struct Point{
    var x: Int
    var y: Int
    
    static func + (lhs: Point , rhs: Point) -> Point{
        Point(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
    }
}


重载+ , - ,+= , -= , 取反, ++ , --


 // +
    static func + (lhs: Point , rhs: Point) -> Point{
        Point(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
    }
    
    // -
    static func - (lhs: Point , rhs: Point) -> Point{
        Point(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
    }
    
    //取反
    static prefix func - (lhs: inout Point){
        lhs = Point(x: -lhs.x, y: -lhs.y)
    }
    
    // +=
    static func += (lhs: inout Point , rhs: Point){
        lhs = lhs + rhs
    }
    
    
    // -=
    static func -= (lhs: inout Point , rhs: Point){
        lhs = lhs - rhs
    }
    
    
    // ++ 在前
    static prefix func ++ (lhs: inout Point){
        //先运算
        lhs += Point(x: 1, y: 1)
    }
    
    // ++ 在后
    static postfix func ++ (lhs: inout Point) -> Point{
        //先保存原来的
        let p = lhs
        lhs += Point(x: 1, y: 1)
        return p
    }

==运算符

如果想要比较两个Point对象是否相等可以使用==运算符,但是Swift==运算符默认不支持结构体类型,所以需要我们重载==运算符.

== 运算符默认不支持结构体

重载==运算符有两步:

  1. 实现 Equatable 协议
  2. 重载 == 运算符
重载 == 运算符

上图可以看到,我们把==运算符的重载方法注释了,也就是说并没有重载==运算符.为什么也可以使用==运算符呢?

Swift会为一下三种情况提供默认的Equatable实现:
1. 只拥有遵守了 Equatable 协议的存储属性的结构体
2. 只关联了遵守 Equatable 协议类型的枚举
3. 没有关联值的枚举

以上三种情况,Swift会默认提供Equatable协议的实现.Point的两个存储属性都是Int类型,而Int类型显然是遵守了Equatable协议的,所以不用重载==.

没有关联类型的枚举:


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

var season1 = Season.spring
var season2 = Season.winter

print(season1 == season2)

关联类型实现了Equatable协议:

enum Season: Equatable{
    case spring(Int,Int)
    case summer
    case autumn
    case winter
}

var season1 = Season.spring(10, 20)
var season2 = Season.winter

print(season1 == season2)

如果是class类型,必须实现Equatable协议,并且重载==方法:


class Person: Equatable{
    var age: Int
    var name: String
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
    
    static func == (p1: Person, p2: Person) -> Bool{
        p1.age == p2.age
    }
}

var person1 = Person(age: 16, name: "Jack")
var person2 = Person(age: 18, name: "Jorn")

print(person1 == person2)


上面Person类可以不实现Equatable协议,==方法同样可以正常使用,但是还是建议实现Equatable协议,因为这样做有两个好处:

1. 一眼就可以看出 Person 类是可比较的
2. 适用于泛型限定

比如说我们写一个比较传进来的泛型参数是否相等的方法:

可以看到编译报错,因为参数是泛型,传入的参数不一定是可比较的.所以必须对泛型添加限定条件,要求必须是实现了Equatale协议的类型.如果我们想让Person实例能适用这个方法,就必须让Person实现Equatale协议:

如果我们实现了Equatale协议,重载了==方法,等价于重载了!=运算符,我们可以直接使用!=运算符.

如果要比较两个引用类型引用的是不是同一个对象,要使用===运算符:


var person1 = Person(age: 16, name: "Jack")
var person2 = Person(age: 18, name: "Jorn")

print(person1 === person2) // false

==是判断两个对象是否相等,如果我们相比较大小呢?

如果要比较两个对象大小,要遵守Comparable协议,然后重载> , >= , < , <=运算符.

Comparable里面就声明了这四个方法:

自定义运算符

如果现有的运算符还是不能满足我们的需求,我们可以根据自己的需求,自定义运算符.

自定义运算符时需要注意两点:
1. 要指明是前缀运算符,后缀运算符,还是中缀运算符
2. 如果是中缀运算符,要设定优先级

下面我们分别自定义前缀运算符,后缀运算符,后缀运算符:


//优先级组
precedencegroup plus3Prece{
    //结合性
    associativity: none //left / right / none
    //比谁的优先级高
    higherThan: AdditionPrecedence
    //比谁的优先级低
    lowerThan: MultiplicationPrecedence
    //在可选链操作中,和赋值运算符一样的优先级
    assignment: true
}
//声明
prefix operator +++
postfix operator +++
infix operator +++ : plus3Prece

//前缀运算符
struct Point{
    var x: Int
    var y: Int
    
    //前缀+++
    static prefix func +++ (p: inout Point) -> Point{
        p = Point(x: p.x + 2, y: p.y + 2)
        return p
    }
    
    //后缀+++
    static postfix func +++ (p: inout Point) -> Point{
        let tempPoint = p
        p = Point(x: p.x + 2, y: p.y + 2)
        return tempPoint
    }
    
    //中缀
    static func +++ (p1: Point, p2: Point) -> Point{
        let p = Point(x: (p1.x + p2.x) * 2, y: (p1.y + p2.y) * 2)
        return p
    }
    
}


优先级组precedencegroup需要介绍一下:
associativity:组合型,left表示从左到右计算.right表示从右到左计算.none表示没有组合型,也就是说不能1 + 2 + 3这样运算.

higherThan:表示比哪个优先级高.

lowerThan:表示比哪个优先级低.

assignment:表示在可选链操作中和赋值运算符有着同样的优先级.

assignment:可能有些抽象,我们回忆一下可选链中的赋值运算符.

可选链中的赋值运算符

assignment和可选链中的赋值运算符一样:

assignment 作用

另外higherThan , lowerThan后面跟着运算符的名称,不是瞎写的.运算符的名称可以去先面这个网址去找:
https://developer.apple.com/documentation/swift/swift_standard_library/operator_declarations?changes=latest_minor

扩展 Extension

扩展类似于 OC 中的分类.扩展可以为类,结构体,枚举,协议添加新的功能.

使用扩展的注意点:
1. 不能覆盖原有的功能,OC的分类可以,swift扩展不可以
2. 不能添加存储属性,因为存储属性保存在实例内存中.扩展不能够改变能存结构
3. 不能通过扩展来添加父类,因为添加父类也有可能改变内存结构
4. 不能添加指定初始化器,重要的初始化器不能通过扩展添加.

Swift 的扩展功能很强大,它可以添加方法,计算属性,下标,便捷初始化器,嵌套类型,协议等等.

扩展计算属性:


//扩展计算属性
extension Double{
    var km: Double{ self / 1_000.0 }
    var m: Double{ self }
}

print(128.6.km)

用扩展下标:


//使用扩展添加下标

extension Array{
    subscript (nulable index: Int) -> Element?{
        if (startIndex ..< endIndex).contains(index){
            return self[index]
        }
        return nil
    }
}


扩展嵌套类型:


//扩展嵌套类型
extension Int{
    //循环n次
    func circles(toDo: () -> Void){
        for _ in 0..<self{
            toDo()
        }
    }
    
    //嵌套类型
    enum Kind{
        case positive,zero,negative
    }
    
    var kind: Kind{
        switch self{
        case 0:
            return .zero
        case let x where x > 0:
            return .positive
        default:
            return .negative
        }
    }
}

3.circles {
    print(2)
}

print((-2).kind)

扩展协议
如果一个类之前没有遵守某个协议,我们可以通过扩展,让他遵守协议:


class Person{}

protocol Runable {
    func run()
}

extension Person: Runable{
    func run() {
        print("run")
    }
}

var person = Person()
person.run()


如果协议中有初始化器方法,那么遵守了此协议的类必须把协议中的初始化器声明为required必要初始化器,这样做的目的是为了让子类都有这个初始化器:


protocol Runable {
    init(speed: Int)
}

class Person: Runable{
    required init(speed: Int) {
        print("run")
    }
}

注意: 协议中不能声明 required 初始化器

我们知道,如果我们自定义了初始化器,那么系统就不会再帮我们自动生成初始化器.有一种办法可以既保留系统生成的初始化器,也可以自定义初始化器.

扩展可以保留系统生成的初始化器:


struct Point {
    var x: Int = 0
    var y: Int = 0
}

extension Point{
    init(p: Point) {
        self.x = p.x
        self.y = p.y
    }
}

//系统生成的初始化器
var p1 = Point()
var p2 = Point(x: 10)
var p3 = Point(y: 10)
var p4 = Point(x: 10, y: 10)

//通过扩展自定义的初始化器
var p5 = Point(p: p4)

比如说现在有这样一个需求,给整数扩展一个方法,判断整数是不是偶数.

我们很快会想到可以这样做:


extension Int{
    func isEven () -> Bool{
        self % 2 == 0
    }
}

print(4.isEven())

Int类型扩展一个方法.但是这样做不完善,因为整数包括有符号整数和无符号整数.那我们怎样囊括所有整数呢?我们只要找到整数的共同点就好了.

swift中所有整数都遵守了BinaryInteger协议,所以我么可以直接给BinaryInteger协议扩展一个方法就好了.

给一个协议扩展方法,凡是遵守了这个协议的类型都有这个方法:


extension BinaryInteger{
    func isEven () -> Bool{
        self % 2 == 0
    }
}

print(4.isEven())

var uNum: UInt = 10
uNum.isEven()

我们知道协议中所有的东西都必须实现,如果实现的不完整就会编译不通过:

协议中的声明必须全部实现

有时候我们可能只想实现协议中的某些方法,可不可以做到呢?

可以使用扩展给协议中的方法提供默认实现来间接实现协议的可选效果:

在扩展中添加协议的默认实现,达到间接可选效果

我们还可以通过扩展给协议添加协议中没有的方法:

通过扩展给协议添加方法

注意一个小细节,如果我们创建Person实例的时候,指明是遵守了Runable协议的类型,会怎么样呢?

为什么会这样呢?

因为我们指明了类型是遵守了 Runable 协议.而Runable协议中并没有test2方法的声明.所以Xcode会认为遵守了此协议的类中可能没有test2方法.所以他就会优先从协议中找这个方法.

扩展中的泛型:

扩展中依然可以使用原类型中的泛型:

Array 中的泛型
扩展中可以使用原类型中的泛型

符合条件后才扩展:


protocol Runable {
    func test1()
}

class Person<C>{
    var pet: C
    init(p: C) {
        self.pet = p
    }
}

//只有当 Person 中的泛型 C 遵守了 Runable 协议后
//才会给 Person 扩展协议
extension Person: Runable where C: Runable{
    func test1() {
        
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353