GeekBand-Swift 第三周筆記(泛型、函數類型、閉包)

泛型、函數類型、閉包

泛型

泛型是什么?簡單說來泛型就是泛指的類型,里面可以實例化任何你想要的類型。
比如我想寫個函數交換兩個數大小:

泛型函數

func exchange (x: inout Int, y: inout Int)
{
    let a = x
    x = y
    y = a 
}
var a = 55
var b = 22
print("a is \(a), b is \(b).")
//輸出結果:a is 55, b is 22.
exchange(x: &a, y: &b)
print("After exchange: a is \(a), b is \(b).")
//輸出結果:After exchange: a is 22, b is 55. 

好,看起來很完美,可是你有沒有發現這個函數只能交換兩個整數,如果要交換小數呢?也許你會說,這不簡單,把參數類型換成 Double 就好了嘛??墒侨绻阌窒虢粨Q兩個字符串呢?這樣換來換去不是個辦法,所以泛型自然應運而生。

func exchange<T>(x: inout T, y: inout T)
{
    let a = x
    x = y
    y = a
}
var a = "xiaoMing"
var b = "xiaoHong"
print("a is \(a), b is \(b).")
//輸出結果:a is xiaoMing, b is xiaoHong.
exchange(x: &a, y: &b)
print("After exchange: a is \(a), b is \(b).")
//輸出結果:After exchange: a is xiaoHong, b is xiaoMing.

我們把參數類型換成 T ,然后在函數名后面加一個尖括號,也寫上T ,實際上你可以寫上任何你想寫的字符,只要后面參數類型與它保持一致就行了。
一點點小改動,現在我們即可用來交換整數,又可以交換字符串啦。
現在看起來貌似很不錯了,可是如果你想比較 a 和 b 大小可能會碰到一點小問題。你如何能保證傳進去的參數支持 “<”、“>” 呢?幸好 Swift 早就想好這個問題了,所以引入了泛型約束。

泛型約束

這個怎么用,看代碼:

func max<T:Comparable>(array: [T]) -> T {
    var value=array[0]
    for index in 1..<array.count
    {
        if array[index]>value {
            value=array[index]
        }
    }
    return value
}
var data1=[1,6,3,8,5,2]
var data2=[12.3, 87.6, 77.8, 20.1, 50.2]
let m1=max(array: data1) // m1 = 8
let m2=max(array: data2) // m2 = 87.6

和泛型函數非常類似,我們只是添加了一個泛型的返回值,再讓函數名背后的泛型 T 遵循 Comparable 協議,就這么簡單?就這么簡單。
只要你傳進去的類型遵循 Comparable 協議,那你就可以隨意調用這個函數,大家可以點進去看看 Comparable 協議都實現了定義了哪些東西。這樣,只要我們想拿該類型做什么事,我們就讓他遵循什么協議就好了,如果你隨意傳個自己寫的類,那編譯器就會提示你錯了,非常方便。

  • 除了協議約束,還有基類約束,二者語法一樣,不再贅述。
  • Where 字句約束
func find<T:Container>(sequence :T, item:T.ItemType)->Bool where T.ItemType: Equatable{
 for index in 0..<sequence.count{
 if(sequence[index]==item){
            return true
        }
    }
    return false
} ```
這個奇奇怪怪的東西是什么?在了解它之前,我們得先知道協議的關聯類型。
#### 關聯類型

protocol Container {
associatedtype ItemType //關聯類型
func append(item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }
}

class Stack<T>: Container {

typealias ItemType=T

var items = [T]()

func push(item: T) {
    items.append(item)
}

func pop() -> T {
    return items.removeLast()
}

func append(item: T) {
    self.push(item: item)
}

var count: Int {
    return items.count
}
subscript(i: Int) -> T {
    return items[i]
}

} ```
哇,不要說你看不懂,我也看暈了,怎么又是 T 又是 ItemType ,這都是些什么???
不要慌,先看第一個 Container,它是一個協議。其實它的意思如下偽碼:

protocol Container<ItemType> {
 func append(item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}

是不是有點兒眉目了,這不就是泛型協議嗎,Swift 為啥還要畫蛇添足搞出一個關聯類型出來?稍安勿躁,蘋果這么做自然有他的好處了。
參考鏈接
我簡單總結下好處:

  • 讓代碼更簡潔清晰,特別是當你的協議需要定義多個泛型類型時
  • 可以使用“.”表達式,方便 where 約束
    好了,有了關聯類型,我們就可以像上方那樣使用 T.ItemType 的語法來用 where 約束了,是不是非常清晰明了,當你定義的 associatedtype 多了之后,好處就更加明顯了。

函數類型與閉包

基本概念

閉包是函數類型的實例,而函數類型就是一個指針,類似于類,棧上儲存指針,指向堆上的對象。


函數類型內存模型.png

乍一看有點兒犯迷糊?不要緊,一個個來分析。
函數類型是一個指針,它在堆上有兩個對象,這兩個對象也是指針。一個叫對象指針,一個叫函數指針。

  • 對象指針:這個指針并不是每個函數類型的實例都會有,對象指針,看名字就知道它指向的是一個對象,至于什么樣的函數類型擁有對象指針,咱們放到變量捕獲那兒講。
  • 函數指針:這個指向的就是這個函數類型里面的函數真正內存所在地
    函數類型怎么用呢?你可以將它用在任何使用類型的地方。例如:
  • 聲明一個變量 var addInt : (Int, Int) -> Int 現在你可以將任何參數是兩個 Int 返回值是一個 Int 的函數賦值給 addInt,就像使用一個變量去使用它,只不過現在該變量是一個函數。
  • 傳遞給某個函數作為一個參數
  • 將它作為某個函數的返回值
    這兩種不具體寫出來了,總之當做它是一個類型就可以了。

函數類型實例化

那既然是一個類型,可以被賦值,那它支持哪些函數給它賦值呢?

