下标(subscript)
class Point {
var x = 0.0, y = 0.0
subscript(index:Int) -> Double {
set{
if index == 0 {
x = newValue
} else if index == 1 {
y = newValue
}
}
get {
if index == 0 {
return x
} else if index == 1 {
return y
}
return 0
}
}
}
var p = Point()
print(p[0]) // 0.0
print(p[1]) // 0.0
print(p[2]) // 0.0
print(p.x) // 0.0
print(p.y) // 0.0
p[0] = 10.1
p[1] = 20.2
print(p[0]) // 10.1
print(p[1]) // 20.2
print(p[2]) // 0.0
print(p.x) // 10.1
print(p.y) // 20.2
复制代码
下标细节
- subscript可以没有set方法,但是必须要有get方法
class Point {
var x = 0.0, y = 0.0
subscript(index:Int) -> Double {
get {
if index == 0 {
return x
} else if index == 1 {
return y
}
return 0
}
}
}
复制代码
- 如果只有get方法,可以省略get
class Point {
var x = 0.0, y = 0.0
subscript(index:Int) -> Double {
if index == 0 {
return x
} else if index == 1 {
return y
}
return 0
}
}
复制代码
- 可以设置参数标签
class Point {
var x = 0.0, y = 0.0
subscript(index i:Int) -> Double {
if i == 0 {
return x
} else if i == 1 {
return y
}
return 0
}
}
var p = Point()
p.y = 20.1
print(p[index: 1]) // 20.1
复制代码
- 下标可以是类型方法
class Sum {
static subscript(v1:Int,v2:Int) -> Int {
return v1 + v2
}
}
print(Sum[20,30]) // 50
复制代码
接收多个参数的下标
class Grid {
var data = [
[0,1,2],
[3,4,5],
[6,7,8]]
subscript(row:Int, column:Int) -> Int {
set {
guard row>=0 && row < 3 && column >= 0 && column < 3 else {
return
}
data[row][column] = newValue
}
get {
guard row>=0 && row < 3 && column >= 0 && column < 3 else {
return 0
}
return data[row][column]
}
}
}
var grid = Grid()
grid[0,1] = 99
grid[1,2] = 88
grid[5,6] = 100 // 不会被赋值
print(grid.data) // [[0, 99, 2], [3, 4, 88], [6, 7, 8]]
复制代码
类的继承
- 值类型(枚举、结构体)不支持
- 没有父类的类,称为:基类
- Swift并没有像OC,Java那样的规定:任何类最终都要继承自某个基类)
- 子类可以重写父类的下标、方法、属性,重写必须加上
override
关键字 重写实例方法、下标
class Animal {
func speak() {
print("Animal speak")
}
subscript(index:Int) -> Int {
return index
}
}
class Cat: Animal {
override func speak() {
super.speak()
print("Cat speak")
}
override subscript(index: Int) -> Int {
return super[index] * 2
}
}
var anim:Animal
anim = Animal()
anim.speak()
print(anim[10])
anim = Cat()
anim.speak()
print(anim[10])
// 输出结果为
Animal speak
10
Animal speak
Cat speak
20
复制代码
重写属性
- 子类可以将父类的属性(存储、计算)重写为
计算属性
- 子类不可以将父类属性重写为存储属性
- 只能重写var属性,不能重写let属性
- 重写时,属性名、类型要一致
- 子类重写后的属性权限不能小于父类属性的权限
- 如果父类属性是只读,那么子类重写后的属性可以是只读,也可以是可读可写
- 如果父类属性是可读可写,那么子类重写后的属性也必须是可读可写的
重写类型属性
- 被class修饰的计算属性,可以被子类重写
- 被static修饰的类型属性(存储、计算),不可以被子类重写
属性观察器
可以在子类中为父类属性(除只读计算属性、let属性)增加属性观察器
final
- 被final修饰的方法、下标、属性,禁止重写
- 被final修饰的类,禁止被继承
初始化器
- 类、结构体、枚举都可以定义初始化器
- 类有2中初始化器:指定初始化器(designated initializer)、便捷初始化器(convenience initializer)
// 指定初始化器
init(parameters) {
statements
}
// 便捷初始化器
convenience init(parameters) {
statements
}
复制代码
- 每个类至少有一个指定初始化器,指定初始化器是类的主要初始化器
- 默认初始化器重视类的置顶初始化器
- 类偏向于少量指定初始化器,一个类通常只会有一个初始化器
- 初始化器的相互调用规则:
- 指定初始化器必须从它的直系父类调用指定初始化器
- 便捷初始化器必须从相同的类里调用另一个初始化器
- 便捷初始化器最终必须调用一个指定初始化器
- 这一套规则保证了
- 使用任意初始化器,都可以完整的初始化实例
两段式初始化
- Swift在编码安全方面是煞费苦心,为了保证初始化过程的安全,设定了两段式初始化、安全检查
- 两段式初始化:
- 第一阶段:初始化所有存储属性
- 外层调用指定\编辑初始化器
- 分配内存给实例,但未初始化
- 指定初始化器确保当前类定义的存储属性都初始化
- 指定初始化器调用父类的初始化器,不断向上调用,形成初始化器链
- 第二阶段:设置新的存储属性值
- 从顶部初始化器往下,链中每一个指定初始化器都有机会进一步定制实例
- 初始化器现在能够使用self(访问、修改它的属性,调用它的实例方法等等)
- 最终,链中任何便捷初始化器都有机会定制实例及使用self
安全检查
- 指定初始化器必须调用父类初始化器之前,其所在类定义的所有存储属性都要初始化完成
- 指定初始化器必须先调用父类初始化器,然后才能为继承的属性设置新值
- 便捷初始化器必须先调用同类的其他初始化器,然后再为任意属性设置新值
- 初始化器在第一阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性值,也不能引用self
- 直到第一阶段结束,实例才算完全合法
重写
- 当重写父类的置顶初始化器,必须加上override(即使子类的实现是便捷初始化器)
- 如果子类写了一个匹配父类便捷初始化器,不用加上override,因为父类的便捷初始化器永远不会通过子类直接调用,因此,严格来说,子类无法重写父类的便捷初始化器
自动继承
- 如果子类没有自定义任何指定初始化器,它会自动继承父类所有的置顶初始化器
- 如果子类提供了父类所有指定初始化器的实现(要么通过方式1继承,要么重写)子类自动继承所有的父类便捷初始化器
- 就算子类添加了更多的便捷初始化器,这些规则仍然适用
- 子类以便捷初始化器的形式重写父类的置顶初始化器,也可以作为满足规则2的一部分
required
- 用required修饰的指定初始化器,表明其所有子类都必须实现该初始化器(通过继承或者重写实现)
- 如果子类重写了required初始化器,也必须加上required,不用加override
class Animal {
required init() { }
init(weight:Double) { }
}
class Cat: Animal {
required init() {
super.init()
}
}
复制代码
属性观察器
- 父类的属性在它自己的初始化器中赋值不会触发属性观察器,但在子类的初始化器中赋值会触发属性观察器
class Animal {
var height:Double {
willSet {
print("Animal willset",newValue)
}
didSet {
print("Animal didSet",oldValue,height)
}
}
init() {
self.height = 0
}
}
class Cat: Animal {
override init() {
super.init()
self.height = 66
}
}
let myCat = Cat()
// log:
// Animal willset 66.0
// Animal didSet 0.0 66.0
复制代码
可失败初始化器
- 类、结构体、枚举都可以使用init?定义可失败初始化器
class Animal {
var name:String
init?(name:String) {
if name.isEmpty {
return nil
}
self.name = name
}
}
var anim = Animal.init(name: "")
print(anim?.name)
// 枚举自带的可失败初始化器
enum Answer:Int {
case wrong,right
}
var an = Answer(rawValue: 1)
an?.rawValue
复制代码
- 不允许同时定义参数标签、参数个数、参数类型相同的可失败初始化器和非可失败初始化器(不能构成重载)
- 可以用init!定义隐私解包的可失败初始化器
- 可失败初始化器可以调用非可失败初始化器,非可失败初始化器调用可失败初始化器需要调用解包
- 如果初始化器调用一个可失败初始化器导致初始化失败,那么整个初始化过程都失败,并且之后的代码都停止运行
- 可以用一个非可失败初始化器重写一个可失败初始化器,但反过来是不行的。
反初始化器(deinit)
- deinit叫做反初始化器,类似C++的析构函数、OC中的dealloc方法
- 当类的实例对象被释放内存时,就会调用对象的deinit方法
class Animal {
deinit {
print("Animal对象被释放了")
}
}
复制代码
- deinit不接受任何参数,不能写小括号,不能自行调用
- 父类的deinit能被子类继承
- 子类的deinit实现执行完毕后会调用父类的deinit
可选链
class Car { var price = 0 }
class Dog { var weight = 0 }
class Person {
var name: String = ""
var dog: Dog = Dog()
var car:Car? = Car()
func age() -> Int { 20 }
func eat() {print("I want Eat")}
subscript(index:Int) -> Int { index }
}
var person:Person? = Person()
var age1 = person?.age() // Int?
var age2 = person!.age() // Int
var name = person?.name // String?
var index = person?[5] // Int?
func getName() -> String {
"jack"
}
// 如果person为nil,不会调用getName()方法
person?.name = getName()
复制代码
- 如果可选项为nil,调用方法、下标、属性失败,结果为nil
- 如果可选项不为nil,调用方法、下标、属性成功,结果会被包装成可选项
- 如果结果本来就是可选项,不会进行再次包装
var dog = person?.dog
var height = person?.dog.weight
var price = person?.car?.price
复制代码
- 多个?可以链接在一起
- 如果链中任何一个节点是nil,那么整个链就会调用失败
协议(Protocol)
- 协议可以用来定义方法、属性、下标的声明,协议可以被枚举、结构体、类遵守(多个协议之间用逗号隔开)
protocol Drawable {
func draw()
var x:Int {get set}
var y:Int {get}
subscript(index:Int) -> Int {get set}
}
class myDraw: Drawable {
func draw() {
print("myDraw->draw")
}
var x: Int = 0
var y: Int = 0
subscript(index: Int) -> Int {
get {
print("start get index")
return index
}
set {
print("set index")
}
}
}
let myd = myDraw()
myd.x = 100
let v = myd[20]
print(v)
// start get index
// 20
protocol Test1 {}
protocol Test2 {}
protocol Test3 {}
protocol Test4 {}
class TestClass: Test1,Test2,Test3,Test4 { }
复制代码
- 协议中定义方法时不能有默认参数值
- 默认情况下,协议中定义的内容必须全部实现(要做到部分实现,请看后续...)
协议中的属性
- 协议中定义属性时必须用var关键字
- 实现协议时的属性权限要不小于协议中定义的属性权限
- 协议中定义get、set,用var存储属性或get、set计算属性去实现
- 协议定义get,用任何属性都可以实现
var x: Int {
get {0}
set {}
}
复制代码
- 为了保证通用,协议中必须用static定义类型方法,类型属性、类型下标
protocol Drawable {
static func draw()
}
class Person1: Drawable {
static func draw() {
print("static Person1 draw")
}
}
class Person2: Drawable {
class func draw() {
print("class Person2 draw")
}
}
复制代码
mutating
- 只有将协议中的实例方法标记为mutating
- 才允许结构体、枚举的具体实现修改自身内存
- 累的实现方法不用加mutating,枚举、结构体才需要加mutating
protocol ChangeV {
mutating func change()
}
class Size: ChangeV {
var width:Int = 0
func change() {
width = 100
}
}
struct Point:ChangeV {
var x:Int = 0
mutating func change() {
x = 20
}
}
复制代码
init
- 协议中还可以定义初始化器init,非final类实现时必须加上required
- 如果协议实现的初始化器,刚好是重写了父类的指定初始化器,那么这个初始化必须同时加上required、override
protocol Drawable {
init(x:Int,y:Int)
}
class Point: Drawable {
required init(x: Int, y: Int) { }
}
final class Size: Drawable {
init(x: Int, y: Int) { }
}
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!去实现
协议的继承
- 一个协议可以继承其他协议
protocol Runable {
func run()
}
protocol Livable:Runable {
func drink()
}
class Person:Livable {
func drink() { }
func run() { }
}
复制代码
协议组合
- 协议组合,可以包含一个类(最多一个)
// 参数是接收Person或其子类的对象
func fn0(obj:Person) { }
// 参数是接收遵守Livable协议的对象
func fn1(obj:Livable) { }
// 参数是接收遵守Livable、Runable协议的对象
func fn2(obj:Livable & Runable) { }
// 参数是接收Person或其子类,并且遵守Livable、Runable协议的对象
func fn3(obj:Person&Livable&Runable) { }
typealias RealPerson = Person & Livable & Runable
func fn4(obj:RealPerson) { }
复制代码
CaseIterable
- 让枚举遵守CaseIterable,可以还是先遍历枚举值
enum Season:CaseIterable {
case spring, summer, autumn, winter
}
let seasons = Season.allCases
print(seasons.count)
for season in seasons {
print(season)
}
复制代码
Self
- Self代表当前类型
class Person {
var age = 1
static var count = 2
func run() {
print(self.age) // self是当前对象
print(Self.count) // Self是当前类
}
}
复制代码
- Self一般作为返回值类型,限定返回值类型与调用者是同一类型(也可作为参数类型)
protocol Runnable {
func test() -> Self
}
class Person: Runnable{
required init() { }
func test() -> Self {
type(of: self).init()
}
}
class Student : Person {}
var p = Person()
print(p.test()) // Person
var stu = Student()
print(stu.test()) // Student
复制代码
错误处理
自定义错误
- 测试系统处理异常出现的错误信息
func divide1(_ num1: Int, _ num2:Int) -> Int {
return num1 / num2
}
var result = divide1(10, 0)
// Fatal error: Division by zero
复制代码
- 自定义错误信息
enum SomeError: Error {
case illegalArg(String)
case outOfBounds(Int, Int)
case outOfMemory
}
func divide1(_ num1: Int, _ num2:Int) throws -> Int {
if num2 == 0 {
throw SomeError.illegalArg("0不能作为除数")
}
return num1 / num2
}
var result = try divide1(10, 0)
// Fatal error: Error raised at top level: LearnSwift.SomeError.illegalArg("0不能作为除数"): file
复制代码
do-catch
- 可以用do-catch捕捉Error
func test() {
print("1")
do {
print("2")
print(try divide1(30, 0))
print("3")
print("4")
} catch let SomeError.illegalArg(msg) {
print("参数异常:",msg)
} catch let SomeError.outOfBounds(size, index) {
print("位置越界","size=\(size)","index=\(index)")
} catch SomeError.outOfMemory {
print("内存溢出")
} catch {
print("其他错误")
}
print("5")
}
test()
// 1
// 2
// 参数异常: 0不能作为除数
// 5
复制代码
- 抛出Error后,try下一句直到作用域结束的代码都将停止运行
处理Error
- 处理Error的2中方式
- 通过do-catch捕捉Error
- 不捕捉Error,在当前函数增加throws声明,Error将自动抛给上层函数如果最顶层函数(main函数)依然没有捕捉Error,那么程序将终止
func test() throws {
print("1")
print(try divide1(10, 0))
print("2")
}
try test()
// 1
// Fatal error: Error raised at top level: LearnSwift.SomeError.illegalArg("0不能作为除数"):
复制代码
do {
print(try divide1(20, 0))
} catch is SomeError {
print("SomeError")
}
// SomeError
复制代码
try?、try!
- 可以使用try、try!调用可能抛出Error的函数,这样就不用去处理Error
func test() {
print("111")
var result1 = try? divide1(20, 10)
print("result1=\(result1)")
var result2 = try? divide1(20, 0)
print("result2=\(result2)")
var result3 = try! divide1(20, 5)
print("result3 = \(result3)")
print("222")
var result4 = try! divide1(10, 0)
print("result4=\(result4)")
print("333")
}
test()
//111
//result1=Optional(2)
//result2=nil
//result3 = 4
//222
//Fatal error: 'try!' expression unexpectedly raised an error: LearnSwift.SomeError.illegalArg("0不能作为除数"):
// 在遇到可能抛出异常的语句不能使用try!
复制代码
- rethrows表明:函数本身不会抛出错误,但调用闭包参数抛出错误,那么它会将错误向上抛出
func exec(_ fn:(Int,Int) throws -> Int, _ num1:Int, _ num2:Int) rethrows {
print(try fn(num1,num2))
}
print("1")
try exec(divide1(_:_:), 20, 2)
print("2")
try exec(divide1(_:_:), 20, 0)
print("3")
//1
//10
//2
//Fatal error: Error raised at top level: LearnSwift.SomeError.illegalArg("0不能作为除数"):
复制代码
defer
- defer语句:用来定义以任何方式(抛错误、return等)离开代码块之前必须执行的代码,defer语句将延迟至当前作用域结束之前执行
func divide1(_ num1: Int, _ num2:Int)throws -> Int {
return num1 / num2
}
func open(_ fileName:String) -> Int {
print("openFile")
return 0
}
func close(_ file:Int) {
print("close")
}
func processFile(_ filename:String)throws {
let file = open(filename)
defer {
close(file)
}
print("123456")
return
}
try processFile("test.js")
// openFile
// 123456
// close
复制代码
- defer语句的执行顺序与定义顺序相反
func fn1() { print("start fn1") }
func fn2() { print("start fn2") }
func test() {
print("start test()")
defer { fn1() }
defer { fn2() }
print("end test()")
}
test()
//start test()
//end test()
//start fn2
//start fn1
复制代码
assert(断言)
- 很多编程语言都有断言机制:不符合指定条件就抛出运行时错误,常用语调试(Debug)阶段的条件判断
- 默认情况下,Swift的断言只会在Debug模式下生效,Release模式下会忽略
func divide(_ v1:Int, _ v2:Int) -> Int {
assert(v2 != 0, "q除数不能为0") // 当条件不满足的时候就执行断言
return v1 / v2
}
print(divide(20,0))
// Assertion failed: q除数不能为0:
复制代码
- 增加Swift Flags修改断言的默认行为
- -assert-config Release:强制关闭断言
- -assert-config Debug:强制开启断言
fatalError
- 如果遇到严重问题,希望技术程序运行时,可以直接使用fatalError函数抛出错误(这是无法通过do-catch捕捉的错误)使用了fatalError函数,就不需要再写return
func test(_ num:Int) -> Int {
if num >= 0 {
return num * 2
}
fatalError("参数num不能小于0")
}
print(test(10))
print(test(-10))
//20
//Fatal error: 参数num不能小于0:
复制代码
- 某些不得不实现、但不希望别人调用的方法,可以考虑内部使用fatalError函数
class Person { required init() {print("person init")}}
class Student: Person {
required init() { fatalError("dongt call Student.init")}
init(score: Int) {print("Student init score=\(score)")}
}
var stu1 = Student(score: 200)
var stu2 = Student()
// Student init score=200
// person init
// Fatal error: dongt call Student.init:
复制代码
- 可以使用do实现局部作用域
func test() {
var str = "str"
do {
var str = "str1"
print("str11=\(str)")
}
do {
var str = "str2"
print("str22=\(str)")
}
print("str=\(str)")
}
test()
//str11=str1
//str22=str2
//str=str
复制代码
泛型
- 泛型可以将类型参数化,提高代码复用率,减少代码量
func swapValues<T>(_ a:inout T, _ b:inout T) {
(a,b) = (b,a)
}
var i1 = 10
var i2 = 20
swapValues(&i1, &i2)
print("i1=\(i1),i2=\(i2)")
struct Date {
var year = 0, month = 0, day = 0
}
var date1 = Date(year: 2010, month: 10, day: 10)
var date2 = Date(year: 2020, month: 12, day: 12)
swapValues(&date1, &date2)
print("date1=\(date1),date2=\(date2)")
//i1=20,i2=10
//date1=Date(year: 2020, month: 12, day: 12),date2=Date(year: 2010, month: 10, day: 10)
复制代码
- 泛型函数赋值给变量
func test<T1,T2>(_ t1:T1,_ t2:T2) {
print("t1=\(t1),t2=\(t2)")
}
var fn:(Int,Double) ->() = test
fn(12,33.33)
//t1=12,t2=33.33
复制代码
class Stack<E> {
var elements = [E]()
func push(_ element:E) { elements.append(element) }
func pop() -> E { elements.removeLast() }
func top() -> E { elements.last! }
func size() -> Int { elements.count }
}
// 继承
class SubStack<E>: Stack<E> { }
var stack = Stack<Int>()
stack.push(111)
stack.push(222)
stack.push(333)
print(stack.top()) // 333
print(stack.size()) // 3
print(stack.pop()) // 333
print(stack.size()) // 2
复制代码
关联类型(Associated Type)
- 关联类型的作用:给协议中用到的类型定义一个占位名称
- 协议中可以拥有多个关联类型
protocol Stackable {
associatedtype Element // 关联类型
mutating func push(_ element:Element)
mutating func pop() -> Element
func top() -> Element
func size() -> Int
}
class Stack<E>: Stackable {
typealias Element = E
var elements = [E]()
func push(_ element: E) { elements.append(element) }
func pop() -> E { elements.removeLast() }
func top() -> E { elements.last! }
func size() -> Int { elements.count }
}
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 }
}
var ss = StringStack()
ss.push("jack")
ss.push("rose")
print(ss.top()) //rose
print(ss.elements) //["jack", "rose"]
复制代码
类型约束
protocol Runnable{}
class Person { }
// 约束泛型需要继承Person且遵守Runnable协议
func swapValues<T:Person & Runnable>(_ a:inout T, _ b:inout T) {
(a,b) = (b,a)
}
复制代码
协议类型的注意点
protocol Runnable{}
class Person:Runnable {}
class Car:Runnable{}
func get1(_ type:Int) -> Runnable {
if type == 0 {
return Person
}
return Car
}
复制代码
protocol Runnable{
associatedtype Speed
var speed:Speed { get }
}
class Person:Runnable {
typealias Speed = Double
var speed: Double{0.0}
}
class Car:Runnable{
typealias Speed = Int
var speed: Int{0}
}
func get2(_ run:Runnable) {
}
复制代码
解决方案
- 解决方案1:使用泛型
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)
复制代码
- 解决方案2:使用some关键字声明一个不透明类型
func get(_ type:Int) -> some Runnable {
return Car()
}
var r1 = get(0)
var r2 = get(1)
复制代码
- some限制只能返回一种类型
- some除了用在返回值类型上,一般还可以用在属性类型上
protocol Runnable{ associatedtype Speed }
class Dog: Runnable {
typealias Speed = Double
}
class Person {
var pet: some Runnable {
return Dog()
}
}
复制代码