方法 - Methods

2018-12-06 14:38 更新

方法 - Methods

方法是與特定類型相關(guān)聯(lián)的函數(shù)。類、結(jié)構(gòu)體以及枚舉均可以定義實(shí)例方法,該方法為指定類型的實(shí)例封裝了特定的任務(wù)與功能。類、結(jié)構(gòu)體以及枚舉也能定義類型方法,該方法與類型自身相關(guān)聯(lián)。類型方法類似于在 Objective-C 中的類方法。

在 Swift 中,結(jié)構(gòu)體和枚舉能夠定義方法;事實(shí)上這是 Swift 與 C/Objective-C 的主要區(qū)別之一。在 Objective-C 中,類是唯一能定義方法的類型。在 Swift 中,你可以選擇是否定義一個(gè)類、結(jié)構(gòu)體或枚舉,且仍可以靈活地對(duì)你所創(chuàng)建的類型進(jìn)行方法的定義。

實(shí)例方法

實(shí)例方法是某個(gè)特定類、結(jié)構(gòu)體或枚舉類型的實(shí)例的方法。他們通過提供訪問的方式和修改實(shí)例屬性,或提供與實(shí)例目的相關(guān)的功能性來支持這些實(shí)例的功能性。準(zhǔn)確的來講,實(shí)例方法的語法與函數(shù)完全一致,參考函數(shù)說明。

實(shí)例方法要寫在它所屬的類型的前后括號(hào)之間。實(shí)例方法能夠訪問他所屬類型的所有的其他實(shí)例方法和屬性。實(shí)例方法只能被它所屬的類的特定實(shí)例調(diào)用。實(shí)例方法不能被孤立于現(xiàn)存的實(shí)例而被調(diào)用。

下面定義一個(gè)簡(jiǎn)單的類 Counter 的示例(Counter 可以用來對(duì)一個(gè)動(dòng)作發(fā)生的次數(shù)進(jìn)行計(jì)數(shù)):

    class Counter {
        var count = 0
        func increment() {
            count++
        }
        func incrementBy(amount: Int) {
            count += amount
        }
        func reset() {
            count = 0
        }
    }

Counter 可以定義三種實(shí)例方法:

  • increment 讓計(jì)數(shù)器按一遞增
  • incrementBy(amount: Int) 讓計(jì)數(shù)器按一個(gè)指定的整數(shù)值遞增
  • reset 將計(jì)數(shù)器重置為 0

Counter 這個(gè)類還聲明了一個(gè)可變屬性 count,用它來保持對(duì)當(dāng)前計(jì)數(shù)器值的追蹤。

和調(diào)用屬性一樣,用點(diǎn)語法調(diào)用實(shí)例方法:

    let counter = Counter()
    // the initial counter value is 0
    counter.increment()
    // the counter's value is now 1
    counter.incrementBy(5)
    // the counter's value is now 6
    counter.reset()
    // the counter's value is now 0

方法的局部參數(shù)名稱和外部參數(shù)名稱

函數(shù)參數(shù)有一個(gè)局部名稱(在函數(shù)體內(nèi)部使用)和一個(gè)外部名稱(在調(diào)用函數(shù)時(shí)使用),參考外部參數(shù)名稱。對(duì)方法參數(shù)也是一樣的,因?yàn)榉椒▋H僅是與某一類型相關(guān)的函數(shù)。但是,局部名稱和外部名稱的默認(rèn)行為不同于函數(shù)和方法。

在 Swift 中的方法和在 Objective-C 中的方法極其相似,像在 Objective-C 一樣,在 Swift 中方法的名稱名稱通常用一個(gè)介詞指向方法的第一個(gè)參數(shù),比如:with、for 以及 by 等等,前面的 Counter 類的例子中 incrementBy 方法就是這樣的。當(dāng)其被訪問時(shí),介詞的使用使方法可被解讀為一個(gè)句子介詞的使用讓方法在被調(diào)用時(shí)能像一個(gè)句子一樣被解讀。Swift 這種方法命名約定很容易落實(shí),因?yàn)樗怯貌煌哪J(rèn)處理方法參數(shù)的方式,而不是用函數(shù)參數(shù)(來實(shí)現(xiàn)的)。

