方法 - Methods

2018-12-06 14:38 更新

方法 - Methods

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

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

實例方法

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

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

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

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

Counter 可以定義三種實例方法:

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

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

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

    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ù)有一個局部名稱(在函數(shù)體內(nèi)部使用)和一個外部名稱(在調(diào)用函數(shù)時使用),參考外部參數(shù)名稱。對方法參數(shù)也是一樣的,因為方法僅僅是與某一類型相關(guān)的函數(shù)。但是,局部名稱和外部名稱的默認(rèn)行為不同于函數(shù)和方法。

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

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

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

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

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

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

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

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

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

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

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

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

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

self 屬性

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

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

    func increment() {
        self.count++
    }

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

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

下面的例子演示 self 消除方法參數(shù)x和實例屬性 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ù)。

在實例方法中修改值類型

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

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

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

注意:不能在結(jié)構(gòu)體類型常量上調(diào)用變異方法,因為常量的屬性不能被改變,即使想改變的是常量的變量屬性也不行,詳情參見存儲屬性和實例變量

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

在變異方法中給 self 賦值

變異方法可以賦予隱含屬性 self 一個全新的實例。上述 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)建了一個新的分支結(jié)構(gòu)(他的 x 和 y 的值都被設(shè)定為目標(biāo)值了)。調(diào)用這個版本的方法和調(diào)用上個版本的最終結(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

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

類型方法

如上所述,實例方法是被類型的某個實例調(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,你可以對所有的類,結(jié)構(gòu)體和枚舉進(jìn)行 type-evel 方法定義。每種類型方法僅適用于其所支持的類型。

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

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

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

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

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

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

    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)測任何玩家已解鎖的最高級別。該值被存儲在成為 highestUnlockedLevel 的靜態(tài)屬性中。

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

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

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

下面,Player 類使用 LevelTracker 來監(jiān)測和更新每個玩家的發(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)測該玩家的游戲進(jìn)程。它也提供 competedLevel 方法,一旦玩家完成某個指定等級,調(diào)用該方法。該方法為所有玩家解鎖下一個級別并將當(dāng)前玩家進(jìn)程更新為下一個級別。(忽略了 advanceToLevel 布爾返回值,因為之前調(diào)用上行時就知道了這個等級已經(jīng)被解鎖了。)

你可以為一個新玩家創(chuàng)建一個 Player 類的實例,然后看這個玩家完成等級一時發(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)建了第二個玩家,并嘗試讓他開始一個沒有被任何玩家解鎖的等級,關(guān)于設(shè)置玩家當(dāng)前等級的嘗試會失?。?

    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)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號