[TOC]
協(xié)議
協(xié)議中全是抽象概念(只有聲明沒有實現(xiàn)), 遵循協(xié)議的類可以各自對協(xié)議中的計算屬性和方法給出自己的實現(xiàn)版本 ,這樣當我們面向協(xié)議編程時就可以把多態(tài)的優(yōu)勢發(fā)揮到淋漓盡致 ,可以寫出更通用更靈活的代碼(符合開閉原則)
實現(xiàn)開閉原則最關(guān)鍵有兩點:
- 抽象是關(guān)鍵(在設(shè)計系統(tǒng)的時候一定要設(shè)計好的協(xié)議);
- 封裝可變性(橋梁模式 - 將不同的可變因素封裝到不同的繼承結(jié)構(gòu)中)
協(xié)議是方法的集合(計算屬性相當于就是方法), 可以把看似不相關(guān)的對象的公共行為放到一個協(xié)議中。
協(xié)議在Swift開發(fā)中大致有三種作用:
- 能力 - 遵循了協(xié)議就意味著具備了某種能力
- 約定 - 遵循了協(xié)議就一定要實現(xiàn)協(xié)議中的方法
- 角色 - 一個類可以遵循多個協(xié)議, 一個協(xié)議可以被多個類遵循, 遵循協(xié)議就意味著扮演了某種角色, 遵循多個協(xié)議就意味著可以扮演多種角色
創(chuàng)建協(xié)議
protocol Flyable {
func fly()
}
protocol Fightable {
func fight()
}
協(xié)議的實現(xiàn)(一個類可以繼承多個協(xié)議)
class Superman: Fightable,Flyable {
func fight() {
print("超人正在打怪獸.")
}
func fly() {
print("超人使用超能力飛行.")
}
}
協(xié)議擴展 - 可以在協(xié)議擴展中給協(xié)議中的方法提供默認實現(xiàn)
也就是說如果某個類遵循了協(xié)議但是沒有實現(xiàn)這個方法就直接使用默認實現(xiàn)
那么這個方法也就相當于是一個可選方法(可以實現(xiàn)也可以不實現(xiàn))
extension Fightable {
func fight() {
print("正在打架")
}
}
協(xié)議的繼承(協(xié)議可以繼承多個協(xié)議)
protocol NiuBi: Flyable, Fightable {
func dive()
}
協(xié)議的組合
let array: [protocol<Flyable, Fightable>] = [
Superman()
]
for obj in array {
obj.fly()
obj.fight()
}
運行結(jié)果

依賴倒轉(zhuǎn)原則(面向協(xié)議編程)
- 聲明變量的類型時應(yīng)該盡可能使用協(xié)議類型
- 聲明方法參數(shù)類型時應(yīng)該盡可能使用協(xié)議類型
- 聲明方法返回類型時應(yīng)該盡可能使用協(xié)議類型
結(jié)構(gòu)
Swift中的類和結(jié)構(gòu)體定義的語法是非常相似的。類使用class關(guān)鍵詞定義類,使用struct關(guān)鍵詞定義結(jié)構(gòu)體,它們的語法格式如下:
class 類名 {
定義類的成員
}
struct 結(jié)構(gòu)體名 {
定義結(jié)構(gòu)體的成員
}
結(jié)構(gòu)與類的區(qū)別:
1: 結(jié)構(gòu)的對象是值類型, 類的對象是引用類型
- 值類型在賦值的時候會在內(nèi)存中進行對象的拷貝
- 引用類型在賦值的時候不會進行對象拷貝只是增加了一個引用
** 結(jié)論**: 我們自定義新類型時優(yōu)先考慮使用類而不是結(jié)構(gòu)除非我們要定義的是一種底層的數(shù)據(jù)結(jié)構(gòu)(保存其他數(shù)據(jù)的類型)
2: 結(jié)構(gòu)會自動生成初始化方法
3: 結(jié)構(gòu)中的方法在默認情況下是不允許修改結(jié)構(gòu)中的屬性除非加上mutating關(guān)鍵字
struct Student {
var name: String
var age: Int
func study(courseName: String) {
print("\\(name)正在學(xué)習(xí).")
}
mutating func getOlder() {
age += 1
}
}
委托
有的時候某個對象要做某件事情但其自身又沒有能力做這件事情
這個時候就可以使用委托回調(diào)的編程模式讓別的對象來做這件事情
以考試找槍手代考為例
實現(xiàn)委托回調(diào)的編程模式有以下幾個步驟:
1 設(shè)計一個協(xié)議(被委托方必須要遵循協(xié)議才能給別的對象當委托)
protocol ExamDelegate: class {
func answerTheQuestion()
}
class Student {
var name: String
weak var delegate: ExamDelegate?//2委托方添加一個屬性其類型是遵循了協(xié)議的被委托方
init(name: String) {
self.name = name
}
func joinExam() {
print("姓名: \\(name)")
delegate?.answerTheQuestion()
}
}
3自己做不了的事情委托給別的對象來做
class Gunman: ExamDelegate {//4讓類遵循協(xié)議成為被委托方(協(xié)議表能力)
func answerTheQuestion() {//5 遵循協(xié)議就必須要實現(xiàn)協(xié)議中的方法(協(xié)議表約定)
print("奮筆疾書各種答案")
}
}
6它遵循了協(xié)議所以有充當委托的能力也就是說可以扮演被委托方的角色
let stu = LazyStudent(name: "老王")
let gun = Gunman()
stu.delegate = gun
stu.joinExam()
運行結(jié)果