具體來說,Swift 默認(rèn)僅給方法的第一個(gè)參數(shù)名稱一個(gè)局部參數(shù)名稱;但是默認(rèn)同時(shí)給第二個(gè)和后續(xù)的參數(shù)名稱局部參數(shù)名稱和外部參數(shù)名稱。這個(gè)約定與典型的命名和調(diào)用約定相匹配,這與你在寫 Objective-C 的方法時(shí)很相似。這個(gè)約定還讓 expressive method 調(diào)用不需要再檢查/限定參數(shù)名。

看看下面這個(gè) Counter 的替換版本(它定義了一個(gè)更復(fù)雜的 incrementBy 方法):

    class Counter {
        var count: Int = 0
        func incrementBy(amount: Int, numberOfTimes: Int) {
            count += amount * numberOfTimes
        }
    }

incrementBy 方法有兩個(gè)參數(shù):amountnumberOfTimes。默認(rèn)地,Swift 僅把 amount 當(dāng)做一個(gè)局部名稱,但是把 numberOfTimes 既看作局部名稱又看做外部名稱。調(diào)用方法如下:

    let counter = Counter()
    counter.incrementBy(5, numberOfTimes: 3)
    // counter value is now 15

你不必對(duì)第一個(gè)參數(shù)值進(jìn)行外部參數(shù)名稱的定義,因?yàn)閺暮瘮?shù)名 incrementBy 已經(jīng)能很清楚地看出它的目的/作用。但是,第二個(gè)參數(shù),就要被一個(gè)外部參數(shù)名稱所限定,以便在方法被調(diào)用時(shí)明確它的作用。

