協議
1.協議:協議只給出方法的聲明,不給出具體方法的實現過程,協議是方法的集合(計算屬性相當于就是方法),誰遵循協議就要定義方法。
2.協議在Swift中的作用:
- 1.能力 - 遵循了協議就意味著具備了某種能力
- 2.約定 - 遵循了協議就一定要實現協議中的方法
- 3.角色 - 一個類可以遵循多個協議,一個協議也可以被多個類遵循,遵循協議就意味著扮演了某種角色,遵循了多個協議就意味著可以扮演多個角色
3.協議與繼承的區別在于:Swift中的繼承是單一繼承(一個類只能有一個父類),如果希望讓一個類具有多重能可以使用協議來實現。
例:我們先建一個Father的類,這個類有吃、喝、嫖、賭四種行為(方法)
class Father {
func eat(){
}
func drink(){
}
func wench(){
}
func gamble(){
}
}
然后我們再創建一個Monk的類,和一個Musician的類
protocol Monk {
func eatVegitable()
func chant()
func knockTheBell()
}
protocol Musician{
func playPiano()
func playViolin()
}
最后創建一個Son的類,它繼承了Father,并且遵循了Monk、Musician協議
class Son: Father, Monk, Musician {
override func gamble() {
print("正在斗地主")
}
func eatVegitable() {
}
func chant() {
}
func knockTheBell() {
}
func playPiano() {
}
func playViolin() {
}
func steal(){
}
}
這個Son類繼承于Father當然就擁有了它的屬性,同時它遵循了Monk、Musician協議,就擁有了它們的能力,但前提是必須要實現它們的方法。
所以當我們創建一個Son的對象它就擁有了如下的方法:
4.協議的繼承
5.協議的組合
let array: [protocol<Monk,Musician>] = [Son()]
當然協議也有多態。
6.協議的擴展
協議擴展 - 可以在協議擴展中給協議中的方法提供默認實現
也就是說如果某個類遵循了協議但是沒有實現這個方法就直接使用默認實現
那么這個方法也就相當于是一個可選方法(可以實現也可以不實現)
extension Fightable{
func fight(){
print("正在打架.")
}
}
總結:協議中全是抽象概念(只有聲明沒有實現),遵循協議的類可以各自對協議中的計算屬性和方法給出自己的實現版本,這樣當我們面向協議編程時就可以把多態的優勢發揮到淋漓盡致,可以寫出更通用更靈活的代碼(符合開閉原則)
- 實現開閉原則最關鍵有兩點:
- 1.抽象是關鍵(在設計系統的時候一定要設計好協議);
- 2.封裝可變性(橋梁模式- 將不同的可變因素封裝到不同的繼承結構中)
- 3.接口(協議)隔離原則:協議的設計一定要小而專不要大而全,協議的設計也要高內聚
7.委托回調和代理模式
關于這兩種方法我們可以舉一個考生找槍手代考的例子:
//槍手代考: 委托回調
protocol ExamDelegate: class {
func answerTheQuestion()
}
class LazyStudent {
var name: String
weak var delegate: ExamDelegate? //委托方添加一個屬性,其類型是遵循了協議的被委托方
init(name: String) {
self.name = name
}
func joinExam() {
print("姓名: \(name)")
delegate?.answerTheQuestion()
}
}
class Gunman: ExamDelegate {
func answerTheQuestion() {
print("奮筆疾書各種答案")
}
}
let stu = LazyStudent(name: "王大錘")
let gun = Gunman()
stu.delegate = gun
stu.joinExam()
//槍手代考: 代理模式
protocol ExamCandidate: class {
func answerTheQuestion()
}
class LazyStudent: ExamCandidate {
var name: String
init(name: String) {
self.name = name
}
func answerTheQuestion() {
}
}
class Gunman: ExamCandidate {
var name: String
var target: LazyStudent? //代理方添加一個屬性,其類型是被代理方
init(name: String) {
self.name = name
}
func answerTheQuestion() {
if let stu = target {
print("姓名: \(stu.name)")
print("奮筆疾書答案")
print("提交試卷")
}
}
}
let stu = LazyStudent(name: "王大錘")
let gun = Gunman(name: "駱昊")
gun.target = stu
gun.answerTheQuestion()
** 注意:**就學生和槍手這個例子而言,委托回調相當于學生只是帶了個作弊器,而代理模式才是真正的槍手幫學生代考,這兩種方法還是有一定的區別的。
結構體
1.我們分別創建一個類和一個結構體:
class Student1 {
var name: String
var age: Int
init (name: String, age: Int){
self.name = name
self.age = age
}
func study(courseName: String){
print("\(name)正在學習.")
}
}
struct Student2 {
var name: String
var age: Int
func study(courseName: String){
print("\(name)正在學習.")
}
mutating func getOlder(){
age += 1
}
}
2.創建對象,找出區別
let stu1 = Student1(name: "羅大號", age: 35)
var stu3 = stu1 //此處內存中仍然只有一個學生對象
stu3.name = "羅小號"
stu3.age = 18
print(stu1.name)
print(stu1.age)
執行結果為:
羅小號
18
Program ended with exit code: 0
創建結構體的對象:
let stu2 = Student2(name: "翠花", age: 26)
var stu4 = stu2 //此處內存中復制了一個學生對象
stu4.name = "荷花"
stu4.age = 18
print(stu2.name)
print(stu2.age)
執行結果為:
翠花
26
Program ended with exit code: 0
總結: 區別1:重點:結構的對象是值類型,類的對象是引用類型,值的類型在賦值的時候會在內存中進行對象的拷貝,引用類型在賦值的時候不會進行對象拷貝,只是增加了一個引用.
區別2: 結構會自動生成初始化方法
區別3: 結構中的方法在默認情況下是不允許修改結構中的屬性的,除非加上mutating
結論: 我們自定義新類型是優先考慮使用類而不是結構,除非我們要定義的是一種底層的數據結構
自動引用計數(ARC)
1.我們先創建幾個類:
class Person {
init(){
print("創建一個人")
}
deinit{
print("人嗝屁了")
}
}
class Teacher: Person {
override init() {
super.init()
print("創建一個老師")
}
deinit{
print("老師嗝屁")
}
}
class Student: Person{
override init() {
super.init()
print("創建一個學生對象!")
}
deinit {
print("學生對象嗝屁!")
}
}
說明:1.因為創建對象的方式多種多樣,所以可以定義多種初始化方法,對象就可以使用多種初始化方法。
2.子類只能調用直接父類的初始化方法,子類構造器必須調用父類的非便利構造器(指派構造器)
2.//創建一個學生對象,然后用stu1去引用它,所以此時學生對象引用計數為1
var stu1: Student? = Student()
//此處沒有創建新的學生對象,原來的學生對象的引用計數+1
var stu2 = stu1 var stu3 = stu1
//學生對象引用計數-1
stu1 = nil
//學生對象引用計數-1
stu2 = nil
//學生對象引用計數-1
//當學生對象引用計數為0時,ARC會自動清理內存釋放學生對象
//ARC即時性的內存清理 優于java的Garbage Collection(垃圾回收)
stu3 = nil
//弱(weak)修飾的引用不會導致自動引用計數增加,默認是強引用(會增加引用計數)
weak var stu2 = stu1 weak var stu3 = stu1
3.釋放內存的方法
- 1.如果想釋放內存,程序員可以手動將一個引用賦值成nil
stu1 = nil
- 2.stu是一個局部變量,在函數調用結束后局部變量就消失了,所以學生對象的引用計數也就變成0了,所以會被ARC釋放
func foo(){
let stu = Student()
print(stu)
}
foo()
結果如下:
創建一個人
創建一個學生對象!
Day08_17_01.Student
學生對象嗝屁!
人嗝屁了
Program ended with exit code: 0
- 3.引用轉移(會導致原來對象上的引用計數-1,新對象+1)
var stu: Person = Student()
stu = Teacher()
stu = Person()
打印結果如下:
創建一個人
創建一個老師
學生對象嗝屁!
人嗝屁了
創建一個人
老師嗝屁
人嗝屁了
Program ended with exit code: 0
從結果可以看出:創建子類對象的時候一定是先創建了父類對象;棧(FIOL)是先進后出的結構。
- 4.自動釋放池:通過向atuoreleasepool函數傳入一個閉包來實現
autoreleasepool { () -> () in
//自動釋放池中的對象引用在池的邊界引用計數會收到引用計數-1的消息
//將來做IOS開發是如果在某個地方會創建很多的臨時對象
//那么最好在此處設置一個自動釋放池避免內存瞬時峰值過高
let stu1 = Student()
let stu2 = stu1
}
離開自動釋放池時 stu1會收到引用計數-1的消息,stu2也會收到引用計數-1的消息
4.類與類之間的循環引用
我們來創建兩個類(部門和員工)相互引用:
class Dept{
weak var manager: Emp?
init(){
print("創建一個部門")
}
deinit{
print("銷毀一個部門")
}
}
class Emp {
var dept: Dept?
init(){
print("創建一個員工")
}
deinit{
print("銷毀一個員工")
}
- 如上形成循環引用,導致ARC無法釋放內存
解決方法:1.如果程序中出現了類與類之間的雙向關聯關系 必須將其中一端設置成weak引用,如果允許使用可空類型通常使用weak來破除循環引用
2.如果不允許使用可空類型就必須使用unowned來破除循環引用
class Emp {
weak var dept: Dept?
init(){
print("創建一個員工")
}
deinit{
print("銷毀一個員工")
}
或者這樣:
class Emp {
unowned var dept: Dept
init(dept: Dept){
self.dept = dept
print("創建一個員工")
}
deinit{
print("銷毀一個員工")
}
//需要注意的是如果員工對象關聯的部門對象被釋放了
//如果還要通過員工對象去操作它所關聯的部門對象將導致程序崩潰
// EXC_BAD_ACCESS
泛型
1.泛型(generic) - 讓類型不再是程序中的硬代碼(寫死的東西)
2.定義一個虛擬的類型T(也可以用其它字符代替),調用函數是根據傳入的參數類型來決定T到底是什么
func mySwap<T>(inout a: T, inout _ b: T){
(a,b) = (b,a)
}
var x = "hello", y = "good"
mySwap(&x, &y)
print(x,y) //結果:good hello
3.泛型限定
<T: Comparable>限定T類型必須是遵循了Comparable協議的類型
func myMin<T: Comparable>(a: T, _ b: T) -> T {
return a > b ? a : b
}
var z = "hello", s = "good"
print(myMin(z, s)) //打印結果:hello
上述代碼中虛擬類型T是不可以直接比較的,但是遵循了Comparable協議后就能比較了。