內(nèi)存管理
Swift 是自動管理內(nèi)存的,這也就是說,我們不再需要操心內(nèi)存的申請和分配。當我們通過初始化創(chuàng)建一個對象時,Swift 會替我們管理和分配內(nèi)存。而釋放的原則遵循了自動引用計數(shù) (ARC) 的規(guī)則:當一個對象沒有引用的時候,其內(nèi)存將會被自動回收。這套機制從很大程度上簡化了我們的編碼,我們只需要保證在合適的時候?qū)⒁弥每?(比如超過作用域,或者手動設(shè)為 nil 等)
如果程序中出現(xiàn)了類與類之間雙向關(guān)聯(lián)關(guān)系 必須要將其中一端設(shè)置為weak引用 否則將會形成循環(huán)引用導(dǎo)致ARC無法釋放內(nèi)存
推薦使用
- 如果允許使用可空類型通常使用weak來破除循環(huán)引用
- 如果員工關(guān)聯(lián)的部門對象被釋放了那么dept會被賦值為nil
- 如果要繼續(xù)給dept對象發(fā)消息程序不會崩潰
謹慎使用
- 如果不允許使用可空類型就必須使用unowned來破除循環(huán)引用
- 需要注意的是如果員工對象關(guān)聯(lián)的部門對象被釋放了
- 如果還要通過員工對象去操作它所關(guān)聯(lián)的部門對象將導(dǎo)致程序崩潰(EXC_BAD_ACCESS)
員工與部門為例
class Emp {
weak var dept: Dept?//weak 破除循環(huán)
init(dept: Dept) {
print("創(chuàng)建一個員工")
self.dept = dept
}
deinit { //在銷毀對象的時候調(diào)用
print("銷毀一個員工")
}
}
class Dept {
var manager: Emp?
init() {
print("創(chuàng)建一個部門")
}
deinit {
print("銷毀一個部門")
}
}
func bar() {
let dept = Dept()
let emp = Emp(dept: dept)
dept.manager = emp
}
bar()
泛型
泛型 (generic) - 讓類型不再是程序中的硬代碼, Swift中的類、結(jié)構(gòu)和枚舉都可以使用泛型
泛型限定<T: Comparable>限定T類型必須是遵循了Comparable協(xié)議的類型
定義一個虛擬類型T, 調(diào)用函數(shù)時根據(jù)傳入的參數(shù)類型來決定T到底是什么
func myMin<T: Comparable>(a: T, _ b: T) -> T {
return a < b ? a : b
}
class Student: Comparable {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
func ==(one: Student, two: Student) -> Bool {
return one.name == two.name
}
func <(one: Student, two: Student) -> Bool {
return one.name < two.name
}
func <=(one: Student, two: Student) -> Bool {
return one.name <= two.name
}
func >(one: Student, two: Student) -> Bool {
return one.name > two.name
}
func >=(one: Student, two: Student) -> Bool {
return one.name >= two.name
}
var stu1 = Student(name: "小強", age: 35)
var stu2 = Student(name: "狒狒", age: 18)
let minStu = myMin (stu1,stu2)
print(minStu.age)
異常處理
定義一個遵循ErrorType協(xié)議的枚舉
通過不同的case定義程序中可能出現(xiàn)的若干種異常狀況
enum FractionError: ErrorType {
case ZeroDenominator // 分母為0
case DivideByZero // 除以0
}
以分數(shù)為例
class Fraction {
private var _num: Int
private var _den: Int
var info: String {
get {
return _num == 0 || _den == 1 ? "\\(_num)" : "\\(_num)/\\(_den)"
}
}
// 如果一個方法拋出了異常 那么在聲明方法時必須要寫上throws關(guān)鍵字
// throws關(guān)鍵字是提醒方法的調(diào)用者方法可能會出狀況 調(diào)用時要寫try
init(num: Int, den: Int) throws {
_num = num
_den = den
if _den == 0 {
// 如果程序中出現(xiàn)問題就拋出錯誤(異常)
// 被throw關(guān)鍵字拋出的必須是遵循ErrorType協(xié)議的東西
throw FractionError.ZeroDenominator
}
else {
simplify()
normalize()
}
}
func add(other: Fraction) -> Fraction {
// 如果能夠確保方法調(diào)用時不出異常那么可以在try關(guān)鍵字后加!
// 這樣就可以在不寫do...catch的情況下調(diào)用可能出狀況的方法
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)
}
如果能夠保證代碼不出錯可以在try后面加!
如果不確定代碼是否出錯可以在try后面加?
需要注意的是有?的地方會產(chǎn)生Optional(可空類型)
稍后可能還需要對可空類型進行拆封, 拆封方式有二:
- 不安全的做法: xxx!
- 安全的做法: 用if let = xxx { }進行拆封
例
func foo() {
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("無效的分數(shù)無法進行加法運算")
}
}
foo()
對于可能出狀況的代碼要放在do...catch中執(zhí)行
在可能出狀況的方法前還要寫上try表示嘗試著執(zhí)行
如果在do中沒有出現(xiàn)任何狀況那么catch就不會執(zhí)行
如果do中出現(xiàn)了狀況代碼就不會再向下繼續(xù)執(zhí)行而是轉(zhuǎn)移到catch中
在do的后面可以跟上多個catch用于捕獲不同的異常狀況 但是最多只有一個catch會被執(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!!!")
}
catch FractionError.DivideByZero {
print("除以0是不行的!!!")
}
catch {//其他錯誤
print("出錯了! 我也不知道什么問題")
}