day one
依赖倒转原则(面向协议编程)
- 声明变量的类型时应该尽可能使用协议类型
- 声明方法参数类型时应该尽可能使用协议类型
- 声明方法返回类型时应该尽可能使用协议类型
协议组合
let array: [protocol<Flyable, Fightable>] = [
// Rocket(),
// Bird(),
Superman(),
// Boxer()
// Airplane()
]
协议中全是抽象概念(只有声明没有实现) 遵循协议的类可以各自对协议中的计算属性和方法给出自己的实现版本 这样当我们面向协议编程时就可以把多态的优势发挥到淋漓尽致 可以写出更通用更灵活的代码(符合开闭原则)
就像两个人说话一样,把话说得委婉一点,肯定不会错。
实现开闭原则最关键有两点:
- 抽象是关键(在设计系统的时候一定要设计好的协议);
- 封装可变性(桥梁模式 - 将不同的可变因素封装到不同的继承结构中)
接口(协议)隔离原则: 协议的设计要小而专不要大而全(注意不要和单一原则搞混了)
协议的设计也要高度内聚
协议是方法的集合(计算属性相当于就是方法)
可以把看似不相关的对象的公共行为放到一个协议中
协议在Swift开发中大致有三种作用:
- 能力 - 遵循了协议就意味着具备了某种能力
- 约定 - 遵循了协议就一定要实现协议中的方法
- 角色 - 一个类可以遵循多个协议, 一个协议可以被多个类遵循, 遵循协议就意味着扮演了某种角色, 遵循多个协议就意味着可以扮演多种角色
Swift中的继承是单一继承(一个类只能有一个父类), 如果希望让一个类具备多重能力可以使用协议来实现(C++里面是通过多重继承来实现的, 这是一种非常狗血的做法)
相当于给协议取默认值并实现,如果你没有实现就取默认值,不会报错。
协议扩展 - 可以在协议扩展中给协议中的方法提供默认实现,也就是说如果某个类遵循了协议但是没有实现这个方法就直接使用默认实现,那么这个方法也就相当于是一个可选方法(可以实现也可以不实现)
day two
某对象想要做一个事,却不能做,可以用委托回调的编程模式来解决,弄一个协议,然后在另外一个对象来实现解决,最后带用该方法就行。
delegate?.showMessage(self, message: renjuBoard.isBlackTurn ? "白棋胜" : "黑棋胜")
protocol CanvasDelegate: class {
// 协议里面的方法就是要委托其他对象做的事情
func showMessage(canvas: Canvas, message: String)
}
class ViewController: UIViewController, CanvasDelegate
func showMessage(canvas: Canvas, message: String) {
let alertController = UIAlertController(title: message, message: "", preferredStyle: .Alert)
let okAction = UIAlertAction(title: "确定", style: .Default) { action in
// 此处通过尾随闭包来定义点击确定按钮后要做什么
canvas.clearBoard()
}
alertController.addAction(okAction)
self.presentViewController(alertController, animated: true, completion: nil)
}
// 6. 给画布对象绑定委托(self就是视图控制器对象它遵循了协议所以有充当委托的能力也就是说可以扮演被委托方的角色)
canvas.delegate = self
结构的对象是直类型,类的对象的引用类型,
前者是直接又创建了一个对象 后者是一个对象,地址两份而已。结构体会自动初始化,但是注意顺序。
结构中的方法在默认情况下是不允许修改结构中的属性除非加上mutating关键字
在Swift中同名函数只要参数列表不同是可以共存的 这个叫函数的重载
栈 (stack) - 我们定义的局部变量/临时变量都是放在栈上
- 特点: 小、快
堆 (heap) - 我们创建的对象都是放在堆上的 - 特点: 大、慢
day three
ARC自动释放
ARC即时性的内存清理 优于java自动清理内存释放学生对象(Garbage Collection 垃圾回收) ps:老师极力推介我们利用起ARC,比手动释放更好。
常用一下四个来释放内存,
1.nil
2.weak
3.引用转移
4.自动释放池
1.将stu1的值赋值给stu2和stu3,使他们的引用计数各自加一,也就是说这里共有3个引用计数,而当引用计数为零的时候ARC才会自动释放,这里我们将这三个为空,那么引用计数为零了,及就会被自动释放内存。这是为空的解决办法。
var stu1:Student? = Student()
var stu2 = stu1//+1
var stu3 = stu1//+1
stu1 = nil
stu2 = nil
stu3 = nil
2.weak就是虚弱的,就像一条虚弱的线,不会增加计数的。
var stu1: Student? = Student()
weak(弱引用) 修饰的引用不会增加引用计数 默认是strong(强引用)会增加计数
weak var stu2 = stu1
weak var stu3 = stu2
stu1 = nil
3.引用转移,就是利用了一个函数结束后局部变量就会消失的特点,然后引用计数为0,最后被ARC所释放掉
func foo(){
let stu = Student()
print(stu)
}
4.自动释放池,这里容易出现的问题不是其中主对象减一,而是涉及到的每个对象引用计数减一。
//autoreleasepool { () -> () in
// //自动释放池中的对象引用在池的边界引用计数会会收到引用计数-1的消息
// //将来做IOS开发时 如果某个地方会创建很多临时对象
// //那么最好在此处设置一个自动释放池避免内存瞬时峰值过高造成闪退
// let stu1 = Student()
// let stu2 = stu1
//
//}
//离开自动释放池的时候stu1会收到引用计数-1的消息
//stu2也会收到引用计数-1的消息
当然也有ARC无法释放的内存,那就是形成循环引用(类与类之间的相互关联),报错以这样的形式EXC-BAD-ACCESS
解决的办法有两个1.使用可空类型weak来破除
2.不可空类型的unowned来破除
推荐使用
如果允许使用可空类型通常使用weak来破除循环引用
如果员工关联的部门对象被释放了那么dept会被赋值为nil
如果要继续给dept对象发消息程序不会崩溃
weak var dept: Dept?
谨慎使用
如果不允许使用可空类型就必须使用unowned来破除循环引用
需要注意的是如果员工对象关联的部门对象被释放了
如果还要通过员工对象去操作它所关联的部门对象将导致程序崩溃
就用weak就行了,记得类打个?
三个构造器
1.指派构造器(designated)
2.便利构造器(convenience)
3.必要构造器(required)
这里值得注意的是必要构造器
所谓的必要构造器意味着子类也要提供一模一样的构造器
初始化的时候
想要给某些属性赋值,要在初始化完成后弄,不然会弄成不正确的值
init(){
}
convenience init() {
self.init(name: "无名氏", age: 20)
}
required init(name: String, age: Int) {
print("创建一个人!")
self.name = name
self.age = age
}
范型
让函数的参数类类型具有普遍性,专业点就是降耦合,Type(抽象化)让类型(具体化)变虚,更加灵活,根据传入的参数类型来决定T到底是什么
func mySwap<T>(inout a:T, inout _ b:T){
(a,b) = (b,a)
}
当然还涉及到一些做比较的情况
还要加上协议
范型限定 <T:Comparable>限定T类型必须是遵循了Comparable协议的类型
func myMin<T:Comparable>(a:T, _ b:T) -> T{
return a < b ? a : b
}
如果一个方法抛出了异常,那么我们在申明的时候必须要写上throws关键字
throws关键字是提醒方法的调用者方法可能会出现状况 调用时要写try
如果能够确保方法调用时不出异常 那么可以在try关键字加上!这表示在不写do catch的情况下 调用出状况的方法 throws可以不写出来
对于可能出状况的代码放在do...catch里面去执行
在可能出状态的方法前还要写上try表示尝试着执行
如果在do中没有出现任何状况 那么catch(捕获)就不会执行
如果do中出现了状况 就在出状况的地方停下来 不会继续向下执行 而是转移到catch中去。
在do的后面可以跟上多个catch用与捕获多种状况 但是最多只有一个catch会被执行
catch先小后大范围
如果能保证代码不出错可以在try后面!
如果不能保证代码是否出错可以在try后面?
需要注意的是有?的地方会产生optional
稍后可能还需要对可空类型进行拆封拆封方式有二:
1.不安全的做法:xxx!
2.安全的做法:用if let = xxx
// 定义一个遵循ErrorType协议的枚举
// 通过不同的case定义程序中可能出现的若干种异常状况
enum FractionError: ErrorType {
case ZeroDenominator // 分母为0
case DivideByZero // 除以0
}
总结
Swift学习总结
语言基础
程序是指令的集合,写程序就是写一系列的指令去控制计算机做我们想做的事情。
编译:将程序设计语言转换成计算机能够理解的机器语言或者某种中间代码的过程。
冯诺依曼体系结构的计算机:
- 使用二进制
- 程序存储执行
变量和常量
定义变量和常量是为了保存数据,变量和常量就是某种类型的值的存储空间。
var a: Int = 10
a = 100
var b: Int
b = 1000
var c = 10000
let d: Int = 10
// d = 100 // compiler error
let e = 1000
说明:1. Swift有非常强大的类型推断,所以定义变量或常量时如果可以的话应该直接使用类型推断不用手动指定类型;2. 如果可以的话应该尽可能使用常量而不是变量。
语言元素
var a: Int = 10
关键字:有特殊含义的单词
标识符:给变量、常量、函数、类、结构、协议、枚举、方法、属性等起的名字
- 字母(Unicode字符)、数字、下划线,数字不能开头
- 大小写敏感(区分大小写)
- 不能使用关键字做标识符
- 使用驼峰命名法(命名变量、常量、函数、方法、属性第一个单词小写,从第二个单词开始每个单词首字母大写;命名类、结构、协议、枚举每个单词首字母都要大写)
- 见名知意
- 命名私有的属性和方法时以下划线开头
运算符:Swift中的运算符其实都是函数
- 赋值运算符:=、+=、-=、……
- 算术运算符:+、-、*、/、%
- 比较运算符:==、!=、<、<=、>、>=
- 逻辑运算符:&&、||、!
- 条件运算符:?:
- 其他运算符:[]、.、??、?、!
字面(常)量:
- 整数字面量:10、1_234_567、0x10、0o10、0b10
- 小数字面量:123.45、1.2345e2、0xab.cdp2
- 字符字面量:"c"、"\n"、"\u{41}"、"\u{9a86}"
- 字符串字面量:"Hello"、"caf\u{e9}"
- 布尔字面量:true、false
- 空值字面量:nil
- 类型字面量:String.self、UILabel.self
分隔符:将不同的语言元素符号分开
说明:Swift中每个语句后面的分号是可写可不写的,写代码时尽量保证一行只有一条语句这样就可以省略掉分号。
分支和循环
分支
- if...else...
下面的程序实现了分段函数求值。
let x = 3.2
let y: Double
if x < -1 {
y = 3 * x + 5
}
else if x <= 1 {
y = 5 * x - 3
}
else {
y = 7 * x + 1
}
print("f(\(x))=\(y)")
- switch...case...default
下面的程序实现了将百分制的成绩转换成A-E的等级。
let score = 92.5
let level: String
switch score {
case 0..<60:
level = "E"
case 60..<70:
level = "D"
case 70..<80:
level = "C"
case 80..<90:
level = "B"
case 90...100:
level = "A"
default:
level = "输入错误"
}
print(level)
循环
- while
下面的程序实现了1-100求和。
var sum = 0
var i = 1
while i <= 100 {
sum += i
i += 1
}
print(sum)
- repeat...while...
下面的程序实现了1-100求和。
var sum = 0
var i = 1
repeat {
sum += i
i += 1
} while i <= 100
print(sum)
- for
下面的程序实现了1-100求和。
var sum = 0
for i in 1...100 {
sum += i
}
print(sum)
穷举法:穷尽所有可能性直到找到正确答案。
下面的程序实现了"百钱百鸡"问题的求解。
for x in 0...20 {
for y in 0...33 {
let z = 100 - x - y
if 5 * x + 3 * y + z / 3 == 100 && z % 3 == 0 {
print("公鸡: \(x), 母鸡: \(y), 小鸡: \(z)")
}
}
}
说明:在循环中可以使用break关键字来提前终止循环,也可以使用continue关键字使循环直接进入下一轮,但是应该尽量减少对break和continue的使用,因为它们不会让你的程序变得更好。
综合案例:Craps赌博游戏。
游戏规则:玩家摇两颗色子,如果第一次摇出了7点或11点,玩家胜;如果摇出2点、3点或12点,庄家胜;其他点数游戏继续。在继续的过程中玩家重新摇色子,如果摇出了7点,庄家胜;如果摇出了第一次摇的点数,玩家胜;否则玩家继续摇色子直到分出胜负。
func roll() -> Int {
return Int(arc4random_uniform(6)) + 1
}
let firstPoint = roll() + roll()
print("玩家摇出了\(firstPoint)点")
var needsGoOn = false
switch firstPoint {
case 7, 11: print("玩家胜!")
case 2, 3, 12: print("庄家胜!")
default: needsGoOn = true
}
while needsGoOn {
let currentPoint = roll() + roll()
print("玩家摇出了\(currentPoint)点")
if currentPoint == 7 {
print("庄家胜!")
needsGoOn = false
}
else if currentPoint == firstPoint {
print("玩家胜!")
needsGoOn = false
}
}
容器
数组
数组是使用连续的内存空间保存多个同类型的元素的容器,因为数组中的元素在内存中是连续的,所以可以使用下标运算来访问数组中的元素,实现随机存取。
- 创建数组
var array1: [Int] = []
var array2: Array<Int> = []
var array3 = [1, 2, 3, 4, 5]
var array4 = [Int](count: 5, repeatedValue: 0)
var array5 = Array<Int>(count: 5, repeatedValue: 0)
- 添加元素
array1.append(2)
array1.append(3)
array1.insert(1, atIndex: 0)
array1.insert(4, atIndex: array1.count)
array1 += [5]
array1 += [6, 7, 8]
- 删除元素
array1.removeAtIndex(2)
array1.removeFirst()
array1.removeFirst(2)
array1.removeLast()
array1.removeRange(1...2)
array1.removeAll()
- 修改元素
array3[0] = 100
array3[array3.count - 1] = 500
- 遍历数组
- 方式1
for i in 0..<array3.count {
print(array3[i])
}
- 方式2
for temp in array3 {
print(temp)
}
for temp in array3[1...3] {
print(temp)
}
说明:for-in循环是一个只读循环,这也就意味着再循环的过程中不能对数组中的元素进行修改
- 方式3
for (i, temp) in array3.enumerate() {
if i == 0 {
array3[i] = 1
}
print("\(i). \(temp)")
}
提醒:操作数组时最重要的是不要越界访问元素。数组对象的count属性表明了数组中有多少个元素,那么有效的索引(下标)范围是0到count-1。
数组中的元素也可以是数组,因此我们可以构造多维数组。最常见的是二维数组,它相当于是一个有行有列的数组,数组中的每个元素代表一行,该数组中的每个元素代表行里面的列。二维数组可以模拟现实世界中的表格、数学上的矩阵、棋类游戏的棋盘、2D游戏中的地图,所以在实际开发中使用非常广泛。
下面的程序是用二维数组模拟表格的例子。
func randomInt(min: UInt32, max: UInt32) -> Int {
return Int(arc4random_uniform(max - min + 1) + min)
}
let namesArray = ["关羽", "张飞", "赵云", "马超", "黄忠"]
let coursesArray = ["语文", "数学", "英语"]
var scoresArray = [[Double]](count: namesArray.count, repeatedValue: [Double](count: coursesArray.count, repeatedValue: 0))
for i in 0..<scoresArray.count {
for j in 0..<scoresArray[i].count {
scoresArray[i][j] = Double(randomInt(50, max: 100))
}
}
for (index, array) in scoresArray.enumerate() {
var sum = 0.0
for score in array {
sum += score
}
let avg = sum / Double(coursesArray.count)
print("\(namesArray[index])的平均成绩为: \(avg)")
}
for i in 0..<coursesArray.count {
var sum = 0.0
for row in 0..<scoresArray.count {
sum += scoresArray[row][i]
}
let avg = sum / Double(namesArray.count)
print("\(coursesArray[i])课的平均成绩为: \(avg)")
}
集合
集合在内存中是离散的,集合中的元素通过计算Hash Code(哈希码或散列码)来决定存放在内存中的什么位置,集合中不允许有重复元素。
- 创建集合
var set: Set<Int> = [1, 2, 1, 2, 3, 5]
- 添加和删除元素
set.insert(100)
set.remove(5)
set.removeFirst()
set.removeAll()
- 集合运算(交集、并集、差集)
var set1: Set<Int> = [1, 2, 1, 2, 3, 4, 5]
var set2: Set<Int> = [1, 3, 5, 7]
set1.intersect(set2)
set1.union(set2)
set1.subtract(set2)
字典
字典是以键值对的方式保存数据的容器,字典中的每个元素都是键值对组合,通过键可以找到对应的值。
- 创建字典
var dict = [
1: "hello",
2: "good",
3: "wonderful",
5: "delicious"
]
- 添加、删除、修改元素
dict[3] = "terrible"
dict[4] = "shit"
dict[5] = nil
- 遍历元素
for key in dict.keys {
print("\(key) ---> \(dict[key]!)")
}
for value in dict.values {
print(value)
}
for (index, value) in dict.enumerate() {
print("\(index). \(value.0) ---> \(value.1)")
}
重要操作
- 排序
- sort
- sortInPlace
说明:排序方法的参数是一个闭包(closure),该闭包的作用是比较数组中两个元素的大小。
let array = [23, 45, 12, 89, 98, 55, 7]
array.sort({ (one: Int, two: Int) -> Bool in
return one < two
})
array.sort({ (one, two) in one < two })
array.sort({ one, two in one < two })
array.sort({ $0 < $1 })
array.sort { $0 < $1 }
array.sort(<)
- 过滤
let array = [23, 45, 12, 89, 98, 55, 7]
// 筛选掉不满足条件的数据
let newArray = array.filter { $0 > 50 }
print(newArray) // [89, 98, 55]
- 映射
let array = [23, 45, 12, 89, 98, 55, 7]
// 通过映射对数据进行变换处理
let newArray = array.map { $0 % 10 }
print(newArray)
- 归约
let array = [23, 45, 12, 89, 98, 55, 7]
let result = array.reduce(0, combine: +)
print(result)
函数和闭包
函数是独立的可重复使用的功能模块,如果程序中出现了大量的重复代码,通常都可以将这部分功能封装成一个独立的函数。在Swift中,函数是"一等公民",可以作为类型来使用,也就是说函数可以赋值给一个变量或常量,可以将函数作为函数的参数或者返回值,还可以使用高阶函数。
func 函数名([参数1: 类型, 参数2: 类型, ...]) [throws|rethrows] [-> 返回类型] {
函数的执行体
[return 表达式]
}
- 外部参数名
- inout参数
- 可变参数列表
闭包就是没有名字的函数(匿名函数)或者称之为函数表达式(Lambda表达式),Objective-C中与之对应的概念叫block。如果一个函数的参数类型是函数我们可以传入一个闭包;如果一个函数的返回类型是函数我们可以返回一个闭包;如果一个类的某个属性是函数我们也可以将一个闭包表达式赋值给它。
{ ([参数列表]) [-> 返回类型] in 代码 }
面向对象编程(OOP)
基本概念
对象:接收消息的单元,对象是一个具体的概念。
类:对象的蓝图和模板,类是一个抽象概念。
消息:对象之间通信的方式,通过给对象发消息可以让对象执行对应的操作来解决问题。
四大支柱
抽象:定义类的过程就是一个抽象的过程,需要做数据抽象和行为抽象,数据抽象找到对象的属性(保存对象状态的存储属性),行为抽象找到对象的方法(可以给对象发的消息)。
封装:
- 观点1: 我们在类中写方法其实就是在封装API,方法的内部实现可能会很复杂,但是这些对调用这来说是不可见的,调用只能看到方法有一个简单清晰的接口。
- 观点2: 将对象的属性和操作这些属性的方法绑定在一起。
- 观点3: 隐藏一切可以隐藏的实现细节,只提供简单清晰的接口(界面)。
继承:
多态:
三个步骤
- 定义类
- 数据抽象
- 存储属性
- 行为抽象
- 方法(写到类里面的函数或者说跟对象绑定的行为就是方法)
- 对象方法:给对象发的消息
- 类方法:给类发的消息,与对象的状态无关的方法
- 计算属性
- 方法(写到类里面的函数或者说跟对象绑定的行为就是方法)
- 构造器
- 指派构造器
- 便利构造器(convenience)
- 必要构造器(required)
- 创建对象
- 给对象发消息
class Triangle {
var a: Double
var b: Double
var c: Double
init(a: Double, b: Double, c: Double) {
self.a = a
self.b = b
self.c = c
}
// 类方法(发给类的消息与对象状态无关)
// 此处的static也可以换成class作用相同
static func isValid(a: Double, _ b: Double, _ c: Double) -> Bool {
return a + b > c && b + c > a && c + a > b
}
// 对象方法(发给对象的消息与对象状态有关)
func perimeter() -> Double {
return a + b + c
}
}
let a = 1.0
let b = 2.0
let c = 3.0
// 在创建对象前先调用类方法判定给定的三条边能否构成三角形
// 类方法是发给类的消息所以不用创建对象直接通过类名调用
if Triangle.isValid(a, b, c) {
let t = Triangle(a: a, b: b, c: c)
// 对象方法是发给对象的消息要先创建对象才能调用
print(t.perimeter())
}
else {
print("无法创建三角形")
}
相关内容
枚举
结构(体)
总结:类和结构的区别到底有哪些?什么时候应该使用结构?什么时候应该使用类?
扩展(extension)
运算符重载
下标运算(subscript)
-
访问修饰符
- private
- internal
- public
面向协议编程(POP)
协议
protocol 协议名[: 父协议1, 父协议2, ...] {
// 方法的集合(计算属性相当于就是方法)
}
- 能力:
- 约定:
- 角色:
依赖倒转原则
用协议实现委托回调
一个对象想做某件事情但是自身没有能力做这件事情就可以使用委托回调,具体的步骤是:
- 设计一个协议,让被委托方遵循协议并实现协议中的方法
- 委托方有一个属性是协议类型的,通过该属性可以调用协议中的方法
注意:委托方的协议类型的属性通常是可空类型,因为要写成弱引用(weak)。
其他
- 协议组合:protocol<协议1, 协议2, ...>
- 可选方法
- 协议扩展:对协议中的方法给出默认实现
泛型
让类型不再是程序中的硬代码(hard code),可以设计出更通用的代码。
泛型函数
泛型类/结构/枚举
相关知识
- 泛型限定
- where子句
错误处理
enum MyError: ErrorType {
case A
case B
case C
}
- throw
- throws / rethrows
- do
- catch
- try
边角知识
- ARC
- 正则表达式
- 嵌套类型