11. init、deinit、可选链、协议、元类型
构造和析构
构造方法
构造方法是一种特殊的方法
一个对象创建完毕后,都需要调用构造方法进行初始化(比如属性的初始化)
验证:init方法是在对象创建完毕的时候调用
回到存储属性
在对象创建的时候,存储属性必须有初始值
两种设置初始值的方法:
在定义属性时为其设置默认值
在构造方法中为属性赋初值
不管在哪赋初值,都是为了保证:对象创建后,存储属性有值
除了没有参数的构造方法,还有自定义的有参数构造方法
构造方法类型
构造方法可以分为两类:
指定构造方法(Designated Constructor)
便利构造方法(Convenience Constructor)
被convenicence修饰的构造方法,被称为便利构造方法
- 默认情况下,每一个类都有一个隐私的无参的指定构造方法:
init(){}
- 如果一个类定义了一个有参的指定构造方法,就不会自动生成无参的指定构造方法
- 指定构造方法必须调用其直接父类的指定构造方法(除非没有父类)
- 便利构造方法必须调用同一类中定义的其他构造方法(不一定是指定构造)
- 便利构造方法最终必须以调用一个指定构造方法结束
只有便利构造方法才能直接调用当前类中的其他构造方法
也就是说,指定构造方法在当前类中不能直接调用其他指定构造方法
只有指定构造方法才能直接调用其父类的构造方法
也就是说,便利构造方法不能直接调用其父类的构造方法
- 如果父类中只有一个指定构造方法且是无参的,那么,子类的指定构造方法默认会自动调用父类中无参的指定构造方法
- 如果父类中存在有参的指定构造方法,那么,子类的指定构造方法不会自动调用父类的无参的指定构造方法
- 常量只能在父类中初始化
自动继承
- 如果子类没有自定义任何指定初始化器,它会自动继承父类所有的指定初始化器
required
- 用required修饰指定初始化器,表明其所有子类都必须实现该初始化器(通过继承或重写实现)
- 如果子类重写了required初始化器,也必须加上required,不用加override
可失败初始化器
类、结构体、枚举都可以使用init?
定义可失败初始化器
就是,通过这种初始化器初始化的对象,可能是nil,也就是初始化完后的对象,是可选项对象
反初始化器(deinit)
deinit叫做反初始化器,类似C++的析构函数、OC中的dealloc方法
当类的实例对象被释放内存时,就会调用实例对象的deinit方法
deinit{}
- deinit不接受任何参数,不能写小括号,不能自行调用
- 父类的deinit能被子类继承
- 子类的deinit实现执行完后,会调用父类的deinit(先子后父)
可选链(Optional Chaining)
- 如果可选项为nil,调用方法、下标、属性失败,结果为nil
- 如果可选项不为nil,调用方法、下标、属性成功,结果会被包装成可选项
- 结果本来就是可选项的,不会进行再次包装
协议(Protocol)
协议可以用来定义方法、属性、下标的声明,协议可以被枚举、结构体、类遵守(多个协议之间用逗号隔开)
一个简单的协议:
protocol Drawable {
//方法
func draw()
//属性
var x: Int {get set}//可读可写
var y: Int {get}//只读
//下标
subscript(index: Int) -> Int {get set}//可读可写
}
- 协议中定义方法时,不能有默认参数值
- 默认情况下,协议中定义的内容必须全部实现(也有办法做到只实现一部分协议)
协议中的属性
- 协议中定义属性时,
必须
使用var关键字
如果是只读属性,在实现的时候,可能返回的是函数,所以返回值是不确定的)
- 实现协议时的属性权限 >= 协议定义的属性权限
- 协议定义get、set,用var存储属性或者get、set计算属性去实现
- 协议定义get,用任何属性都可以实现
协议中static、class使用
为了保证通用,协议中必须使用static定义类型方法、类型属性、类型下标
class关键字只能用在类里面,而结构体也可以遵守协议,结构体没有class,所以,
涉及到类的,协议中只能使用static
在协议声明的时候,声明类型的时候,必须用static,然而,在实现类型的时候,可以用class也可以用static
区别是:
class允许被子类重写;
static不允许被子类重写
protocol Drawable {
//声明类方法
static func draw()
}
class Size: Drawable {
//使用class修饰类方法
class func draw() {}
}
class Size1: Size {
//使用class修饰类方法,允许子类重写
override class func draw() {}
}
class Point: Drawable {
//使用static修饰类方法
static func draw() {
print("Point-draw")
}
}
class Point1: Point {
//使用static修饰类方法,不允许子类重写
//Method does not override any method from its superclass
//override func draw() {}
}
协议中mutating
只有将协议中的实例方法标记为mutating
- 才允许结构体、枚举的具体实现修改自身内存
- 类在实现方法时不用加mutating,枚举、结构体才需要加mutating
协议中init
协议中还可以定义初始化器init
非final类实现时,必须加上required
目的是强制要求遵守协议的类,必须实现init方法
protocol Drawable {
init(x: Int, y: Int)
}
class Point: Drawable {
//必须加上required,不然报错
required init(x: Int, y: Int) {}
}
//使用final修饰,子类不能继承,所以自己实现就可以,不需要加required
final class Size: Drawable {
init(x: Int, y: Int) {}
}
struct struct1: Drawable{
//struct没有继承,也就不需要加required
init(x: Int, y: Int) {}
}
协议可以继承(遵守)
一个协议,可以继承(遵守)其他协议
protocol Drawable {
func fun1()
}
protocol Drawable2: Drawable
{
func fun2()
}
class person: Drawable2{
func fun1() {
}
func fun2() {
}
}
协议组合
CaseIterable协议
Iterable: 可迭代对象,可迭代的
让枚举遵守CaseIterable协议,可以实现遍历枚举值
//创建一个枚举,遵守CaseIterable协议
enum Season: CaseIterable{
case spring, summer, autumn, winter
}
//就可以使用CaseIterable里面的allCases方法,获取所有的元素,组成一个数组
let seasons = Season.allCases
print(seasons);
//打印:[swiftTest.Season.spring, swiftTest.Season.summer, swiftTest.Season.autumn, swiftTest.Season.winter]
CustomStringConvertible协议
convertible:可以转换的, 可转化的
遵守CustomStringConvertible协议,可以自定义实例的打印
字符串
//遵守CustomStringConvertible协议
class Person: CustomStringConvertible{
var age: Int
var weight: Int
init(age: Int, weight: Int) {
self.age = age
self.weight = weight
}
//实现协议方法:你想怎样自定义打印
var description: String{
"age = \(age), weight = \(weight)"
}
}
var p1 = Person(age: 11, weight: 80)
print(p1)//age = 11, weight = 80
其他关键字:
mutating
默认情况下,结构体、枚举,不允许内部方法修改其实例属性值:
添加mutating后,不报错:
struct Point {
var x = 0.0
var y = 0.0
mutating func moveBy(deltaX: Double, deltaY: Double){
x += deltaX
y += deltaY
}
}
类里面,在实现方法时,可以直接修改存储属性,不需要加mutating
@discardableResult
在func前加上@discardableResult
,可以消除:函数有返回值,但没有使用的警告⚠️
Any、AnyObject
Swift提供了2种特殊类型:Any、AnyObject
- Any:可以代表任意类型(枚举、结构体、类,也包括函数类型)
- AnyObject:可以代表任何
类
类型(在协议后面写上:AnyObject
代表只有类能遵守这个协议)
is、as?、as!、as
is
是用来判断是否为某种类型
as
用来做强制类型转换
is
后面可以放本类
,也可以放父类
,也可以放遵守的协议
,都会返回true
as?
一般用于类型转换,转换后可能为nil,因此用as?,转换后是一个可选类型
as!
带有强制解包的意思,转换后不是可选类型,但转换失败就会崩溃,因此,慎用
X.self、X.Type、AnyClass
此处X,指的是类(非结构体),且是类对象(非对象)
X.self
是一个元类型(metadata
)的指针,metadata
存放着类型相关的信息
Person.self
,即元类型指针,里面存放着元类型的地址,即person对象的前八个字节的地址
X.self
属于X.Type
类型
//p是Person类型
var p: Person = Person(age: 11, weight: 80)
//pType是Persin.Type类型,即X.Type类型
var pType: Person.Type = Person.self
public typealise AnyClass = Anyobject.Type
即,AnyClass是一个X.Type类型,是一个存储元类地址的指针类型
Self
Self
一般用作返回值类型,限定返回值跟方法调用者必须是同一类型
Self
也可以作为参数类型
class Person{
var age = 1
static var count = 2
func run(){
//self是对象
print(self.age)//1
//Self代表类对象
print(Self.count);//2
}
}
let p = Person()
p.run()
12. Error处理、泛型
错误处理
错误类型
开发过程中常见的错误类型:
- 语法错误(编译报错)
- 逻辑错误
- 运行时错误(可能会导致闪退,一般也叫做异常)
处理Error的两种方式:
- 使用do-try-catch
do {
try 可能错误的代码
}catch {
}
- 不捕捉Error,在当前函数前增加throws声明,Error将自动抛给上层函数
如果上层也没用处理,则还是崩溃,因此,少用或者用的严谨些
try?、try!
可以使用try?、try!调用可能会抛出Error的函数,这样就不用去处理Error
try?,如果调用不成功,则结果是nil;如果调用成功,则会包装一下成Option
try!,如果调用成功,则不会包装
defer
defer语句:用来定义以任何方式(抛错误、return等)离开代码块前必须要执行的代码
也就是,错误发生了,也得执行defer
defer语句将延迟至当前作用域结束前执行
fun funcName(参数) throws {
//正常代码
print("123")
defer{
//必须执行的代码,且在}结束的时候才执行
}
//可能会出错的代码
try xxx
}
defer语句的执行顺序与定义顺序相反
assert(断言)
不符合指定条件就抛出运行时错误,常用于调试(debug)阶段的条件判断
默认情况下,Swift的断言只会在Debug模式下生效,Release模式下会忽略
do-catch一般用于编写正式代码,万一有错,去处理错误,不至于崩溃
assert是在编译开发阶段,有错误就崩溃,不进行处理
-assert-config Release
强制关闭断言
-assert-config Debug
强制开启断言
fatalError
如果遇到严重问题,希望结束程序运行时,可以直接使用fatalError函数抛出错误
泛型(Generics)
泛型可以将类型参数化,提高代码复用率,减少代码量
可以在函数名后面加上<T>
,T代表任意类型
协议中如何使用泛型?
协议中,不能直接使用<T>
而是使用:
关联类型(Associated Type)
//定义一个栈,是一个协议
protocol Stackable{
//使用关联对象,定义Element
associatedtype Element
//push一个元素
mutating func push(_ element: Element)
//出栈一个元素
mutating func pop() -> Element
//获取顶部元素
func top() -> Element
//获取栈还有几个元素
func size() -> Int
}
关联类型的作用:给协议中用到的类型定义一个占位名称
协议中可以拥有多个关联类型
类型约束
泛型是可以被约束的:<T: Person & Runnable>
//定义一个协议
protocol Runnable {}
//定义一个类
class Person{}
//函数里面的参数是一个泛型,且泛型是Person类或其子类,其遵守Runnable协议
func swapValues<T: Person & Runnable>(_ a: inout T, _ b: inout T){
(a, b) = (b, a)
}
13. String、Array底层
14. 可选项本质、运算符重载、扩展
//定义一个可选项
var age: Int? = 10
switch age {
case let v://此处不会解包
print("1", v)
case nil:
print("2")
}
打印:
1 Optional(10)
v是一个可选项,也就是在switch-case中,并不会像if let那样,解包操作
switch age {
case let v?://v后面加一个?
print("1", v)
case nil:
print("2")
}
打印:
1 10
age如果是非nil,则赋值给v
age如果是nil,则走case nil
高级运算符
溢出运算符(Overflow Operator)
swift的算数运算符出现溢出的时候,会抛出运行时错误
print(Int8.min)
print(Int8.max)
print(UInt8.min)
print(UInt8.max)
打印:
-128
127
0
255
在知道UInt8的取值范围后,做超出值的赋值操作:
var a: UInt8 = UInt8.max
a += 1
此时,会报错:Thread 1: Swift runtime failure: arithmetic overflow
- swift有溢出运算符( &+、&-、&*),用来支持运算
var a1: UInt8 = UInt8.max
var a2 = a1 &+ 1
print(a1, a2)//255, 0
这三个运算符,是一个循环
也就是255后面没有值了,再加1,就回到起点,成了0
其他两个减和乘类似
运算符重载(Operator Overload)
类、结构体、枚举可以为现有的运算符提供自定义的实现,这个操作叫做:运算符重载
比如,a + b = 10
可以做到p(1, 2) + p(2, 5) = (3, 7)
只需要做一个函数名为+
的函数即可
Equatable
想要得知2个实例是否等价,一般做法是遵守Equatable
协议,重载 ==
运算符
判断 基本数据类型,可以直接比较,而对于实例(对象),直接用==
会报错
class Person{
var age: Int
var weight: Int
init(age: Int, weight: Int) {
self.age = age
self.weight = weight
}
}
var p1 = Person(age: 10, weight: 80)
var p2 = Person(age: 10, weight: 90)
print(p1 == p2)//Binary operator '==' cannot be applied to two 'Person' operands
此时,我们约定,只要age相等,则就代表两个对象相等
这时,就可以用到上面提到的Equatable
class Person: Equatable{
var age: Int
var weight: Int
init(age: Int, weight: Int) {
self.age = age
self.weight = weight
}
//协议方法
//其实,这个就是运算符重载,使用了一个函数名为 == 的函数
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.age == rhs.age
}
}
var p1 = Person(age: 10, weight: 80)
var p2 = Person(age: 10, weight: 90)
print(p1 == p2)//true
- 引用类型比较存储的地址值是否相等(是否引用着同一个对象),使用
===
、!==
符号
===
只适用于引用类型
Comparable
想比较两个实例的大小,一般可以:
- 遵守Comparable协议
- 重载相应的运算符
//遵守Comparable协议
class Person: Comparable{
var age: Int
var weight: Int
init(age: Int, weight: Int) {
self.age = age
self.weight = weight
}
//重载相应的运算符
static func < (lhs: Person, rhs: Person) -> Bool {
lhs.age < rhs.age
}
//重载相应的运算符
static func == (lhs: Person, rhs: Person) -> Bool {
lhs.age == rhs.age
}
}
var p1 = Person(age: 11, weight: 80)
var p2 = Person(age: 10, weight: 90)
print(p1 > p2)//true
自定义运算符(Custom Operator)
可以自定义新的运算符:在全局作用域使用operator进行声明
比如:
prefix operator 前缀运算符
prefix operator +++
就是定义了一个自定义运算符+++,实现前缀运算
扩展(Extension)
Swift中的扩展,类似于OC中的分类
扩展可以为:枚举、结构体、类、协议添加新功能
可以添加:方法、计算属性、下标、(便捷)初始化器、嵌套类型、协议等
扩展不能做的事:
- 不能覆盖原有的功能
- 不能添加存储属性,不能向已有的属性添加属性观察器
- 不能添加父类
- 不能添加指定初始化器,不能添加反初始化器
- …
主要是,不能改变原来的结构
存储属性相当于添加新的属性,影响了原来的结构
父类,如果对父类扩展,就是继承父类,那么父类里面的东西就被扩展拿到,影响了原来的结构
如果希望自定义初始化器的同时,编译器也能够生成默认初始化器
可以在扩展中编写自定义初始化器
required
初始化器不能写在扩展中
也就是,扩展对象遵守的协议,在扩展中不能使用
required
初始化器
如果一个类,已经实现了某个协议的方法,但是没有声明它遵守该协议
则,可以在扩展中遵守该协议
扩展可以给协议提供默认实现,也即间接实现:可选协议
的效果
一般协议,都要求必须实现,使用扩展,在扩展中写上某些不用的方法,就间接实现了可选协议的效果
15. 访问控制、内存管理
访问控制(Access Control)
Swift提供了5个不同的访问级别,从高到低有:
open
:允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写(open只能用在类、类成员上)public
:允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写internal
:只允许定义实体的模块中访问,不允许在其他模块中访问(绝大部分模式是internal
级别)fileprivate
:只允许在定义实体的源文件中访问private
:只允许在定义实体的封闭声明中访问
模块:大致就是多个target,或者多个动态库,不同框架
源文件:就是.swift文件
open、public都可以在其他库里面直接使用,区别是public不能继承、重写
internal只能在当前库使用,其他库不允许使用
fileprivate只有当前文件.swift里面使用
private,只有自己的作用区域使用
访问级别的使用准则
一个实体,不可以被更低访问级别的实体定义
元组
元组类型的访问级别,是所有成员类型最低的那个
比如,有一个元组(a, b)a的访问级别是public,b的访问级别是fileprivate
那么,该元组类型的访问级别是fileprivate
成员、嵌套类型
类型的访问级别会影响成员(属性、方法、初始化器、下标)、嵌套类型的默认访问级别
一般情况下,类型为private
或fileprivate
,那么成员\嵌套类型默认也是private
或fileprivate
一般情况下,类型为internal
或public
,那么成员\嵌套类型默认也是internal
直接在全局作用域下定义private
,等价于fileprivate
初始化器
如果一个public
类想在另外一个模块调用编译生成的默认无参初始化器,必须显式提供public
的无参初始化器
因为public
类的默认初始化器是internal
级别的
required
初始化器 >= 它的默认访问级别
如果结构体有private
\ fileprivate
的存储实例属性,那么它的成员初始化器也是private
\ fileprivate
否则默认就是internal
枚举类型的case
不能给enum的每个case单独设置访问级别
每个case自动接收enum的访问级别
public enum定义的case也是public
协议
协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别
即,协议的访问级别,不能在协议里面定义,而是在协议定义的时候写
扩展
如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别
如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别,跟直接在类型中定义的成员一样
可以单独给扩展添加的成员设置访问级别
不能给用于遵守协议的扩展显式设置扩展的访问级别
内存管理
跟OC一样,Swift也是采取基于引用计数的ARC内存管理方案(针对堆空间)
Swift的ARC有3种引用
- 强引用:默认情况下,引用都是强引用,使用
strong
- 弱引用:通过
weak
定义弱引用
必须是可选类型的var,因为实例销毁后,ARC会自动将弱引用设置为nil(会改变var,可以为nil,所以是可选类型)
ARC自动给弱引用设置nil时,不会触发属性观察器 - 无主引用:通过
unowned
定义无主引用
不会产生强引用,实例销毁后仍然存储着实例的内存地址(类似于OC的unsafe_unretained
)
试图在实例销毁后访问无主引用,会产生运行时错误(野指针)
闭包的循环引用
闭包表达式默认会对用到的外层对象产生额外的强引用(对外层对象进行了retain操作)
在闭包表达式的捕获列表声明weak或unowned引用,解决循环引用问题
闭包里面如果用到了self,必须在闭包前加上lazy
因为在实例初始化完毕之后,才能引用self
加强点:初始化、继承、协议、扩展