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
加强点:初始化、继承、协议、扩展





![[牛客网]——C语言刷题day3](https://img-blog.csdnimg.cn/direct/9c77aa01fd304b11a0e1020f76cdc9b2.png)













