记:本周对于协议和委托回调的学习使我感悟颇深,一开始由于都是简单的举例,自己也是完全跟着老师的节奏敲代码。对于简单的例子,我接受的似乎过于快(毕竟很简单的例子,使得我对于其用法有一种莫名的掌握感),在课下又缺乏系统的总结和概括。当面对实题测试的时候,我发现自己只能按照笔记的步骤去创建和实现。但是就算这样,在一些细节和忽略的地方我并不能把握好什么是重点,什么是关键。以至于我很失落,我不知道如何去抓住这门语言的某些知识点,我认识了三周的Swift,现在对我来说却是个陌生人。
协议
- 协议是方法的集合(计算属性相当于就是方法)
- 可以把看似不相关的对象的公共行为放到一个协议中(去掉重复的代码)
- 协议在Swift开发中大致有三种作用:
- 能力 - 遵循了协议就意味着具备了某种能力
- 约定 - 遵循了协议就一定要实现协议中的方法
- 角色 - 一个类可以遵循多个协议, 一个协议可以被多个类遵循, 遵循协议就意味着扮演了某种角色, 遵循多个协议就意味着可以扮演多种角色
- Swift中的继承是单一继承(一个类只能有一个父类), 如果希望让一个类具备多重能力可以使用协议来实现(C++里面是通过多重继承来实现的, 这是一种非常狗血的做法)
protocol 协议名 {
// 协议的方法
}
- 协议的扩展
- 可以在协议扩展中给协议中的方法提供默认实现
- 也就是说如果某个类遵循了协议但是没有实现这个方法就直接使用默认实现
- 那么这个方法也就相当于是一个可选方法(可以实现也可以不实现)
extension 扩展的协议名 {
// 协议中的默认实现方法
}
- 协议的继承
- 协议也可以向类一样进行继承
protocol 子协议名: 父协议名1, 父协议名2, ... {
// 协议的方法
}
下面是小雨康桥的一首诗,过于悲观,我们帮小雨康桥渡劫到来生吧。
我想做燕子 只需简单思想 只求风中流浪
我想做树 不长六腑五脏 不会寸断肝肠
我做不成燕子 所以我躲不过感情的墙
我做不成树 因此也撑不破伤心的网
来生做燕子吧 随意找棵树休息翅膀 然后淡然飞向远方
来生做树吧 当枝头燕子飞走时 再不去留恋张望
/// 生物
class Creature {
}
// 来生
// 将燕子和树设计成两个协议
protocol Swallow {
func thinkSimply()
func wanderInWind()
}
protocol Tree {
func beHeartless()
}
// 人类
class Person: Creature {
var name: String
init(name: String) {
self.name = name
}
}
// 小雨康桥除了继承人之外还遵循了燕子和树的协议(类名必须在协议名之前)
class Xiaoyu: Person, Swallow, Tree {
func thinkSimply() {
print("随意找棵树休息翅膀")
}
func wanderInWind() {
print("然后淡然飞向远方")
}
func beHeartless() {
print("当枝头燕子飞走时 再不用留恋张望")
}
}
// 来生的小雨康桥(遵循了燕子和树协议的人)
let xiaoyu: Creature = Xiaoyu(name: "小雨康桥")
// 判定小雨康桥可不可以变成燕子
if let swallow = xiaoyu as? Swallow {
swallow.thinkSimply()
swallow.wanderInWind()
}
else {
if let person = xiaoyu as? Person {
print("\(person.name)躲不过感情的墙")
}
}
// 判定小雨康桥可不可以变成树
if let tree = xiaoyu as? Tree {
tree.beHeartless()
}
else {
if let person = xiaoyu as? Person {
print("\(person.name)撑不破伤心的网")
}
}
Note:协议中全是抽象概念(只有声明没有实现) 遵循协议的类可以各自对协议中的计算属性和方法给出自己的实现版本 这样当我们面向协议编程时就可以把多态的优势发挥到淋漓尽致 可以写出更通用更灵活的代码(符合开闭原则)
实现开闭原则最关键有两点:
- 抽象是关键(在设计系统的时候一定要设计好的协议);
- 封装可变性(桥梁模式 - 将不同的可变因素封装到不同的继承结构中)
接口(协议)隔离原则: 协议的设计要小而专不要大而全
协议的设计也要高度内聚
结构
要学习结构,就必须先了解计算机的内存机制。
计算机的硬件由五大部件构成:
-
运算器、控制器、存储器、输入设备、输出设备
- 运算器 + 控制器 => CPU (中央处理器)
- 存储器 => 内存 (RAM - Random Access Memory)
-
程序员可以使用的内存大致分为五块区域:
- 栈 (stack) - 我们定义的局部变量/临时变量都是放在栈上
- 特点: 小、快
- 堆 (heap) - 我们创建的对象都是放在堆上的
- 特点: 大、慢
- 数据段 - 全局量
- 只读数据段 - 常量
- 代码段 - 函数和方法
结构和类的比较
- 区别1: 结构的对象是值类型, 类的对象是引用类型
- 值类型在赋值的时候会在内存中进行对象的拷贝
- 引用类型在赋值的时候不会进行对象拷贝只是增加了一个引用
结论: 我们自定义新类型时优先考虑使用类而不是结构除非我们要定义的是一种底层的数据结构(保存其他数据的类型)
// 学生类
class StuClass {
var name: String
var age: Int
var tel: String?
init(name: String, age: Int) {
self.name = name
self.age = age
}
func getOlder() {
age += 1
}
func study(courseName: String) {
print("\(name)正在学习.")
}
}
// 学生结构
struct StuStructure {
var name: String
var age: Int
// 区别2: 结构会自动生成初始化方法,所以不需要在结构内部初始化
// 区别3: 结构中的方法在默认情况下是不允许修改结构中的属性除非加上mutating关键字
mutating func getOlder() {
age += 1
}
}
//StuStructure
// 引用类型的类
let stu1 = StuClass(name: "高圆圆", age: 18)
var stu3 = stu1 // 此处内存中仍然只有一个学生对象
stu3.name = "Frank"
stu3.age = 19
print(stu1.name) // Frank
print(stu1.age) // 19
// 值类型的结构
let stu2 = Student2(name: "高圆圆", age: 18)
var stu4 = stu2 // 此处内存中会复制一个新的学生对象
stu4.name = "Frank"
stu4.age = 19
print(stu2.name) // 高圆圆
print(stu2.age) // 18
委托回调 / 代理
代码是最好笔记,直接上代码,语言表达只会更抽象。
// 假设的学生委托枪手代考的例子
// 委托回调
// 1. 设计一个协议(被委托方必须要遵循协议才能给别的对象当委托)
// 代考协议
protocol ExamDelegate: class {
// 协议里面的方法就是要委托其他对象做的事情
// 答题
func anserTheQuestion()
}
class Student {
// 2. 委托方添加一个属性其类型是遵循了协议的被委托方
weak var dalegate: ExamDelegate?
var name: String
init(name: String) {
self.name = name
}
func joinExam() {
print("\(name)啥都不会做啊!")
// 3. 自己做不了的事情委托给别的对象来做
dalegate?.anserTheQuestion()
}
}
// 4. 让枪手遵循协议成为被委托方(协议表能力)
class Gunner: ExamDelegate {
// 5. 遵循协议就必须要实现协议中的方法(协议表约定)
func anserTheQuestion() {
print("枪手帮忙弹无虚发!")
}
}
let stu = Student(name: "高圆圆")
let gun = Gunner()
stu.dalegate = gun
stu.joinExam()
// 高圆圆啥都不会做啊!
// 枪手帮忙弹无虚发!
// 代理
// 1. 设计一个协议(被代理方必须要遵循协议才能给别的对象当代理)
protocol ExamCandidate: class {
func answerTheQuestion()
}
class Student {
var name: String
init(name: String) {
self.name = name
}
func answerTheQuestion() {
print("\(name)啥都不会做啊!")
}
}
// 2. 让枪手遵循协议成为代理方
class Gunman: ExamCandidate {
var name: String
// 3. 代理方添加一个属性其类型是被代理方
var target: Student?
init(name: String) {
self.name = name
}
// 4. 遵循协议就必须要实现协议中的方法
func answerTheQuestion() {
if let stu = target {
print("姓名:\(stu.name)")
print("奋笔疾书挥汗如雨!")
print("提交试卷")
}
}
}
let stu = Student(name: "高圆圆")
let gun = Gunman(name: "Frank")
// 5.自己做不了的事让代理方来做
gun.target = stu
gun.answerTheQuestion()
// 姓名:高圆圆
// 奋笔疾书挥汗如雨!
// 提交试卷
构造器
- 指派构造器(designated)
- 便利构造器(convenience)
- 指派构造器前面加上convenience可以将构造器指定为便利构造器
- 可以有多个便利构造器
- 必要构造器(required)
- 指派构造器前面加上required可以将构造器指定为必要构造器
- 所谓的必要构造器意味着子类也要提供一模一样的构造器
初始化
- 初始化的第一阶段
- 初始化自己特有的属性
- 子类只能调用直接父类的构造器
- 子类构造器必须调用父类的非便利构造器(指派构造器)
- 调用父类的初始化方法
- 初始化的第二阶段
- 此处可以调用对象的方法因为对象已经完成了初始化
ARC即时性的内存清理
如果程序中出现了类与类之间双向关联关系,将会形成循环引用导致ARC无法释放内存。
解决方法:
-
如果允许使用可空类型通常使用weak来破除循环引用(推荐使用)原因如下:
- 如果与之关联的对象被释放了,那么该对象会被赋值为nil
- 如果要继续给该对象发消息程序不会崩溃
-
如果不允许使用可空类型就必须使用unowned来破除循环引用(谨慎使用)原因如下:
- 如果与之关联的对象被释放了,仍然通过该对象向其发消息,那么会导致程序崩溃
泛型(generic)
让类型不再是程序中的硬代码(写死的东西)
Swift中的类、结构和枚举都可以使用泛型
// 冒泡排序
// 定义一个虚拟类型T, 调用函数时根据传入的参数类型来决定T到底是什么类型
// 泛型限定
// <T: Comparable>限定T类型必须是遵循了Comparable协议的类型
func bubbleSort<T: Comparable>(array: [T]) -> [T] {
var newArray = array
for i in 0..<newArray.count - 1 {
var swapped = false
for j in 0..<newArray.count - 1 - i {
if newArray[j] > newArray[j + 1] {
(newArray[j], newArray[j + 1]) = (newArray[j + 1], newArray[j])
swapped = true
}
}
if !swapped {
break
}
}
return newArray
}
let array1: Array<Int> = [23, 45, 99, 12, 68, 51, 70, 66]
let array2 = bubbleSort(array1)
print(array2) // [12, 23, 45, 51, 66, 68, 70, 99]
let array3 = ["hello", "zoo", "kiss", "apple", "good"]
let array4 = bubbleSort(array3)
print(array4) // ["apple", "good", "hello", "kiss", "zoo"]
异常处理
以上周自定义的分数运算的类为例
// 短除法(欧几里得算法)
// x和y的最大公约数跟y%x和x的最大公约数是一样的
// Greatest Common Divisor
func gcd(x: Int, _ y: Int) -> Int {
if x > y {
return gcd(y, x)
}
else if y % x != 0 {
return gcd(y % x, x)
}
else {
return x
}
}
// 定义一个遵循ErrorType协议的枚举
// 通过不同的case定义程序中可能出现的若干种异常状况
enum FractionError: ErrorType {
case ZeroDenominator // 分母为0
case DivideByZero // 除以0
}
class Fraction {
private var _num: Int
private var _den: Int
var info: String {
get {
return _num == 0 || _den == 1 ? "\(_num)" : "\(_num)/\(_den)"
}
}
// 如果一个方法抛出了异常 那么在声明方法时必须要写上throws关键字
// throws关键字是提醒方法的调用者方法可能会出状况 调用时要写try
init(num: Int, den: Int) throws {
_num = num
_den = den
if _den == 0 {
// 如果程序中出现问题就抛出错误(异常)
// 被throw关键字抛出的必须是遵循ErrorType协议的东西
throw FractionError.ZeroDenominator
}
else {
simplify()
normalize()
}
}
func add(other: Fraction) -> Fraction {
// 如果能够确保方法调用时不出异常那么可以在try关键字后加!
// 这样就可以在不写do...catch的情况下调用可能出状况的方法
return try! Fraction(num: _num * other._den + other._num * _den, den: _den * other._den)
}
func sub(other: Fraction) -> Fraction {
return try! Fraction(num: _num * other._den - other._num * _den, den: _den * other._den)
}
func mul(other: Fraction) -> Fraction {
return try! Fraction(num: _num * other._num, den: _den * other._den)
}
func div(other: Fraction) throws -> Fraction {
if other._num == 0 {
throw FractionError.DivideByZero
}
return try! Fraction(num: _num * other._den, den: _den * other._num)
}
func normalize() -> Fraction {
if _den < 0 {
_num = -_num
_den = -_den
}
return self
}
func simplify() -> Fraction {
if _num == 0 {
_den = 1
}
else {
let x = abs(_num)
let y = abs(_den)
let g = gcd(x, y)
_num /= g
_den /= g
}
return self
}
}
// 运算符重载(为自定义的类型定义运算符)
func +(one: Fraction, two: Fraction) -> Fraction {
return one.add(two)
}
func -(one: Fraction, two: Fraction) -> Fraction {
return one.sub(two)
}
func *(one: Fraction, two: Fraction) -> Fraction {
return one.mul(two)
}
func /(one: Fraction, two: Fraction) throws -> Fraction {
return try one.div(two)
}
do {
let f1 = try Fraction(num: 3, den: 4)
let f2 = try Fraction(num: 0, den: 9)
print(f1.info)
print(f2.info)
let f3 = f1 + f2
print(f3.info)
let f4 = f1 - f2
print(f4.info)
let f5 = f1 * f2
print(f5.info)
let f6 = try f1 / f2
print(f6.info)
}
catch FractionError.ZeroDenominator {
print("分母不能为0!!!")
}
catch FractionError.DivideByZero {
print("除以0是不行的!!!")
}
catch {
print("出错了! 我也不知道是什么问题!!!")
}
Note: 1. 对于可能出状况的代码要放在do...catch中执行
2.在可能出状况的方法前还要写上try表示尝试着执行
3.如果在do中没有出现任何状况那么catch就不会执行
4.如果do中出现了状况代码就不会再向下继续执行而是转移到catch中
5.在do的后面可以跟上多个catch用于捕获不同的异常状况 但是最多只有一个catch会被执行
自省
突然发现自己的很多不足之处,对Swift语法的学习算是告一段落了,但是我对该语言的运用却是很有限的,我只能在playground中写一些类似于排序、简单的逻辑运算还有一些小的数字游戏等。对于复杂一点的逻辑以及需要较困难的综合运用,我都是比较难以入手的,但有时候在老师的指点下也会有一种豁然开朗的感觉,我觉得那正是我喜欢这行的证明。我个人认为经验和技术是需要大量的代码堆积起来的,而我才刚刚起步。我怕走错路,更怕被牵着跑,欲速则不达。不管怎样总算是开了个头,此后的时间虽然有限但是我会好好把握的。
周末,我整理一下对于委托回调和代理的关系,以及如何分别实现两者。虽然写得很简单,但是实现的步骤却很清晰明了,我喜欢这种感觉。