這種默認(rèn)的行為能夠有效的檢查方法,比如你在參數(shù) numberOfTimes 前寫了個(gè)井號(hào)(#)時(shí):

    func incrementBy(amount: Int, #numberOfTimes: Int) {
        count += amount * numberOfTimes
    }

這種默認(rèn)行為使上面代碼意味著:在 Swift 中定義方法使用了與 Objective-C 同樣的語法風(fēng)格,并且方法將以自然表達(dá)式的方式被調(diào)用。

修改方法的外部參數(shù)名稱

有時(shí),對(duì)一個(gè)方法的第一個(gè)參數(shù)提供一個(gè)外部參數(shù)名是有用的,即使這不是默認(rèn)行為。你可以自己添加一個(gè)明確的外部名稱或你也可以用一個(gè) hash 符號(hào)作為第一個(gè)參數(shù)的前綴,然后用這個(gè)局部名字作為外部名字。

相反,若你不想為方法的第二或后續(xù)參數(shù)提供一個(gè)外部名稱,你可以通過使用下劃線 (_) 作為該參數(shù)的顯式外部名稱來覆蓋默認(rèn)行為。

self 屬性

類型的每一實(shí)例都有一個(gè)被稱為 self 的隱含屬性,該屬性完全等同于該實(shí)例本身??梢栽谝粋€(gè)實(shí)例的實(shí)例方法中使用這個(gè)隱含的 self 屬性來引用當(dāng)前實(shí)例。

在上面的例子中,increment 方法也可以被寫成這樣:

    func increment() {
        self.count++
    }

實(shí)際上,你不必在你的代碼里面經(jīng)常寫 self。不論何時(shí),在一個(gè)方法中使用一個(gè)已知的屬性或者方法名稱,如果你沒有明確的寫 self,Swift 假定你是指當(dāng)前實(shí)例的屬性或者方法。這種假定在上面的 Counter 中已經(jīng)示范了:Counter 中的三個(gè)實(shí)例方法中都使用的是 count (而不是 self.count)。

這條規(guī)則的主要例外發(fā)生在當(dāng)實(shí)例方法的某個(gè)參數(shù)名稱與實(shí)例的某個(gè)屬性名稱相同時(shí)。在這種情況下,參數(shù)名稱享有優(yōu)先權(quán),并且在引用屬性時(shí)必須使用一種更恰當(dāng)(被限定更嚴(yán)格)的方式。你可以使用隱藏的 self 屬性來區(qū)分參數(shù)名稱和屬性名稱。

下面的例子演示 self 消除方法參數(shù)x和實(shí)例屬性 x 之間的歧義:

    struct Point {
        var x = 0.0, y = 0.0
        func isToTheRightOfX(x: Double) -> Bool {
            return self.x > x
        }
    }
    let somePoint = Point(x: 4.0, y: 5.0)
    if somePoint.isToTheRightOfX(1.0) {
        println("This point is to the right of the line where x == 1.0")
    prints "This point is to the right of the line where x == 1.0"

如果不使用 self 前綴,Swift 就認(rèn)為兩次使用的 x 都指的是名稱為 x 的函數(shù)參數(shù)。

在實(shí)例方法中修改值類型

結(jié)構(gòu)體和枚舉均屬于值類型一般情況下,值類型的屬性不能在其實(shí)例方法內(nèi)被修改。

但是,如果在某個(gè)具體方法中,你需要對(duì)結(jié)構(gòu)體或枚舉的屬性進(jìn)行修改,你可以選擇變異(mutating)這個(gè)方法。方法可以從內(nèi)部變異它的屬性;并且它做的任何改變?cè)诜椒ńY(jié)束時(shí)都會(huì)回寫到原始結(jié)構(gòu)。方法會(huì)給它隱含的 self 屬性賦值一個(gè)全新的實(shí)例,這個(gè)新實(shí)例在方法結(jié)束后將替換原來的實(shí)例。

對(duì)于變異方法,將關(guān)鍵字 mutating 放到方法的 func 關(guān)鍵字之前就可以了:

    struct Point {
        var x = 0.0, y = 0.0
        mutating func moveByX(deltaX: Double, y deltaY: Double) {
            x += deltaX
            y += deltaY
        }
    }
    var somePoint = Point(x: 1.0, y: 1.0)
    somePoint.moveByX(2.0, y: 3.0)
    println("The point is now at (\(somePoint.x), \(somePoint.y))")
    // 輸出 "The point is now at (3.0, 4.0)"

上文 Point 結(jié)構(gòu)體定義一個(gè)變異(mutating)方法 moveByx,該方法按一定的數(shù)量移動(dòng) Point 結(jié)構(gòu)體。moveByX 方法在被調(diào)用時(shí)修改了這個(gè) point,而不是返回一個(gè)新的 point。方法定義是加上 mutating 關(guān)鍵字,因此,方法可以修改值類型的屬性。

注意:不能在結(jié)構(gòu)體類型常量上調(diào)用變異方法,因?yàn)槌A康膶傩圆荒鼙桓淖?,即使想改變的是常量的變量屬性也不行,詳情參?a href="http://m.hgci.cn/swiftlanguageguide/3slc1iab.html" stored_properties_of_constant_structure_instances="">存儲(chǔ)屬性和實(shí)例變量:

    let fixedPoint = Point(x: 3.0, y: 3.0)
    fixedPoint.moveByX(2.0, y: 3.0)
    // this will report an error

在變異方法中給 self 賦值

變異方法可以賦予隱含屬性 self 一個(gè)全新的實(shí)例。上述 Point 的示例也可以采用下面的方式來改寫:

    struct Point {
        var x = 0.0, y = 0.0
        mutating func moveByX(deltaX: Double, y deltaY: Double) {
            self = Point(x: x + deltaX, y: y + deltaY)
        }
    }

