窺探 Swift 之 函數與閉包的應用實例

jopen 8年前發布 | 12K 次閱讀 閉包 Swift Apple Swift開發

今天的博客算是比較基礎的,還是那句話,基礎這東西在什么時候都是最重要的。說到函數,只要是寫過程序就肯定知道函數是怎么回事,今天就來討論一下Swift中的函數的特性以及Swift中的閉包。今天的一些小實例中回類比一下Objective-C中的函數的寫法等等。Swift中的函數還是有許多好用的特性的,比如輸入參數,使用元組返回多個值, 定義形參名,設定默認參數以及可變參數等等一些好用的特性。而在Swift中的閉包就是Objective-C中的Block, 除了語法不通外,兩者的用法是一樣的。廢話少說,開始今天的主題,先搞一搞Swift中的函數,然后在搞一搞Swift中的閉包。

一.Swift中的函數

1. 函數的定義與使用

在介紹Swift中的函數之前,我想用Objective-C中的一個簡單的加法函數來作為引子,然后類比著實現一下Swift中相同功能的函數。關于函數定義就比較簡單了,就是一些語法的東西,下面的代碼片段是Objc中求兩個整數之和的函數,并返回兩個數的和。

- (NSInteger)sumNumber1:(NSInteger) number1
                number2:(NSInteger) number2 {    return number1 + number2;
}

函數的功能比較簡單了,就是把兩個整數傳進來,然后返回兩個整數的和。接下來將用Swift語言實現,也好通過這個實例來熟悉一下Swift語言中定義函數的語法。下方是Swift語言中求兩個整數之和的函數。語法比較簡單了,在Swift中定義函數,我們會使用到關鍵字 func 來聲明函數。

//函數定義
 func sum (number1:Int, number2:Int) -> Int{
     return number1 + number2;
 }

用文字來描述Swift定義基本函數的語法就是: func 函數名 (形參 列表 ) -> 返回值類型 { 函數體 }, 這樣你就可以定義一個函數了。當然,函數定義時還有好多其他的用法,下面會詳細介紹。上面函數的調用方法如下:

let sumTwoNubmer = sum(2, number2: 3);

2. 函數中的形參列表

關于函數中的形參列表還是有必要提上一嘴的,因為形參列表作為函數數據源之一,所以把參數列表好好的搞一搞還是很有必要的。參數列表也有很多好用的使用方式,接下來詳細的介紹一下函數的形參列表。

(1) 默認的形參是常量(let)

在函數的形參列表中,默認的形參是常量。也就是相當于用 let 關鍵字對形參進行修飾了。我們可以做個試驗,把上面加法函數做一個修改,在加法函數中對number1進行加1操作,你會得到一個錯誤,這個錯誤的大體意思就是“number1是不可被修改的,因為它是let類型的常量”。并且編譯器還給人出了Fix-it(修復)的方案,就是在number1前面使用 var 關鍵字進行修飾,使其成為變量,這樣才可以修改其值。

上面說這么多,一句話:形參默認是常量,如果你想讓其是變量,那么你可以使用var關鍵字進行修飾,這樣被關鍵字var修飾的變量在函數中就可以被修改。下方就是報的這個錯誤,和編譯器提供的解決方案。(在Objc中默認可以在函數中改變形參的值)

(2)給形參命名

為了代碼的可讀性和可維護性,我們在定義函數時,需要為每個參數名一個名字,這樣調用者見名知意,很容易就知道這個參數代表什么意思了。接下來還是在上述加法函數中進行修改,為每個參數名一個名字,并看一下調用方式。修改上面的函數,給第一個形參命名成numberOne, 第二個形參為numberTwo, 下方是修改后的函數。 緊接著sum()函數的調用方式也會有所改變,在調用函數時編譯器會給出參數的名稱,這樣調用者一目了然。

//函數定義
func sum (numberOne number1:Int, numberTwo number2:Int) -> Int{
    return number1 + number2;
}

let sumTwoNubmer = sum(numberOne: 10, numberTwo: 20);

調用上述函數時,下方是編譯器給出的提示,一目了然呢。

關于Swift中參數名的內容,要說明的是在Swift1.0的時候,你可以在參數前面添加上#號,然后參數名就與變量(或者常量)的名字相同,而Swift2.0后這個東西去掉了,因為默認就相當于Swift1.0中添加#號。

(3) 函數的傳參與傳引用

