protocol协议
- 协议可以用来定义方法,下标,属性的声明;
- 协议可以被类,结构体,枚举遵守,多个协议之间用逗号隔开;
- 协议中定义方法时,不能有默认参数值;
- 默认情况下,协议中定义的内容必须全部实现;
- 协议中定义属性时必须使用
var
关键字;
- 实现协议时的属性权限不小于协议中定义的属性权限;
- 协议中定义get,set,可用var存储属性或者get,set计算属性去实现;
- 协议中定义get,任何属性都可以实现;
import Foundation
protocol Drawable {
func draw() -> Void
var x: Int { get set }
var y: Int { get }
subscript(index: Int) -> Int { get set }
}
mutating
- 只有将协议中的实例方法标记为
mutating
,才允许结构体,枚举的具体实现能修改自身内存,类在实现方法时不用加mutating
,结构体和枚举才需要加mutating
;
协议中定义init初始化器
- 协议中还可以定义初始化器;
- 非
final
类实现时必须加上required
;
protocol Drawable1 {
init(x: Int,y: Int)
}
class Point : Drawable1 {
required init(x: Int, y: Int) {
}
}
final class Size : Drawable1{
init(x: Int, y: Int) {
}
}
- 如果从协议中实现的初始化器,刚好是重写了父类指定的初始化器,那么这个初始化必须同时加上
required
与override
关键字;
protocol Livable {
init(age: Int)
}
class Person {
init(age: Int) {
}
}
class Student : Person,Livable {
required override init(age: Int) {
super.init(age: age)
}
}
init,init?,init!
- 协议中定义的init?,init!,可以用init,init?,init!去实现;
- 协议中定义的init,可以用init,init!去实现;
protocol Livable {
init()
init?(age: Int)
init!(no: Int)
}
class Person : Livable {
required init() {} //可二选一
required init!() {}
required init?(age: Int) {} //可三选一
required init!(age: Int) {}
required init(age: Int) {}
required init!(no: Int) {} //可三选一
required init?(no: Int) {}
required init(no: Int) {}
}
协议的继承
protocol Runable {
func run()
}
protocol Livable : Runable{
func sleep()
}
class Person : Livable {
func run() {
}
func sleep() {
}
}
协议的组合
protocol Livable {}
protocol Runable {}
class Person {}
//参数:接收Person或其子类的实例
func fn0(obj: Person) {}
//参数:接收遵守Livable协议的实例
func fn1(obj: Livable) {}
//参数:接收同时遵守Livable与Runable协议的实例
func fn2(obj: Livable & Runable) {}
//参数:接收同时遵守Livable与Runable协议的实例且是Person或其子类的实例
func fn3(obj: Person & Livable & Runable) {}
typealias RealPerson = Person & Livable & Runable
func fn4(obj: RealPerson) {}
CaseIterable协议
- 让枚举遵守CaseIterable协议,可以实现遍历枚举值;
enum Season : CaseIterable {
case spring,summer,autum,winter
}
//Season.allCases协议属性
let seasons = Season.allCases
print(seasons.count) //4
for season in seasons {
print(season)
}
CustomStringConvertible协议
- 遵守CustomStringConvertible协议,可以自定义打印字符串;
class Person : CustomStringConvertible {
var age: Int
var name: String
init(age: Int,name: String) {
self.age = age
self.name = name
}
//实现协议方法
var description: String {
"age = \(age),name = \(name)"
}
}
var person = Person(age: 31, name: "liyanyan")
print(person) //age = 31,name = liyanyan
Any 与 AnyObject
- 在Swift中提供了两种特殊的类型:Any 与 AnyObject
-
Any
:代表任意类型(枚举,结构体,类,函数类型)
-
AnyObject
:代表任意类(class)类型;
- 在协议后面加上
: AnyObject
,表示此协议只有类class才能遵守实现;
is,as,as?,as!
- is:用来判断是否为某种类型;
- as:用来做强制类型转换;
protocol Runnable {
func run()
}
class Person {
}
class Student : Person, Runnable{
func run() {
print("Student run")
}
func study() {
print("Student study")
}
}
//var stu: Any = 10
//print(stu is Int) //true
//stu = "li"
//print(stu is String) //true
//stu = Student()
//print(stu is Person) //true
//print(stu is Student)//true
//print(stu is Runnable) //true
var stu: Any = 10
//第一个问号:表示强制转换可能失败
//第二个问号:可选链
(stu as? Student)?.study() //no
stu = Student()
(stu as? Student)?.study() //yes
(stu as! Student).study() //yes
(stu as? Student)?.run() //yes
//数组 存放任意类型
var data = [Any]()
data.append(Int("123") as Any)
-
as!
:确保能强转成功,由于是强制类型转换,如果转换失败会报 runtime 运行错误;
-
as?
:不能确保能否强转成功,所以返回的是可选类型;
.self,.Type,AnyClass,type(of: T)
- 我们知道单纯的数值5是Int类型,此时数值5是Int类型的一个值,Int类型是对具体数值5的一个抽象,可以这么说Int类型是抽象,数值5是具象,数值5占用多少内存空间,与数值5本身没有什么关系,这是由Int类型决定的,也就是说Int类型本身含有的信息数据决定了具体数值的内存分配,我们将描述Int类型的信息数据称之为元类型,简单而言
元类型是描述类型的类型,存放着类型的相关信息
,Int类型是描述具体整型数据的类型,存放着整型数据的信息;
- 根据Int类型与数值5的关系,即Int类型是抽象,数值5是具象,数值5是Int类型的具体表现值,那么根据元类型的定义可知,元类型是类型的类型,那么元类型是抽象,在Swift中利用
.Type
进行表示,元类型的具体表现值是具体的类型,在Swift中使用.self
进行表示;
- 创建C语言工程,测试代码如下:
import Foundation
let intType: Int.Type = Int.self
let floatType: Float.Type = Float.self
print(intType) //Int
print(floatType) //Float
-
Int.Type
是类型,是抽象,也就是所谓的元类型,Int.self
是数值,是具象,具体的类型;
- 元类型在有继承关系时,其数值不仅包含当前的类型,也包含当前类型的子类型,代码如下:
class YYAnimal {
}
class YYPerson: YYAnimal {
}
let type1: YYAnimal.Type = YYAnimal.self //success
let type2: YYAnimal.Type = YYPerson.self //success
let type3: YYPerson.Type = YYAnimal.self //报错
-
YYAnimal.Type
是抽象,是元类型,其数值包括YYAnimal类型,也包括YYAnimal类型的子类型YYPerson类型;
- 相反子类的元类型,其数值只能是子类类型以及子类的子类类型,不包括父类的类型;
class Person {
}
var person: Person = Person()
var pType: Person.Type = Person.self
- 当断点停在
var person: Person = Person()
所在代码行,汇编代码如下:
- 第一个
register read rax
是在call 0x100003df0
调用之后打印的,rax寄存器中存储的就是Person的类型信息;
- 第二个
register read rax
是在callq 0x100003e70
调用之后打印的,即实例化person对象,rax寄存器中存储的就是person实例对象的内存地址,即0x000000010071ead0
-
x/4gx 0x000000010071ead0
查看person实例对象内存地址中的内容,前8个字节正是类型信息(Person.Type)的内存地址;
-
AnyObject
:表示任意类类型;
-
AnyObject.Type
:任意任意类类型的元类型,在Swift中用关键字AnyClass
表示,即public typealias AnyClass = AnyObject.Type
;
-
type(of: 实例对象)
:返回的是实例对象所属类class的元类型,等价于实例对象所属类class.self
,例如type(of: person) = Person.self
Self
-
Self
:一般用作返回值类型,限定返回值跟方法调用者是同一类型;
protocol Runnable {
func test() -> Self
}
class Person : Runnable{
required init() {}
func test() -> Self {
//Person.init() 返回实例对象
type(of: self).init()
}
}
- 如果
Self
用在类中,要求返回时调用的初始化器是required
的;
-
Self
代表当前类型;
class Person {
var age: Int = 30
static var cout: Int = 50
func test() {
print(self.age)
print(Self.cout)
}
}
-
print(self.age)
:self代表当前的实例对象;
-
print(Self.cout)
:Self代表Person类类型
class Person {
static var age = 0
static func run() {}
}
Person.age = 10
Person.run()
Person.self.age = 10
Person.self.run()
var p0 = Person()
var p1 = Person.self()
var p2 = Person.init()
var p3 = Person.self.init()
//var pType0 = Person
var pType1: Person.Type = Person.self
错误处理
- 开发中的常见错误:
- 语法错误,编译报错;
- 逻辑错误;
- 运行时错误,可能会导致闪退,一般也叫做异常;
自定义错误
- 在Swift可以通过
Error协议
自定义运行时的错误信息;
- 可能会抛出Error的函数,必须加上
throws
声明;
- 函数内部通过
throw
抛出自定义Error;
- 需要使用
try
调用可能会抛出Error的函数,若抛出Error必须要处理,如果不处理会导致程序闪退;
- 可以使用
do -- catch
捕捉Error,进行Error处理;
enum MyError : Error {
case illegaArg(String)
case outOfBounds(Int,Int)
case outOfMemory
}
func divide(num1: Int,num2: Int) throws -> Int {
if num2 == 0 {
throw MyError.illegaArg("0不能作为除数")
}
return num1 / num2
}
func test() {
print("1")
do {
print("2")
print(try divide(num1: 200, num2: 0))//后面的代码停止执行
print("3")
} catch let MyError.illegaArg(msg) {
print("参数异常:",msg)
}catch let MyError.outOfBounds(size, index){
print("下标越界:","size = \(size)","index = \(index)")
}catch MyError.outOfMemory{
print("内存溢出")
}catch{
print("其他错我")
}
print("4")
}
test()
- 抛出Error后,
try
下一句直到作用域结束的代码都将停止执行,例如print(try divide(num1: 200, num2: 0))
后面的print("3")
不会执行;
Error的处理
- 处理Error通常有两种方式:
- 第一种:通过
do -- catch
捕捉Error,进行Error处理,上面已经演示过了;
- 第二种:不捕捉Error,在当前函数添加
throws
声明,Error将自动抛给上层函数,如果最顶层函数,依旧没有捕捉处理Error,应用程序会闪退;
enum MyError : Error {
case illegaArg(String)
case outOfBounds(Int,Int)
case outOfMemory
}
func divide(num1: Int,num2: Int) throws -> Int {
if num2 == 0 {
throw MyError.illegaArg("0不能作为除数")
}
return num1 / num2
}
func test() throws{
print("1")
print(try divide(num1: 200, num2: 0))
print("2")
}
try test() //会闪退
-
test()
,没有捕捉处理Error,而是抛给了上层函数main;
- main函数中没有处理Error,会报错;
enum MyError : Error {
case illegaArg(String)
case outOfBounds(Int,Int)
case outOfMemory
}
func divide(num1: Int,num2: Int) throws -> Int {
if num2 == 0 {
throw MyError.illegaArg("0不能作为除数")
}
return num1 / num2
}
func test0() throws{
print("1")
try test1()
print("2")
}
func test1() throws{
print("3")
try test2()
print("4")
}
func test2() throws{
print("5")
print(try divide(num1: 200, num2: 0))
print("6")
}
try test0() //会闪退
- test2中会出现Error,通过throws自动抛给上层test1,又由于throws,再次自动抛给上层test0,又由于throws,再次自动抛给上层main,main函数没有处理,则会闪退;
try?,try!
- 可以使用
try?,try!
调用可能抛出Error的函数,这样就不用去处理Error;
enum MyError : Error {
case illegaArg(String)
case outOfBounds(Int,Int)
case outOfMemory
}
func divide(num1: Int,num2: Int) throws -> Int {
if num2 == 0 {
throw MyError.illegaArg("0不能作为除数")
}
return num1 / num2
}
func test2() {
print("1")
var result1 = try? divide(num1: 200, num2: 10) //Optional(2)
var result2 = try? divide(num1: 200, num2: 0) //nil
var reslut3 = try! divide(num1: 200, num2: 2) //Int 100
print("2")
}
-
try? divide(num1: 200, num2: 0)
:会抛出错误,会返回nil,不会报错;
enum MyError : Error {
case illegaArg(String)
case outOfBounds(Int,Int)
case outOfMemory
}
func divide(num1: Int,num2: Int) throws -> Int {
if num2 == 0 {
throw MyError.illegaArg("0不能作为除数")
}
return num1 / num2
}
var a = try? divide(num1: 200, num2: 0)
//如果抛出异常,b的赋值操作不会执行 则默认为nil
//如果不抛出异常 b的赋值操作会执行
var b: Int?
do {
b = try divide(num1: 200, num2: 0)
}catch{
}
rethrows
-
rethrows
:表明函数本身不会抛出错误,但调用闭包参数抛出错误,那么它会将错误向上抛;
enum MyError : Error {
case illegaArg(String)
case outOfBounds(Int,Int)
case outOfMemory
}
func divide(num1: Int,num2: Int) throws -> Int {
if num2 == 0 {
throw MyError.illegaArg("0不能作为除数")
}
return num1 / num2
}
func exec(_ fn: (Int,Int) throws -> Int,_ num1: Int,_ num2: Int) rethrows{
print(try fn(num1,num2))
}
try exec(divide, 20, 0)
-
_ fn: (Int,Int) throws -> Int
:闭包参数函数可能会出现Error,所以用throws
进行声明,在其调用的时候用try
-
exec
函数本身不会出现Error,错误是由调用闭包导致的,所以用rethrows
进行声明,让外界调用者知道错误是谁抛出的;
defer
-
defer
语句:用来定义以任何方式,离开代码块前必须要执行的代码;
-
defer
语句将延迟至当前当前作用域结束之前执行;
enum MyError : Error {
case illegaArg(String)
case outOfBounds(Int,Int)
case outOfMemory
}
func divide(num1: Int,num2: Int) throws -> Int {
if num2 == 0 {
throw MyError.illegaArg("0不能作为除数")
}
return num1 / num2
}
func open(_ fileName: String) -> Int {
print("open")
return 0
}
func close(_ file: Int) -> Void {
print("close")
}
func processFile(_ fileName: String) throws -> Void {
let file = open(fileName)
//use file
//...
try divide(num1: 100, num2: 0)
close(file)
}
try processFile("test.txt")
-
processFile
读取文件,当try divide(num1: 100, num2: 0)
抛出Error,导致后面的代码执行不了,文件无法关闭,会导致内存泄漏;
- 可使用
defer
语句进行改造,如下:
func processFile(_ fileName: String) throws -> Void {
let file = open(fileName)
//当前函数执行完成之前,会执行defer语句中的代码
defer {
close(file)
}
//use file
//...
try divide(num1: 100, num2: 0)
}
- 再当前函数执行完成之前,会执行defer语句中的代码,保证文件能关闭;
- defer语句的定义顺序与执行顺序相反;
func fn1() {
print("fn1")
}
func fn2() {
print("fn2")
}
func test() {
defer {
fn1()
}
defer {
fn2()
}
}
test() //fn2 fn1
断言(assert)
- 很多编程语言都有断言机制,不符合条件的就抛出运行时错误,常用于调试阶段的条件判断;
- 默认情况下,Swift断言只会在debug模式下生效,release模式下会忽略;
- assert不能被捕捉,处理,直接闪退;
func divide(_ v1: Int,_ v2: Int) -> Int {
assert(v2 != 0,"除数不能为0")
return v1 / v2
}
fatalError
- 如果遇到严重问题,希望结束程序运行,可以直接使用fatalError函数抛出错误,这是无法通过do--cacth捕捉的错误;
- 若使用了fatalError函数,就不需要return 返回值了;
func test(_ num: Int) -> Int {
if num >= 0 {
return 1
}
fatalError("num不能小于0") //来到这里 会闪退
}
- 在某些不得不实现,但又不希望别人调用的函数方法,可以考虑内部使用
fatalError
函数;
class Person {
required init(){}
}
class Student : Person {
var age: Int
required init() {
fatalError("不要调用这个初始化器")
}
init(age: Int) {
self.age = age
}
}
泛型
- 泛型可以将类型参数化,提高代码的复用率,减少代码量;
函数参数使用泛型
func swapValues<T>(num1: inout T,num2: inout T) -> Void {
let temp = num1
num1 = num2
num2 = temp
}
var n1 = 10
var n2 = 20
swapValues(num1: &n1, num2: &n2)
print(n1)
print(n2)
var d1 = 10.0
var d2 = 20.0
swapValues(num1: &d1, num2: &d2)
print(d1)
print(d2)
var fn1: (inout Int,inout Int) -> () = swapValues
fn1(&n1,&n2)
var fn2: (inout Double,inout Double) -> () = swapValues
fn2(&d1,&d2)
类class使用泛型
class Stack<E> {
var elements = [E]()
func push(_ element: E) -> Void {
elements.append(element)
}
func pop() -> E {
elements.removeLast()
}
func top() -> E {
elements.last!
}
func size() -> Int {
elements.count
}
}
//存储string的栈
var stringStack = Stack<String>()
//存储int的栈
var intStack = Stack<Int>()
//存储任意类型的栈
var anyStack = Stack<Any>()
函数返回值使用泛型
泛型函数的调用本质
func swapValues<T>(num1: inout T,num2: inout T) -> Void {
let temp = num1
num1 = num2
num2 = temp
}
var n1 = 10
var n2 = 20
swapValues(num1: &n1, num2: &n2)
var d1 = 10.0
var d2 = 20.0
swapValues(num1: &d1, num2: &d2)
关联类型
- 关联类型的作用:给协议中用到的类型 定义一个占位名称;
- 协议中可以拥有多个关联类型;
protocol Stackable {
associatedtype Element //关联类型
mutating func push(_ element: Element)
mutating func pop() -> Element
func top() -> Element
func size() -> Int
}
class stringStack : Stackable {
//给关联类型赋值真正要使用的类型
//这行代码也可以不写,编译器也能正确确定要使用的类型
typealias Element = String
var elements = [String]()
func push(_ element: String) {
elements.append(element)
}
func pop() -> String {
elements.removeLast()
}
func top() -> String {
elements.last!
}
func size() -> Int {
elements.count
}
}
- 协议中定义泛型,需要使用
associatedtype
关键字;
- stringStack在实现Stackable协议时,必须要指定Stackable协议中定义的泛型类型,可使用
typealias
关键字,指定泛型的具体类型;
类型约束
protocol Runnable {
}
class Person {
}
func swapValues<T: Person & Runnable>(_ a: inout T,_ b: inout T) {
(a,b) = (b,a)
}
- swapValues函数参数是泛型类型,即可以是任意类型,但此任意类型需要满足
Person & Runnable
这个约束条件,即遵循Runnable协议的Person类型,才能作为入参,否则不能;
- 泛型约束;
protocol Stackable {
associatedtype Element : Equatable
}
class Stack<E : Equatable> : Stackable {
typealias Element = E
}
func equal<S1: Stackable,S2: Stackable>(_ s1: S1, _s2: S2) -> Bool
where S1.Element == S2.Element,S1.Element : Hashable
{
return false
}
-
associatedtype Element : Equatable
:Stackable协议中,定义泛型Element,且Element泛型遵循Equatable协议;
-
equal
函数,其参数是泛型,且泛型遵循Stackable协议;
-
where S1.Element == S2.Element,S1.Element : Hashable
是对关联类型的约束限制;
protocol Runnable { }
class Person : Runnable { }
class Car : Runnable { }
func get(_ type: Int) -> Runnable {
if type == 0 {
return Person()
}
return Car()
}
var r1 = get(0)
var r2 = get(1)
- 上述代码没有问题,在编译期get方法的返回值是遵循Runnable协议的类型,是可以确定的,但具体是什么类型,必须要到运行行才能确定,只有传入参数,调用执行get方法,才能确定具体类型,再看一段代码:
protocol Runnable {
associatedtype Speed
var speed: Speed { get }
}
class Person : Runnable {
var speed: Double { 0.0 }
}
class Car : Runnable {
var speed: Int { 0 }
}
func get(_ type: Int) -> Runnable {
if type == 0 {
return Person()
}
return Car()
}
var r1 = get(0)
var r2 = get(1)
- 上述代码会出现报错,原因在于协议中定义了关联类型(泛型);
- 当在编译期时,
只能确定get方法的返回值是遵循Runnable协议的对象
,但是不能确定关联类型Speed的具体类型,所以会出现报错
;
- 解决方案一:
protocol Runnable {
associatedtype Speed
var speed: Speed { get }
}
class Person : Runnable {
var speed: Double { 0.0 }
}
class Car : Runnable {
var speed: Int { 0 }
}
func get<T: Runnable>(_ type: Int) -> T {
if type == 0 {
return Person() as! T
}
return Car() as! T
}
var r1: Person = get(0)
var r2: Car = get(1)
-
r1
类型确定,那么其speed的类型就随之确定;
- 解决方案二:
protocol Runnable {
associatedtype Speed
var speed: Speed { get }
}
class Person : Runnable {
var speed: Double { 0.0 }
}
class Car : Runnable {
var speed: Int { 0 }
}
func get(_ type: Int) -> some Runnable {
return Car()
}
var r1 = get(0)
var r2 = get(1)
-
get
的返回值类型为some Runnable
对于外界来说是不透明的类型,也就是说some Runnable
,可以屏蔽内部的真实类型,对于外界只知道是遵循Runnable协议的类型;
-
some
,限制只能返回一种类型,那么r1的类型确定,则speed的类型也就确定;
some关键字
-
some
限制只能返回一种类型;
-
some
用于限制返回值的类型上;
-
some
还可以用于属性类型上;
- some 的用法就是修饰在一个 protocol 前面,默认场景下 protocol 是没有具体类型信息的,但是用 some 修饰后,编译器会让 protocol 的实例类型对外透明;
import Foundation
func makeInt() -> Equatable {
return 5
}
let a = makeInt()
let b = makeInt()
if a == b {
print("equal")
}
- 上述代码报错,原因在于Equatable协议作为函数返回值,在makeInt函数调用时,无法确定其返回值的具体类型;
- 我们可以使用
泛型约束
对其进行改造,代码如下:
import Foundation
func makeInt<T: Equatable>() -> T {
return 5 as! T
}
let a: Int = makeInt()
let b: Int = makeInt()
if a == b {
print("equal")
}
- 在使用
泛型约束
声明后,在代码调用的时候编译器可以通过类型推断出 返回值的具体类型是什么;
- 使用
泛型约束
的语法来解决这个问题,比较冗余,可使用some
关键字,在协议前面标记上 some 后,返回值的类型对编译器就变成透明的了,在这个值使用的时候编译器可以根据返回值进行类型推断得到具体类型,代码如下:
import Foundation
func makeInt() -> some Equatable {
return 5
}
let a = makeInt()
let b = makeInt()
if a == b {
print("equal")
}
- 注意:⚠️
some
限制只能返回一种类型,所以makeInt函数只能返回一种数据类型,如果返回多个数据类型,就会报错;
参考文章