新版的變異方法 moveByX 創(chuàng)建了一個(gè)新的分支結(jié)構(gòu)(他的 x 和 y 的值都被設(shè)定為目標(biāo)值了)。調(diào)用這個(gè)版本的方法和調(diào)用上個(gè)版本的最終結(jié)果是一樣的。

枚舉的變異方法可以讓 self 從相同的枚舉設(shè)置為不同的成員:

    enum TriStateSwitch {
        case Off, Low, High
        mutating func next() {
            switch self {
            case Off:
                self = Low
            case Low:
                self = High
            case High:
                self = Off
        }
    ovenLight = TriStateSwitch.Low
    ovenLight.next()
    // ovenLight is now equal to .High
    ovenLight.next()
    // ovenLight is now equal to .Off

上述示例中定義了一個(gè)三態(tài)開關(guān)的枚舉。每次調(diào)用 next 方法時(shí),開關(guān)在不同的電源狀態(tài)(Off, Low, High)之前循環(huán)切換。

類型方法

如上所述,實(shí)例方法是被類型的某個(gè)實(shí)例調(diào)用的方法。你也可以定義調(diào)用類型本身的方法。這種方法就叫做類型方法。聲明類的類型方法,在方法的 func 關(guān)鍵字之前加上關(guān)鍵字 class;聲明結(jié)構(gòu)體和枚舉的類型方法,在方法的 func 關(guān)鍵字之前加上關(guān)鍵字 static。

注:在 Objective-C 中,你可以定義僅適用于 Objective-C 的類的 type-evel 方法。在 Swift,你可以對(duì)所有的類,結(jié)構(gòu)體和枚舉進(jìn)行 type-evel 方法定義。每種類型方法僅適用于其所支持的類型。

與實(shí)例方法相同,也可利用點(diǎn)語法調(diào)用類型方法。但是,你需要在本類型上調(diào)用類型方法,而不是其類型實(shí)例上。下面是如何在 SomeClass 類上調(diào)用類型方法的示例:

    class SomeClass {
        class func someTypeMethod() {
        // type method implementation goes here
        }
    }
    SomeClass.someTypeMethod()

在一個(gè)類型方法的方法體內(nèi),self 指向該類型本身,而不是類型的某個(gè)實(shí)例。對(duì)結(jié)構(gòu)體和枚舉來講,這意味著你可以用 self 來消除靜態(tài)屬性和靜態(tài)方法參數(shù)之間的二意性(類似于我們?cè)谇懊嫣幚韺?shí)例屬性和實(shí)例方法參數(shù)時(shí)做的那樣)。

更廣泛地說,你在一個(gè)類型方法主體內(nèi)使用的任何未經(jīng)限定的方法和屬性名稱將指的是其他 type-level 方法和屬性。一種類型方法能用其他方法名稱調(diào)用另一種類型方法,而無需在方法名稱前面加上類型名稱的前綴。同樣,結(jié)構(gòu)體和枚舉的類型方法也能夠通過使用不帶有類型名稱前綴的靜態(tài)屬性名稱來訪問靜態(tài)屬性。

下述示例中定義名為 LevelTracker 的結(jié)構(gòu)體,該結(jié)構(gòu)體通過不同級(jí)別或階段的游戲?qū)ν婕业倪M(jìn)度進(jìn)行監(jiān)測(cè)。這是一個(gè)單人游戲,但也能在單個(gè)設(shè)備上儲(chǔ)存多個(gè)玩家的信息。

