Swift基础语法1(下标、类的继承、初始化器、可选链、协议、错误处理、泛型)

·  阅读 149

下标(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
}
复制代码
  • 每个类至少有一个指定初始化器,指定初始化器是类的主要初始化器
  • 默认初始化器重视类的置顶初始化器
  • 类偏向于少量指定初始化器,一个类通常只会有一个初始化器
  • 初始化器的相互调用规则:
  1. 指定初始化器必须从它的直系父类调用指定初始化器
  2. 便捷初始化器必须从相同的类里调用另一个初始化器
  3. 便捷初始化器最终必须调用一个指定初始化器

更多的继承关系

  • 这一套规则保证了
  • 使用任意初始化器,都可以完整的初始化实例

两段式初始化

  • Swift在编码安全方面是煞费苦心,为了保证初始化过程的安全,设定了两段式初始化、安全检查
  • 两段式初始化:
  • 第一阶段:初始化所有存储属性
  1. 外层调用指定\编辑初始化器
  2. 分配内存给实例,但未初始化
  3. 指定初始化器确保当前类定义的存储属性都初始化
  4. 指定初始化器调用父类的初始化器,不断向上调用,形成初始化器链
  • 第二阶段:设置新的存储属性值
  1. 从顶部初始化器往下,链中每一个指定初始化器都有机会进一步定制实例
  2. 初始化器现在能够使用self(访问、修改它的属性,调用它的实例方法等等)
  3. 最终,链中任何便捷初始化器都有机会定制实例及使用self

安全检查

  • 指定初始化器必须调用父类初始化器之前,其所在类定义的所有存储属性都要初始化完成
  • 指定初始化器必须先调用父类初始化器,然后才能为继承的属性设置新值
  • 便捷初始化器必须先调用同类的其他初始化器,然后再为任意属性设置新值
  • 初始化器在第一阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性值,也不能引用self
  • 直到第一阶段结束,实例才算完全合法

重写

  • 当重写父类的置顶初始化器,必须加上override(即使子类的实现是便捷初始化器)
  • 如果子类写了一个匹配父类便捷初始化器,不用加上override,因为父类的便捷初始化器永远不会通过子类直接调用,因此,严格来说,子类无法重写父类的便捷初始化器

自动继承

  1. 如果子类没有自定义任何指定初始化器,它会自动继承父类所有的置顶初始化器
  2. 如果子类提供了父类所有指定初始化器的实现(要么通过方式1继承,要么重写)子类自动继承所有的父类便捷初始化器
  3. 就算子类添加了更多的便捷初始化器,这些规则仍然适用
  4. 子类以便捷初始化器的形式重写父类的置顶初始化器,也可以作为满足规则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
  1. 才允许结构体、枚举的具体实现修改自身内存
  2. 累的实现方法不用加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
复制代码

错误处理

自定义错误

  1. 测试系统处理异常出现的错误信息
func divide1(_ num1: Int, _ num2:Int) -> Int {
	return num1 / num2
}

var result = divide1(10, 0)
// Fatal error: Division by zero
复制代码
  1. 自定义错误信息
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中方式
  1. 通过do-catch捕捉Error
  2. 不捕捉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:强制开启断言

assert

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()
	}
}
复制代码
分类:
iOS
标签:
分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改