沉迷学习,日渐消瘦,一天不学习,浑身难受。
学习Swift已过一个月,已经从零基础到入门,能够逐渐理顺思绪,找到关键点。
协议
- 协议是方法的集合(计算属性相当于就是方法),可以把看似不相关的对象的公共行为放到一个协议中
注释: 协议在Swift开发中大致有三种作用:
- 能力 - 遵循了协议就意味着具备了某种能力
- 约定 - 遵循了协议就一定要实现协议中的方法
- 角色 - 一个类可以遵循多个协议, 一个协议可以被多个类遵循, 遵循协议就意味着扮演了某种角色, 遵循多个协议就意味着可以扮演多种角色
Swift中的继承是单一继承(一个类只能有一个父类), 如果希望让一个类具备多重能力可以使用协议来实现(C++里面是通过多重继承来实现的, 这是一种非常狗血的做法)
协议中全是抽象概念(只有声明没有实现) 遵循协议的类可以各自对协议中的计算属性和方法给出自己的实现版本 这样当我们面向协议编程时就可以把多态的优势发挥到淋漓尽致 可以写出更通用更灵活的代码(符合开闭原则)
实现开闭原则最关键有两点:
- 抽象是关键(在设计系统的时候一定要设计好的协议);
- 封装可变性(桥梁模式 - 将不同的可变因素封装到不同的继承结构中)
注释:接口(协议)隔离原则: 协议的设计要小而专不要大而全
协议的设计也要高度内聚
protocol Flyable {
func fly()
}
protocol Fightable {
func fight()
}
协议扩展
协议扩展 - 可以在协议扩展中给协议中的方法提供默认实现
也就是说如果某个类遵循了协议但是没有实现这个方法就直接使用默认实现
那么这个方法也就相当于是一个可选方法(可以实现也可以不实现)
extension Fightable {
func fight() {
print("正在打架")
}
}
// 协议的继承
protocol NiuBi: Flyable, Fightable {
func dive()
}
class Boxer: Fightable {
@objc func fight() {
print("正在进行格斗.")
}
}
- 依赖倒转原则(面向协议编程)
- 声明变量的类型时应该尽可能使用协议类型
- 声明方法参数类型时应该尽可能使用协议类型
- 声明方法返回类型时应该尽可能使用协议类型
et x: protocol<Flyable, Fightable> = Superman()
let y: NiuBi = Superman()
- 协议组合
let array: [protocol<Flyable, Fightable>] = []
计算机属性:
计算机的硬件由五大部件构成:
运算器、控制器、存储器、输入设备、输出设备
运算器 + 控制器 => CPU (中央处理器)
存储器 => 内存 (RAM - Random Access Memory)
程序员可以使用的内存大致分为五块区域:
栈 (stack) - 我们定义的局部变量/临时变量都是放在栈上
- 特点: 小、快
堆 (heap) - 我们创建的对象都是放在堆上的 - 特点: 大、慢
静态区 (static area) - 数据段 - 全局量
- 只读数据段 - 常量
- 代码段 - 函数和方法
值和类型的区别:
- 区别1: 结构的对象是值类型, 类的对象是引用类型
值类型在赋值的时候会在内存中进行对象的拷贝
引用类型在赋值的时候不会进行对象拷贝只是增加了一个引用
** 结论:** 我们自定义新类型时优先考虑使用类而不是结构除非我们要定义的是一种底层的数据结构(保存其他数据的类型)
引用类型的类
let stu1 = Student1(name: "骆昊", age: 35)
var stu3 = stu1 // 此处内存中仍然只有一个学生对象
stu3.name = "罗小号"
stu3.age = 18
print(stu1.name)
print(stu1.age)
- 值类型的结构
区别2: 结构会自动生成初始化方法
et stu2 = Student2(name: "骆昊", age: 35)
var stu4 = stu2 // 此处内存中会复制一个新的学生对象
stu4.name = "王大锤"
stu4.age = 18
print(stu2.name)
print(stu2.age)
- 区别3: 结构中的方法在默认情况下是不允许修改结构中的属性除非加上mutating关键字:
mutating func getOlder() {
age += 1
}
}
- 其他:
在Swift中同名函数只要参数列表不同是可以共存的 这个叫函数的重载
func changeName(inout name: String) {
name = "王大锤"
}
协议委托回调:
有的时候某个对象要做某件事情但其自身又没有能力做这件事情,这个时候就可以使用委托回调的编程模式让别的对象来做这件事情
实现委托回调的编程模式有以下几个步骤:
- 设计一个协议(被委托方必须要遵循协议才能给别的对象当委托)
protocol CanvasDelegate: class {
协议里面的方法就是要委托其他对象做的事情
unc showMessage(canvas: Canvas, message: String)
}
- 委托方添加一个属性其类型是遵循了协议的被委托方
class Canvas: UIView {
// 2. 委托方添加一个属性其类型是遵循了协议的被委托方
weak var delegate: CanvasDelegate?
var renjuBoard = RenjuBoard()
var isAutoMode = false
func clearBoard() {
renjuBoard.reset()
setNeedsDisplay()
}
func randomMove() {
let row = Int(arc4random_uniform(15))
let col = Int(arc4random_uniform(15))
if renjuBoard[row, col] {
renjuBoard[row, col] = renjuBoard.isBlackTurn
setNeedsDisplay()
}
}
- 自己做不了的事情委托给别的对象来做
delegate?.showMessage(self, message: renjuBoard.isBlackTurn ? "白棋胜" : "黑棋胜")
其他:
Swift 2中的guard大法, Swift 3中据说要废掉
guard !isAutoMode else { return }
// guard !renjuBoard.isGameOver else { return }
- 索引器:
// 索引器语法 - 可以直接对棋盘对象做下标运算来放置棋子
subscript(row: Int, col: Int) -> Bool {
get { return board[row][col] == .Space }
set(isBlack) {
if board[row][col] == .Space {
board[row][col] = isBlack ? .Black : .White
isBlackTurn = !isBlackTurn
}
指派构造器:
指派构造器(designated)
required init(name: String, age: Int) {
print("创建一个人!")
self.name = name
self.age = age
}
注释:指派构造器前面加上required可以将构造器指定为必要构造器
所谓的必要构造器意味着子类也要提供一模一样的构造器
- 下面的语句必须写在调用自己的初始化方法之后否则major属性会被赋上不正确的值:
self.major = major
self.init(name: name, age: age)
self.major = major
初始化的第一阶段
1. 初始化自己特有的属性
self.major = major
// 子类只能调用直接父类的构造器
// 子类构造器必须调用父类的非便利构造器(指派构造器)
super.init() // compiler error
// 2. 调用父类的初始化方法
super.init(name: name, age: age)
//初始化的第二阶段
// 此处可以调用对象的方法因为对象已经完成了初始化
study()
}
func study() {
print("\(name)正在学习.")
}
deinit {
print("学生对象嗝屁了!")
}
}
class Teacher: Person {
deinit {
print("老师对象嗝屁了!")
}
}
- 栈 - FILO 先进后出的结构
创建任何子类对象的时候一定是先创建了父类对象
var stu: Person = Student()
引用转移(会导致原来对象上的引用计数-1 新对象引用计数+1)
stu = Teacher()
stu = Person()
- Swift的自动释放池
通过向autoreleasepool函数中传入一个闭包来实现
autoreleasepool { () -> () in
自动释放池中的对象引用在池的边界会收到引用计数-1的消息
> 将来做iOS开发时如果某个地方会创建很多的临时对象. 那么最好在此处设置一个自动释放池避免内存瞬时峰值过高造成闪退
let stu1 = Student()
let stu2 = stu1
}
离开自动释放池时 stu1会收到引用计数-1消息 stu2也会收到引用计数-1消息
- 如果程序中出现了类与类之间双向关联关系 必须要将其中一端设置为weak引用,否则将会形成循环引用导致ARC无法释放内存
注释:
推荐使用
如果允许使用可空类型通常使用weak来破除循环引用
如果员工关联的部门对象被释放了那么dept会被赋值为nil
如果要继续给dept对象发消息程序不会崩溃
weak var dept: Dept?
谨慎使用
如果不允许使用可空类型就必须使用unowned来破除循环引用
需要注意的是如果员工对象关联的部门对象被释放了
如果还要通过员工对象去操作它所关联的部门对象将导致程序崩溃
EXC_BAD_ACCESS
泛型 (generic)
- 让类型不再是程序中的硬代码(写死的东西)
1.定义虚拟类型:
// 定义一个虚拟类型T, 调用函数时根据传入的参数类型来决定T到底是什么
func mySwap<T>(inout a: T, inout _ b: T) {
let temp = a
a = b
b = temp
}
2.泛型限定
// <T: Comparable>限定T类型必须是遵循了Comparable协议的类型
func myMin<T: Comparable>(a: T, _ b: T) -> T {
return a < b ? a : b
}
注释:wift中的类、结构和枚举都可以使用泛型:
struct Stack<T> {
var data: [T] = []
// 入栈
mutating func push(elem: T) {
data.append(elem)
}
// 出栈
mutating func pop() -> T {
return data.removeLast()
}
var isEmpty: Bool {
get { return data.count == 0 }
}
}
抛错误异常
- 如果一个方法抛出了异常 那么在声明方法时必须要写上throws关键字
throws关键字是提醒方法的调用者方法可能会出状况 调用时要写try:
init(num: Int, den: Int) throws {
_num = num
_den = den
if _den == 0 {
// 如果程序中出现问题就抛出错误(异常)
// 被throw关键字抛出的必须是遵循ErrorType协议的东西
throw FractionError.ZeroDenominator
}
else {
simplify()
normalize()
}
}
But:
1.如果能够确保方法调用时不出异常那么可以在try关键字后加!
2.这样就可以在不写do...catch的情况下调用可能出状况的方法
注释:如果能够保证代码不出错可以在try后面加!
1.如果不确定代码是否出错可以在try后面加?
2.需要注意的是有?的地方会产生Optional(可空类型)
稍后可能还需要对可空类型进行拆封, 拆封方式有二:
1. 不安全的做法: xxx!
2. 安全的做法: 用if let = xxx { }进行拆封
let f1 = try? Fraction(num: 3, den: 0)
let f2 = try? Fraction(num: 0, den: 9)
if let a = f1, b = f2 {
let f3 = a + b
print(f3.info)
}
else {
print("无效的分数无法进行加法运算")
}
}
foo
注释:
1.对于可能出状况的代码要放在do...catch中执行.
2.在可能出状况的方法前还要写上try表示尝试着执行
3.如果在do中没有出现任何状况那么catch就不会执行
4.如果do中出现了状况代码就不会再向下继续执行而是转移到catch中
5.在do的后面可以跟上多个catch用于捕获不同的异常状况 但是最多只有一个catch会被执行
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!!!")
总结:
相对于这几周的语法相对于比较枯燥和难一些,攻破了这个之后的路就能更为顺畅一些,在之后的日子更要努力。