所有游戲級(jí)別(除了級(jí)別 1)在游戲初始時(shí)都被鎖定。每當(dāng)玩家完成一個(gè)級(jí)別,在該設(shè)備上,該級(jí)別對(duì)所有的玩家解鎖。LevelTracker 結(jié)構(gòu)體用靜態(tài)屬性和方法來監(jiān)測(cè)解鎖的游戲級(jí)別。也可監(jiān)測(cè)每個(gè)玩家的當(dāng)前等級(jí)。

    struct LevelTracker {
        static var highestUnlockedLevel = 1
        static func unlockLevel(level: Int) {
            if level > highestUnlockedLevel 
                { highestUnlockedLevel = level }
        }
        static func levelIsUnlocked(level: Int) -> Bool {
            return level <= highestUnlockedLevel
        }
        var currentLevel = 1
        mutating func advanceToLevel(level: Int) -> Bool {
            if LevelTracker.levelIsUnlocked(level) {
                currentLevel = level
                return true
            } else {
                return false
        }
    }

LevelTracker 結(jié)構(gòu)體監(jiān)測(cè)任何玩家已解鎖的最高級(jí)別。該值被存儲(chǔ)在成為 highestUnlockedLevel 的靜態(tài)屬性中。

LeveTracker 還定義了兩個(gè)類型函數(shù)與 highestUnlockedLevel 配合工作。第一個(gè)為 unockLevel 類型函數(shù),一旦新的級(jí)別被解鎖,該函數(shù)會(huì)更新 highestUnlockedLevel 的值。第二個(gè)為 levelIsUnocked 便利型函數(shù),若某個(gè)給定級(jí)別數(shù)已經(jīng)被解鎖,該函數(shù)則返回 true。(注:沒用使用 LevelTracker.highestUnlockedLevel,這個(gè)類型方法還是能夠訪問靜態(tài)屬性 highestUnlockedLevel。)

除了其靜態(tài)屬性和類型方法之外,LeveTracker 還監(jiān)測(cè)每個(gè)玩家的游戲進(jìn)程。它使用 currentLevel 實(shí)例屬性來監(jiān)測(cè)玩家當(dāng)前進(jìn)行的級(jí)別。

為便于管理 currentLevel 屬性,LeveTracker 定義了實(shí)例方法 advanceToLevel。在更新 currentLevel 之前,該方法檢查所要求的新級(jí)別是否已經(jīng)解鎖。advanceToLevel 方法返回布爾值以指示是否確實(shí)能夠設(shè)置 currentLevel。

下面,Player 類使用 LevelTracker 來監(jiān)測(cè)和更新每個(gè)玩家的發(fā)展進(jìn)度:

    class Player {
        var tracker = LevelTracker()
        let playerName: String
        func completedLevel(level: Int) {
            LevelTracker.unlockLevel(level + 1)
            tracker.advanceToLevel(level + 1)
        }
        init(name: String) {
            playerName = name

Player 類使用 LevelTracker 來監(jiān)測(cè)該玩家的游戲進(jìn)程。它也提供 competedLevel 方法,一旦玩家完成某個(gè)指定等級(jí),調(diào)用該方法。該方法為所有玩家解鎖下一個(gè)級(jí)別并將當(dāng)前玩家進(jìn)程更新為下一個(gè)級(jí)別。(忽略了 advanceToLevel 布爾返回值,因?yàn)橹罢{(diào)用上行時(shí)就知道了這個(gè)等級(jí)已經(jīng)被解鎖了。)

你可以為一個(gè)新玩家創(chuàng)建一個(gè) Player 類的實(shí)例,然后看這個(gè)玩家完成等級(jí)一時(shí)發(fā)生了什么:

    var player = Player(name: "Argyrios")
    player.completedLevel(1)
    println("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
    // prints "highest unlocked level is now 2"

如果你創(chuàng)建了第二個(gè)玩家,并嘗試讓他開始一個(gè)沒有被任何玩家解鎖的等級(jí),關(guān)于設(shè)置玩家當(dāng)前等級(jí)的嘗試會(huì)失?。?

    player = Player(name: "Beto")
    if player.tracker.advanceToLevel(6) {
        println("player is now on level 6")
    } else {
        println("level 6 has not yet been unlocked")
    }
    // prints "level 6 has not yet been unlocked"
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)