沉迷學(xué)習(xí),日漸消瘦,一天不學(xué)習(xí),渾身難受。
學(xué)習(xí)Swift已過(guò)一個(gè)月,已經(jīng)從零基礎(chǔ)到入門(mén),能夠逐漸理順?biāo)季w,找到關(guān)鍵點(diǎn)。

協(xié)議
- 協(xié)議是方法的集合(計(jì)算屬性相當(dāng)于就是方法),可以把看似不相關(guān)的對(duì)象的公共行為放到一個(gè)協(xié)議中
注釋: 協(xié)議在Swift開(kāi)發(fā)中大致有三種作用:
- 能力 - 遵循了協(xié)議就意味著具備了某種能力
- 約定 - 遵循了協(xié)議就一定要實(shí)現(xiàn)協(xié)議中的方法
- 角色 - 一個(gè)類(lèi)可以遵循多個(gè)協(xié)議, 一個(gè)協(xié)議可以被多個(gè)類(lèi)遵循, 遵循協(xié)議就意味著扮演了某種角色, 遵循多個(gè)協(xié)議就意味著可以扮演多種角色
Swift中的繼承是單一繼承(一個(gè)類(lèi)只能有一個(gè)父類(lèi)), 如果希望讓一個(gè)類(lèi)具備多重能力可以使用協(xié)議來(lái)實(shí)現(xiàn)(C++里面是通過(guò)多重繼承來(lái)實(shí)現(xiàn)的, 這是一種非常狗血的做法)
協(xié)議中全是抽象概念(只有聲明沒(méi)有實(shí)現(xiàn)) 遵循協(xié)議的類(lèi)可以各自對(duì)協(xié)議中的計(jì)算屬性和方法給出自己的實(shí)現(xiàn)版本 這樣當(dāng)我們面向協(xié)議編程時(shí)就可以把多態(tài)的優(yōu)勢(shì)發(fā)揮到淋漓盡致 可以寫(xiě)出更通用更靈活的代碼(符合開(kāi)閉原則)
實(shí)現(xiàn)開(kāi)閉原則最關(guān)鍵有兩點(diǎn):
- 抽象是關(guān)鍵(在設(shè)計(jì)系統(tǒng)的時(shí)候一定要設(shè)計(jì)好的協(xié)議);
- 封裝可變性(橋梁模式 - 將不同的可變因素封裝到不同的繼承結(jié)構(gòu)中)
注釋:接口(協(xié)議)隔離原則: 協(xié)議的設(shè)計(jì)要小而專(zhuān)不要大而全
協(xié)議的設(shè)計(jì)也要高度內(nèi)聚
protocol Flyable {
func fly()
}
protocol Fightable {
func fight()
}
協(xié)議擴(kuò)展
協(xié)議擴(kuò)展 - 可以在協(xié)議擴(kuò)展中給協(xié)議中的方法提供默認(rèn)實(shí)現(xiàn)
也就是說(shuō)如果某個(gè)類(lèi)遵循了協(xié)議但是沒(méi)有實(shí)現(xiàn)這個(gè)方法就直接使用默認(rèn)實(shí)現(xiàn)
那么這個(gè)方法也就相當(dāng)于是一個(gè)可選方法(可以實(shí)現(xiàn)也可以不實(shí)現(xiàn))
extension Fightable {
func fight() {
print("正在打架")
}
}
// 協(xié)議的繼承
protocol NiuBi: Flyable, Fightable {
func dive()
}
class Boxer: Fightable {
@objc func fight() {
print("正在進(jìn)行格斗.")
}
}
- 依賴(lài)倒轉(zhuǎn)原則(面向協(xié)議編程)
- 聲明變量的類(lèi)型時(shí)應(yīng)該盡可能使用協(xié)議類(lèi)型
- 聲明方法參數(shù)類(lèi)型時(shí)應(yīng)該盡可能使用協(xié)議類(lèi)型
- 聲明方法返回類(lèi)型時(shí)應(yīng)該盡可能使用協(xié)議類(lèi)型
et x: protocol<Flyable, Fightable> = Superman()
let y: NiuBi = Superman()
- 協(xié)議組合
let array: [protocol<Flyable, Fightable>] = []
計(jì)算機(jī)屬性:
計(jì)算機(jī)的硬件由五大部件構(gòu)成:
運(yùn)算器、控制器、存儲(chǔ)器、輸入設(shè)備、輸出設(shè)備
運(yùn)算器 + 控制器 => CPU (中央處理器)
存儲(chǔ)器 => 內(nèi)存 (RAM - Random Access Memory)
程序員可以使用的內(nèi)存大致分為五塊區(qū)域:
棧 (stack) - 我們定義的局部變量/臨時(shí)變量都是放在棧上
- 特點(diǎn): 小、快
堆 (heap) - 我們創(chuàng)建的對(duì)象都是放在堆上的 - 特點(diǎn): 大、慢
靜態(tài)區(qū) (static area) - 數(shù)據(jù)段 - 全局量
- 只讀數(shù)據(jù)段 - 常量
- 代碼段 - 函數(shù)和方法
值和類(lèi)型的區(qū)別:
- 區(qū)別1: 結(jié)構(gòu)的對(duì)象是值類(lèi)型, 類(lèi)的對(duì)象是引用類(lèi)型
值類(lèi)型在賦值的時(shí)候會(huì)在內(nèi)存中進(jìn)行對(duì)象的拷貝
引用類(lèi)型在賦值的時(shí)候不會(huì)進(jìn)行對(duì)象拷貝只是增加了一個(gè)引用
** 結(jié)論:** 我們自定義新類(lèi)型時(shí)優(yōu)先考慮使用類(lèi)而不是結(jié)構(gòu)除非我們要定義的是一種底層的數(shù)據(jù)結(jié)構(gòu)(保存其他數(shù)據(jù)的類(lèi)型)
引用類(lèi)型的類(lèi)
let stu1 = Student1(name: "駱昊", age: 35)
var stu3 = stu1 // 此處內(nèi)存中仍然只有一個(gè)學(xué)生對(duì)象
stu3.name = "羅小號(hào)"
stu3.age = 18
print(stu1.name)
print(stu1.age)
- 值類(lèi)型的結(jié)構(gòu)
區(qū)別2: 結(jié)構(gòu)會(huì)自動(dòng)生成初始化方法
et stu2 = Student2(name: "駱昊", age: 35)
var stu4 = stu2 // 此處內(nèi)存中會(huì)復(fù)制一個(gè)新的學(xué)生對(duì)象
stu4.name = "王大錘"
stu4.age = 18
print(stu2.name)
print(stu2.age)
- 區(qū)別3: 結(jié)構(gòu)中的方法在默認(rèn)情況下是不允許修改結(jié)構(gòu)中的屬性除非加上mutating關(guān)鍵字:
mutating func getOlder() {
age += 1
}
}
- 其他:
在Swift中同名函數(shù)只要參數(shù)列表不同是可以共存的 這個(gè)叫函數(shù)的重載
func changeName(inout name: String) {
name = "王大錘"
}
協(xié)議委托回調(diào):
有的時(shí)候某個(gè)對(duì)象要做某件事情但其自身又沒(méi)有能力做這件事情,這個(gè)時(shí)候就可以使用委托回調(diào)的編程模式讓別的對(duì)象來(lái)做這件事情
實(shí)現(xiàn)委托回調(diào)的編程模式有以下幾個(gè)步驟:
- 設(shè)計(jì)一個(gè)協(xié)議(被委托方必須要遵循協(xié)議才能給別的對(duì)象當(dāng)委托)
protocol CanvasDelegate: class {
協(xié)議里面的方法就是要委托其他對(duì)象做的事情
unc showMessage(canvas: Canvas, message: String)
}
- 委托方添加一個(gè)屬性其類(lèi)型是遵循了協(xié)議的被委托方
class Canvas: UIView {
// 2. 委托方添加一個(gè)屬性其類(lèi)型是遵循了協(xié)議的被委托方
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()
}
}
- 自己做不了的事情委托給別的對(duì)象來(lái)做
delegate?.showMessage(self, message: renjuBoard.isBlackTurn ? "白棋勝" : "黑棋勝")
其他:
Swift 2中的guard大法, Swift 3中據(jù)說(shuō)要廢掉
guard !isAutoMode else { return }
// guard !renjuBoard.isGameOver else { return }
- 索引器:
// 索引器語(yǔ)法 - 可以直接對(duì)棋盤(pán)對(duì)象做下標(biāo)運(yùn)算來(lái)放置棋子
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
}
指派構(gòu)造器:
指派構(gòu)造器(designated)
required init(name: String, age: Int) {
print("創(chuàng)建一個(gè)人!")
self.name = name
self.age = age
}
注釋:指派構(gòu)造器前面加上required可以將構(gòu)造器指定為必要構(gòu)造器
所謂的必要構(gòu)造器意味著子類(lèi)也要提供一模一樣的構(gòu)造器
- 下面的語(yǔ)句必須寫(xiě)在調(diào)用自己的初始化方法之后否則major屬性會(huì)被賦上不正確的值:
self.major = major
self.init(name: name, age: age)
self.major = major
初始化的第一階段
1. 初始化自己特有的屬性
self.major = major
// 子類(lèi)只能調(diào)用直接父類(lèi)的構(gòu)造器
// 子類(lèi)構(gòu)造器必須調(diào)用父類(lèi)的非便利構(gòu)造器(指派構(gòu)造器)
super.init() // compiler error
// 2. 調(diào)用父類(lèi)的初始化方法
super.init(name: name, age: age)
//初始化的第二階段
// 此處可以調(diào)用對(duì)象的方法因?yàn)閷?duì)象已經(jīng)完成了初始化
study()
}
func study() {
print("\(name)正在學(xué)習(xí).")
}
deinit {
print("學(xué)生對(duì)象嗝屁了!")
}
}
class Teacher: Person {
deinit {
print("老師對(duì)象嗝屁了!")
}
}
- 棧 - FILO 先進(jìn)后出的結(jié)構(gòu)
創(chuàng)建任何子類(lèi)對(duì)象的時(shí)候一定是先創(chuàng)建了父類(lèi)對(duì)象
var stu: Person = Student()
引用轉(zhuǎn)移(會(huì)導(dǎo)致原來(lái)對(duì)象上的引用計(jì)數(shù)-1 新對(duì)象引用計(jì)數(shù)+1)
stu = Teacher()
stu = Person()
- Swift的自動(dòng)釋放池
通過(guò)向autoreleasepool函數(shù)中傳入一個(gè)閉包來(lái)實(shí)現(xiàn)
autoreleasepool { () -> () in
自動(dòng)釋放池中的對(duì)象引用在池的邊界會(huì)收到引用計(jì)數(shù)-1的消息
> 將來(lái)做iOS開(kāi)發(fā)時(shí)如果某個(gè)地方會(huì)創(chuàng)建很多的臨時(shí)對(duì)象. 那么最好在此處設(shè)置一個(gè)自動(dòng)釋放池避免內(nèi)存瞬時(shí)峰值過(guò)高造成閃退
let stu1 = Student()
let stu2 = stu1
}
離開(kāi)自動(dòng)釋放池時(shí) stu1會(huì)收到引用計(jì)數(shù)-1消息 stu2也會(huì)收到引用計(jì)數(shù)-1消息
- 如果程序中出現(xiàn)了類(lèi)與類(lèi)之間雙向關(guān)聯(lián)關(guān)系 必須要將其中一端設(shè)置為weak引用,否則將會(huì)形成循環(huán)引用導(dǎo)致ARC無(wú)法釋放內(nèi)存
注釋:
推薦使用
如果允許使用可空類(lèi)型通常使用weak來(lái)破除循環(huán)引用
如果員工關(guān)聯(lián)的部門(mén)對(duì)象被釋放了那么dept會(huì)被賦值為nil
如果要繼續(xù)給dept對(duì)象發(fā)消息程序不會(huì)崩潰
weak var dept: Dept?
謹(jǐn)慎使用
如果不允許使用可空類(lèi)型就必須使用unowned來(lái)破除循環(huán)引用
需要注意的是如果員工對(duì)象關(guān)聯(lián)的部門(mén)對(duì)象被釋放了
如果還要通過(guò)員工對(duì)象去操作它所關(guān)聯(lián)的部門(mén)對(duì)象將導(dǎo)致程序崩潰
EXC_BAD_ACCESS
泛型 (generic)
- 讓類(lèi)型不再是程序中的硬代碼(寫(xiě)死的東西)
1.定義虛擬類(lèi)型:
// 定義一個(gè)虛擬類(lèi)型T, 調(diào)用函數(shù)時(shí)根據(jù)傳入的參數(shù)類(lèi)型來(lái)決定T到底是什么
func mySwap<T>(inout a: T, inout _ b: T) {
let temp = a
a = b
b = temp
}
2.泛型限定
// <T: Comparable>限定T類(lèi)型必須是遵循了Comparable協(xié)議的類(lèi)型
func myMin<T: Comparable>(a: T, _ b: T) -> T {
return a < b ? a : b
}
注釋:wift中的類(lèi)、結(jié)構(gòu)和枚舉都可以使用泛型:
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 }
}
}
拋錯(cuò)誤異常
- 如果一個(gè)方法拋出了異常 那么在聲明方法時(shí)必須要寫(xiě)上throws關(guān)鍵字
throws關(guān)鍵字是提醒方法的調(diào)用者方法可能會(huì)出狀況 調(diào)用時(shí)要寫(xiě)try:
init(num: Int, den: Int) throws {
_num = num
_den = den
if _den == 0 {
// 如果程序中出現(xiàn)問(wèn)題就拋出錯(cuò)誤(異常)
// 被throw關(guān)鍵字拋出的必須是遵循ErrorType協(xié)議的東西
throw FractionError.ZeroDenominator
}
else {
simplify()
normalize()
}
}
But:
1.如果能夠確保方法調(diào)用時(shí)不出異常那么可以在try關(guān)鍵字后加!
2.這樣就可以在不寫(xiě)do...catch的情況下調(diào)用可能出狀況的方法
注釋:如果能夠保證代碼不出錯(cuò)可以在try后面加!
1.如果不確定代碼是否出錯(cuò)可以在try后面加?
2.需要注意的是有?的地方會(huì)產(chǎn)生Optional(可空類(lèi)型)
稍后可能還需要對(duì)可空類(lèi)型進(jìn)行拆封, 拆封方式有二:
1. 不安全的做法: xxx!
2. 安全的做法: 用if let = xxx { }進(jìn)行拆封
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("無(wú)效的分?jǐn)?shù)無(wú)法進(jìn)行加法運(yùn)算")
}
}
foo
注釋:
1.對(duì)于可能出狀況的代碼要放在do...catch中執(zhí)行.
2.在可能出狀況的方法前還要寫(xiě)上try表示嘗試著執(zhí)行
3.如果在do中沒(méi)有出現(xiàn)任何狀況那么catch就不會(huì)執(zhí)行
4.如果do中出現(xiàn)了狀況代碼就不會(huì)再向下繼續(xù)執(zhí)行而是轉(zhuǎn)移到catch中
5.在do的后面可以跟上多個(gè)catch用于捕獲不同的異常狀況 但是最多只有一個(gè)catch會(huì)被執(zhí)行
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!!!")
總結(jié):
相對(duì)于這幾周的語(yǔ)法相對(duì)于比較枯燥和難一些,攻破了這個(gè)之后的路就能更為順暢一些,在之后的日子更要努力。