先暫且這么說著,在C語言的函數中可以給函數傳入參數,或者傳入實參的內存地址就是所謂的傳引用。如果傳入的是引用的話,在函數中對值進行修改的話,那么出了函數,這個被修改的值是可以被保留的。在Swift中也是可以的,不過你需要使用inout關鍵字修飾形參,并且在使用該函數時,用&來修飾。這一點和C語言中類似,&就是取地址符。下方是inout使用的一個小實例。

 func incrementStepTwo (inout myNumber:Int) {
     myNumber += 2
 }
 var myTestNumber = 6
 incrementStepTow(&myTestNumber)  //myTestNumber = 8

myTestNumber變量經過incrementStepTwo()函數后,其值就會增加2。當然前提是myTestNumber是變量,如果myTestNumber是常量的話,那么對不起,調用該函數就會報錯,下面是把var改成let后IDE給的錯誤提示。錯誤原因很顯然是你動了一個不該動的值,也就是常量不可再次被修改的。

(4) 不定參數函數

不定參數函數也就是形參的個數是不定的,但是形參的類型必須是相同的。不定形參在使用時怎么取呢?不定個數的形參實際上是一個數組,我們可以通過for循環的形式來遍歷出每個形參的值,然后使用就可以了。下方incrementMultableAdd()函數的形參的個數是不定的,其功能是求多個整數的和。在函數中我們只需遍歷每個參數,然后把每個參數進行相加,最后返回所求的和即可。函數比較簡單,再此就不在啰嗦了。

(5) 默認形參值

在Swift語言中是支持給形參賦初始值的,這一點在其他一些編程語言中也是支持的。但是Objective-C這么看似古老的語言中就不支持給形參指定初始值,在Swift這門現代編程語言中是支持這一特性的。默認參數要從參數列表后開始為參數指定默認值,不然就會報錯。下方就是為函數的形參指定默認參數的示例。一個表白的方法sayLove(), 形參youName默認是“山伯”, 形參loverName默認是“英臺”。 緊接著是sayLove函數的三種不同的調用方式,在調用函數時你可以不傳參數,可以傳一個參數,當然傳兩個也是沒問題的。

因為函數的每個參數都是有名字的,在含有默認參數的函數調用時,可以給任意一個參數進行傳值,其他參數取默認值,這也是Swift的一大特色之一,具體請看如下簡單的代碼示例:

3.函數類型

每個函數都有自己的所屬類型,函數類型說白了就是如果兩個函數參數列表相同以及返回值類型相同,那么這兩個函數就有著相同的函數類型。在Swift中可以定義一個變量或者常量來存儲一個函數的類型。接下來將用過一個實例還介紹一下函數類型是個什么東西。

(1) 首先創建兩個函數類型相同的函數,一個函數返回兩個整數的差值,另一個函數返回兩個整數的乘積。當然這兩個函數比較簡單,直接上代碼:

//現定義兩個函數類型相同的函數
func diff (number1:Int, number2:Int) -> Int {
    return number1 - number2;
}

func mul (number1:Int, number2:Int) -> Int {
    return number1 * number2;
}

(2) 函數定義好后,接著要定義個一個枚舉來枚舉每種函數的類型,下面定義這個枚舉在選擇函數時會用到,枚舉定義如下:

 //定義兩種計算的枚舉類型
 enum CountType:Int {
     case DiffCount = 0
     case MulCount
 }

(3) 接下來就是把(1)和(2)中定義的東西通過一個函數來組合起來。說白了,就是定義個函數來通過枚舉值返回這個枚舉值所對應的函數類型。有時候說多了容易犯迷糊,就直接上代碼得了。下方函數的功能就是根據傳進來的枚舉值來返回相應的函數類型。

//選擇類型的函數,并返回相應的函數類型
func choiseCountType(countType:CountType) -> ((Int, Int) -> Int) {
    //函數類型變量
    var myFuncType:(Int, Int) -> Int
    
    switch countType {
    case .DiffCount:
        myFuncType = diff
    case .MulCount:
        myFuncType = mul
    }
    return myFuncType;
}

(4) 接下來就是使用(3)中定義的函數了,首先我們需要定義一個相應函數類型( ( Int , Int ) -> Int )的變量來接收choiseCountType()函數中返回的函數類型,然后調用該函數類型變量,在Playground中執行的結果如下:

4.函數嵌套