  • 全局函數
  • 嵌套函數
//嵌套函數
func algorithmFunction(symbol:String)-> (Double, Double)->Double{
    
    func add(x:Double, y:Double)->Double{
        return x+y
    }
    
    func minus(x:Double, y:Double)->Double{
        return x-y
    }
    
    func multiply(x:Double, y:Double)->Double{
        return x*y
    }
    
    func divide(x:Double, y:Double)->Double{
        return x/y
    }
    
    switch(symbol){
    case "+":
        return add
    case "-":
        return minus
    case "*":
        return multiply
    case "/":
        return divide
    default:
        return add
    }
}
var algorithm=algorithmFunction(symbol: "/")
let result4 = algorithm(600,80)   // result4 = 7.5
  • 成員函數(實例方法與靜態方法)

閉包

閉包就是函數類型實例,一樣可用于變量、參數、返回值,他倆內存模型一致,那為啥要叫這個為閉包呢?說到這里不得不談談捕獲了,在談捕獲前,想先介紹下內存泄漏。
內存在結構上可以分為堆和棧,棧上的內存由系統分配系統回收又迅捷又方便,不用我們操心。但棧的空間是極其有限的,所以必須要有堆來存放比較龐大的數據,在 Swift 中類和閉包的實例就存放在堆上并且受到 ARC 管理。
基礎數值類型、結構、枚舉、元組等不受 ARC 管理。
ARC 又叫自動引用計數管理機制,在堆上的對象只要有一個指針指向它,它的引用計數就加一,如圖:


引用計數示意.png

FileStream 此時的引用計數就為三,因為有三個指針指向它。當某個對象沒有指針引用的時候,表明此對象沒有任何利用價值了,ARC 會將引用計數降為零,并且銷毀這個對象,釋放所占內存。
好,目前看起來非常完美,perfect!可是,大家應該很快就會發現有個小問題:


循環引用.png

這是兩個類,每個類都有一個屬性,而這個屬性剛好又指向這兩個類。真是你中有我我中有你不可分離啊,代碼大概會是這樣:
import Foundation
class Computer{
    var name: String
    var display: Monitor?
    init(name:String){
        self.name=name
        print("Computer init")
    }
    deinit{
        print("Computer deinit")
    }
}
class Monitor{
    var no: Int
    var device: Computer?
    init(no:Int){
        self.no=no
        print("Monitor init")
    }
    deinit{
        print("Monitor deinit")
    }
}
var imac:Computer?
var screen:Monitor?
imac=Computer(name: "Jason's iMac")
screen=Monitor(no: 29)
imac!.display=screen
screen!.device=imac
//imac!.display=nil
imac=nil
screen=nil
/* print : 
Computer init
Monitor init */

?。∈遣皇巧倭它c什么,析構器怎么沒被調用?這就是經典的內存泄漏了??戳松厦娴膬却婺P?,相信大家對這段代碼為何會輸出如此結構就了解了,這兩個類對象還互相指著呢,引用計數都為一,怎么可能會被釋放呢?
還好,世界上的聰明人早就猜到會發生這種事所以,我們有了處理這種辦法的措施,那就是。。。手工賦值其中一個堆對象指針為 nil !哈,開個玩笑,其實這也是一種非常好的處理辦法,非常靈活,下面我們來看看更普適的方法。


內存泄漏處理辦法.png

解決辦法也著實簡單,只要我們將其中一個強引用指針換成弱引用就 OK 了,也就是說這個弱引用指針將不被算進 ARC 引用計數里去,這樣當棧上的兩個指針斷掉后,兩個對象循環引用自然被打破,如圖:


弱引用.png

有時人們并不想該對象可被賦值為 nil,所以我們又創造了一個無主引用,和弱引用一樣,只是無主引用聲明的對象不許為 nil:
無主引用.png

好了下面再來講捕獲。

捕獲

捕獲.png

這里重點要講的是捕獲生存周期小于閉包對象的參數和局部變量,因為其他的值談不上捕獲,頂多算使用而已,但是這二者卻截然不同,我們是真真正正的復制了他們封裝在臨時對象上,并且閉包的對象指針指向該臨時對象。還記得前面講函數類型時我說過不是所有函數類型的實例都會有這個對象指針嗎?
有對象指針有兩種情況,一是捕獲了類的實例成員,包括實例方法和實例屬性;二就是捕獲了參數或局部變量。第一種就是將該閉包的對象指針指向該類的實例對象,第二種則是將對象指針指向閉包自己創建的臨時對象。
并且,很重要一點,這個對象指針是強引用該對象,這樣,大家可能明白又要出事情了。還是先看內存模型吧:


捕獲內存模型.png

似乎并沒有什么不對,那我們現在思考一種情形。我是一個閉包,我捕獲了類的實例成員,這樣我的對象指針就將指向該實例對象;我是這個類,我定義了一個屬性,該屬性是這個閉包,這樣我的實例對象就將有一個屬性指針指向該閉包??创a:

class Employee{
    var name: String
    var printer:(()->())?  
 /*   lazy var printer: (()->())? = {
        print("name: \(self.name)")
    } */
   init(name:String){
        self.name=name
        self.printer = {
            print("name: \(self.name)")
        }
        print("Employee init")
    }
     deinit {
     print("Employee deinit")
    }
}
var employee:Employee?
employee=Employee(name: "Jason")
employee?.printer?()
//employee?.printer=nil
employee = nil
/* print : 
Employee init
name: Jason */

同樣析構器不會被調用,因為閉包和這個對象還兩兩相指呢,只有當我手動賦值該對象的閉包屬性為 nil 時,析構器才會調用。看圖:

閉包循環引用.png

那解決辦法大家應該也猜到了,同樣是聲明弱引用。
弱引用.png

只放出不同部分: self.printer = { [weak self] in print("name: \(self!.name)") }
弱引用內存圖.png

Weak-Strong Dance

這個有點兒詩意的名字是用來延長弱引用對象的生存周期的,別還沒等到你要用,對象就被系統給咔嚓了。

Weak-Strong Dance.png

下面運用了一點多線程知識,不詳講了,總之你要明白在代碼中弱引用是非常不安全的。在進行一系列操作時,可能等不到你要使用它,就被系統回收了它的內存。在本例中,注釋部分就是臨時轉換成一個強引用局部變量,沒有被注釋的代碼就是 withExtendedLifetime 函數了。

import UIKit
class Employee {
 var name: String
 init(name:String){
        self.name=name
        print("Employee init")
    }
func doClosure() {
        DispatchQueue.global().async { [weak self] in
        /*
            self?.process("first process")
            usleep(500)
            self?.process("second process")
            */
       /*
            if let strongRef=self {
                strongRef.process("first process")
                usleep(500)
                strongRef.process("second process")
            }*/
          withExtendedLifetime(self){
                self?.process(message: "first process")
                usleep(500)
                self?.process(message: "second process")
            }   
        }
    }
 deinit {
        print("Employee deinit")
    }
    func process(message: String) {
        print(message)
    }
}
var employee: Employee? = Employee(name:"Jason")
employee?.doClosure()
DispatchQueue.global().async {
    usleep(100)
    employee = nil
}
dispatchMain()
/* print :
Employee init
first process
second process
Employee deinit
*/

第三周作業

題目

請說出下面代碼存在的問題,以及改進方式。
class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
    let number: UInt64
    let customer: Customer
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card #\(number) is being deinitialized") }
}

答案

一個簡單的循環引用,不解釋了。

class Customer {
    let name: String
    weak var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
    let number: UInt64
    let customer: Customer
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card #\(number) is being deinitialized") }
}
var xiaoMing: Customer?
var xiaoMingID: CreditCard?
xiaoMing = Customer(name: "xiaoMing")
xiaoMingID = CreditCard(number: 522644, customer: xiaoMing!)
xiaoMing?.card = xiaoMingID
xiaoMing = nil
xiaoMingID = nil
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容