可选项是Swift
的一大特色,那么可选项的本质是什么呢?
比如下面代码:
我们从打印结果可以看到,age
是Optional
类型,其实可选项本质就是Optional
类型.?
只是语法糖的一种写法.我们看看Optional
类型的本质是什么:
可以看到Optional
的本质就是一个枚举类型,有两个成员值和一个初始化方法.既然知道可选项的本质是枚举类型,那上面的代码可以这样写:
还可以简化:
可选项类型绑定
我么知道可选项和if
组合使用可以使用可选项绑定来判断可选项中是否有值:
可选项和if
组合时,如果可选项包装有值,那么就进行解包,并把值赋值给a
.
其实可选项还可以和switch
语句组合,也可以使用可选项绑定:
可选项和switch
组合时有两个警告,我们先解决第二个警告:Case is already handled by previous patterns; consider removing it
.这个警告的意思是说,上一个case
已经包含了所有情况,当前的case
永远也不会执行.其实上面代码的可选项绑定时无条件绑定
.不管可选项有没有值都会赋值给a
,所以下面代码就永远也不会执行.那么我们要想实现和if
那种效果,有值的时候解包再赋值,没有值的时候不解包呢?只需要在a
后面添加一个问号 ?
就可以了:
这样一来,第一个警告也消除了.第一个警告就是直接打印可选项的时候的警告.现在使用?
如果可选项有值就会解包,所以警告消除.
双重可选项
既然知道了可选项的本质就是枚举类型:有值是some
,nil
是none
.那么双重可选项也是一样的:
高级运算符
溢出运算符 &+,&-,&*
我们知道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
的==
运算符默认不支持结构体类型,所以需要我们重载==
运算符.
重载==
运算符有两步:
- 实现
Equatable
协议 - 重载
==
运算符
上图可以看到,我们把==
运算符的重载方法注释了,也就是说并没有重载==
运算符.为什么也可以使用==
运算符呢?
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
和可选链中的赋值运算符一样:
另外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
方法.所以他就会优先从协议中找这个方法.
扩展中的泛型:
扩展中依然可以使用原类型中的泛型:
符合条件后才扩展:
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() {
}
}