我們可以把 3 中的代碼使用函數嵌套進行重寫,在Swift中是支持函數嵌套的。 所以可以吧3.1和3.2中的函數放到3.3函數中的,所以我們可以對上述代碼使用函數嵌套進行重寫。使用函數嵌套重寫后的代碼如下所示,當然,choiseCountType()函數的調用方式沒用發生改變,重寫后的調用方式和3.4中的調用方式是一樣一樣的。

//選擇類型的函數,并返回相應的函數類型
func choiseCountType(countType:CountType) -> ((Int, Int) -> Int) {
    
    //現定義兩個函數類型相同的函數
    func diff (number1:Int, number2:Int) -> Int {
        return number1 - number2;
    }
    
    func mul (number1:Int, number2:Int) -> Int {
        return number1 * number2;
    }

    
    //函數類型變量
    var myFuncType:(Int, Int) -> Int
    
    switch countType {
    case .DiffCount:
        myFuncType = diff
    case .MulCount:
        myFuncType = mul
    }
    return myFuncType;
}

二. 閉包

說道Swift中的閉包呢,不得不提的就是Objective-C中的Block, 其實兩者是一個東西,使用方式以及使用場景都是相同的。我們完全可以類比著Objective-C中的Block來介紹一下Swift中的Closure(閉包)。其實就是匿名函數。接下來的這段內容,先介紹一下Swift中Closure的基本語法,然后在類比著ObjC中的Block窺探一下Closure的使用場景。

1.Closure變量的聲明

Closure就是匿名函數,我們可以定義一個閉包變量,而這個閉包變量的類型就是我們上面介紹的“函數類型”。定義一個閉包變量其實就是定義一個特定函數類型的變量,方式如下。因為Closure變量沒有賦初始值,所以我們把其聲明為可選類型的變量。在使用時,用!強制打開即可。

var myCloure0:((Int, Int) -> Int)?

除了上面的方式外,我們還用另一種常用的聲明閉包變量的方式。那就是使用關鍵字typealias定義一個特定函數類型,我們就可以拿著這個類型去聲明一個Closure變量了,如下所示

 //定義閉包類型 (就是一個函數類型)
 typealias MyClosureType = (Int, Int) -> Int
 var myCloure:MyClosureType?

2. 給Closure變量賦值

給Closure變量賦值,其實就是把一個函數體賦值給一個函數類型的變量,和函數的定義區別不大。但是給閉包變量賦值的函數體中含有參數列表,并且參數列表和真正的函數體之間使用 關鍵字in 來分割。 閉包可選變量的調用方式與普通函數沒什么兩樣,唯一不同的是這個函數需要用!來強制打開才可以使用。賦值和調用方式如下。

3. 閉包回調的應用實例

暫且先稱作閉包回調吧,其實就是Objc中的Block回調。在Swift中的閉包回調和Objc中的Block回調用法一致,下方將會通過一個實例來介紹一下閉包的應用之一。下方會創建兩個視圖控制器,我們暫且稱為FirstViewController和SecondViewController。在FirstViewController上有一個Label和一個Button, 這個Button用來跳轉到SecondViewController, 而這個Label用來顯示從SecondViewController中回調過來的值。 而SecondViewController也有一個TextField和一個Button, 點擊Button就會把輸入框中的值通過閉包回調回傳到FirstViewController然后在FirstViewController上的Label顯示。

(1) 構建這個實例的第一步要做的就是使用Stroyboard把我們所需的控件布局好,并且管理相應的類。當然我們這個Demo的重點不在于如何去布局控件,如何去關聯控件,以及如何去使用控件,所以上述的這些就不做贅述了。這個實例的重點在于如何使用Closure實現值的回調。下方是我們的控件布局和目錄結構的截圖,從Storyboard上的控件來看,功能也就一目了然了。點擊“FirstViewController” 上的“Go SecondViewController”按鈕,就會跳轉到 “SecondViewController” 。 在SecondViewController視圖上的輸入框輸入數值,點擊Back按鈕返回到FirstViewController, 同時把輸入框中的文本通過閉包回調的形式回傳過來在FristViewController的label上顯示。大致就這個簡單的功能。

(2)FirstViewController.swift中的內容

FirstViewController.swift中的內容比較簡單,就關聯一個Label控件和一個按鈕點擊的事件,點擊按鈕就會跳轉到SecondViewController,具體代碼如下,在此就不啰嗦了,請看代碼中的注釋。下方代碼重要的一點是在跳轉到SecondViewController時要實現其提供的閉包回調,以便接受回傳過來的值。

//
//  FirstViewController.swift
//  SwiftDemo
//
//  Created by Mr.LuDashi on 15/11/18.
//  Copyright ? 2015年 ZeluLi. All rights reserved.
//

import UIKit

class FirstViewController: UIViewController {

    @IBOutlet var showTextLabel: UILabel!       //展示回調過來的文字信息
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    //點擊按鈕跳轉到SecondViewController
    @IBAction func tapGoSecondViewControllerButton(sender: UIButton) {
        //從Storyboard上加載SecondViewController
        let secondVC = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("SecondViewController")as! SecondViewController
        
        //實現回調,接收回調過來的值
        secondVC.setBackMyClosure { (inputText:String) -> Void in
            self.showTextLabel.text = inputText
        }
        
        //push到SecondViewController
        self.navigationController?.pushViewController(secondVC, animated: true)
    }
}

(3) SecondViewController.swift中的內容

SecondViewController.swift中的內容也不麻煩,就是除了關聯控件和事件外,還定義了一個閉包類型(函數類型),然后使用這個特定的函數類型聲明了一個此函數類型對應的變量。我們可以通過這個變量來接受上個頁面傳過來的閉包體,從而把用戶輸入的值,通過這個閉包體回傳到上個頁面。具體代碼實現如下:

//
//  SecondViewController.swift
//  SwiftDemo
//
//  Created by Mr.LuDashi on 15/11/18.
//  Copyright ? 2015年 ZeluLi. All rights reserved.
//

import UIKit

typealias InputClosureType = (String) -> Void   //定義閉包類型(特定的函數類型函數類型)

class SecondViewController: UIViewController {
    
    @IBOutlet var inputTextField: UITextField!  //輸入框,讓用戶輸入值,然后通過閉包回調到上一個頁面
    
    var backClosure:InputClosureType?           //接收上個頁面穿過來的閉包塊
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    //閉包變量的Seter方法
    func setBackMyClosure(tempClosure:InputClosureType) {
        self.backClosure = tempClosure
    }
    
    @IBAction func tapBackButton(sender: UIButton) {
        if self.backClosure != nil {
            let tempString:String? = self.inputTextField.text
            if tempString != nil {
                self.backClosure!(tempString!)
            }
        }
        self.navigationController!.popViewControllerAnimated(true)
    }
}

(4) 經過上面的步驟這個實例已經完成,接下來就是看一下運行效果的時間了。本來想做成Git動態圖的,感覺實例功能簡單,而且UI上也比較簡單,就沒做,還是看截圖吧。運行效果的截圖如下:

4.數組中常用的閉包函數

在Swift的數組中自帶了一些比較好用的閉包函數,例如Map, Filter, Reduce。接下來就好好的看一下這些閉包,用起來還是比較爽的。

(1) Map(映射)

說到Map的用法和功能,不能不說的是如果你使用過ReactiveCocoa框架,那么對里邊的Sequence中的Map的使用方式并不陌生。其實兩者的使用方法和功能是極為相似的。如果你沒使用過RAC中的Map,那也無關緊要,接下來我們先上段代碼開看一下數組中的Map閉包函數。

通過上面的代碼段以及運行結果,我們不難看出,map閉包函數的功能就是對數組中的每一項進行遍歷,然后通過映射規則對數組中的每一項進行處理,最終的返回結果是處理后的數組(以一個新的數組形式出現)。當然,原來數組中的元素值是保持不變的,這就是map閉包函數的用法與功能。

(2) Filter (過濾器)

Filter的用法還是比較好理解的,Filter就是一個漏勺,就是用來過濾符合條件的數據的。在ReactiveCocoa中的Sequence也是有Filter的,用法還是來過濾Sequence中的數據的。而在數組中的Filter用來過濾數組中的數據,并且返回新的數組,新的數組中存放的就是符合條件的數據。Filter的用法如下實例,下方的實例就是一個身高的過濾,過濾掉身高小于173的人,返回大于等于173的身高數據。

(3)Reduce

在ReactiveCocoa中也是有Reduce這個概念的,ReactiveCocoa中使用Reduce來合并消減信號量。在swift的數組中使用Reduce閉包函數來合并items, 并且合并后的Value。下方的實例是一個Salary的數組,其中存放的是每個月的薪水。我們要使用Reduce閉包函數來計算總的薪水。下方是DEMO的截圖:

來自: http://www.cocoachina.com/swift/20160106/14862.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!