鍍金池/ 教程/ iOS/ 方法(Methods)
特性(Attributes)
Access Control 權(quán)限控制的黑與白
基本運(yùn)算符(Basic Operators)
基礎(chǔ)部分(The Basics)
閉包(Closures)
擴(kuò)展
泛型參數(shù)(Generic Parameters and Arguments)
訪問控制和 protected
語句(Statements)
模式(Patterns)
WWDC 里面的那個(gè)“大炮打氣球”
關(guān)于語言參考(About the Language Reference)
語法總結(jié)(Summary of the Grammar)
嵌套類型
類型(Types)
Swift 初見(A Swift Tour)
泛型
枚舉(Enumerations)
高級(jí)運(yùn)算符
繼承
析構(gòu)過程
關(guān)于 Swift(About Swift)
訪問控制
類和結(jié)構(gòu)體
內(nèi)存安全
Swift 與 C 語言指針友好合作
協(xié)議
屬性(Properties)
可選類型完美解決占位問題
錯(cuò)誤處理
字符串和字符(Strings and Characters)
聲明(Declarations)
自動(dòng)引用計(jì)數(shù)
Swift 里的值類型與引用類型
表達(dá)式(Expressions)
Swift 文檔修訂歷史
造個(gè)類型不是夢(mèng)-白話 Swift 類型創(chuàng)建
歡迎使用 Swift
詞法結(jié)構(gòu)(Lexical Structure)
集合類型(Collection Types)
下標(biāo)
方法(Methods)
可選鏈?zhǔn)秸{(diào)用
版本兼容性
類型轉(zhuǎn)換
構(gòu)造過程
The Swift Programming Language 中文版
函數(shù)(Functions)
Swift 教程
控制流(Control Flow)

方法(Methods)


1.0 翻譯:pp-prog 校對(duì):zqp

2.0 翻譯+校對(duì):DianQK

2.1 翻譯:DianQKRealank 校對(duì):shanks,2016-01-18

2.2 校對(duì):SketchK 2016-05-13

3.0.1,shanks,2016-11-13

4.0 校對(duì):kemchenj 2017-09-21

4.1 翻譯+校對(duì):mylittleswift

本頁(yè)包含內(nèi)容:

方法是與某些特定類型相關(guān)聯(lián)的函數(shù)。類、結(jié)構(gòu)體、枚舉都可以定義實(shí)例方法;實(shí)例方法為給定類型的實(shí)例封裝了具體的任務(wù)與功能。類、結(jié)構(gòu)體、枚舉也可以定義類型方法;類型方法與類型本身相關(guān)聯(lián)。類型方法與 Objective-C 中的類方法(class methods)相似。

結(jié)構(gòu)體和枚舉能夠定義方法是 Swift 與 C/Objective-C 的主要區(qū)別之一。在 Objective-C 中,類是唯一能定義方法的類型。但在 Swift 中,你不僅能選擇是否要定義一個(gè)類/結(jié)構(gòu)體/枚舉,還能靈活地在你創(chuàng)建的類型(類/結(jié)構(gòu)體/枚舉)上定義方法。

實(shí)例方法(Instance Methods)

實(shí)例方法是屬于某個(gè)特定類、結(jié)構(gòu)體或者枚舉類型實(shí)例的方法。實(shí)例方法提供訪問和修改實(shí)例屬性的方法或提供與實(shí)例目的相關(guān)的功能,并以此來支撐實(shí)例的功能。實(shí)例方法的語法與函數(shù)完全一致,詳情參見函數(shù)。

實(shí)例方法要寫在它所屬的類型的前后大括號(hào)之間。實(shí)例方法能夠隱式訪問它所屬類型的所有的其他實(shí)例方法和屬性。實(shí)例方法只能被它所屬的類的某個(gè)特定實(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 += 1
    }
    func increment(by amount: Int) {
        count += amount
    }
    func reset() {
        count = 0
    }
}

Counter 類定義了三個(gè)實(shí)例方法:

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

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

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

let counter = Counter()
// 初始計(jì)數(shù)值是0
counter.increment()
// 計(jì)數(shù)值現(xiàn)在是1
counter.increment(by: 5)
// 計(jì)數(shù)值現(xiàn)在是6
counter.reset()
// 計(jì)數(shù)值現(xiàn)在是0

函數(shù)參數(shù)可以同時(shí)有一個(gè)局部名稱(在函數(shù)體內(nèi)部使用)和一個(gè)外部名稱(在調(diào)用函數(shù)時(shí)使用),詳情參見指定外部參數(shù)名。方法參數(shù)也一樣,因?yàn)榉椒ň褪呛瘮?shù),只是這個(gè)函數(shù)與某個(gè)類型相關(guān)聯(lián)了。

self 屬性

類型的每一個(gè)實(shí)例都有一個(gè)隱含屬性叫做 selfself 完全等同于該實(shí)例本身。你可以在一個(gè)實(shí)例的實(shí)例方法中使用這個(gè)隱含的 self 屬性來引用當(dāng)前實(shí)例。

上面例子中的 increment 方法還可以這樣寫:

func increment() {
    self.count += 1
}

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

使用這條規(guī)則的主要場(chǎng)景是實(shí)例方法的某個(gè)參數(shù)名稱與實(shí)例的某個(gè)屬性名稱相同的時(shí)候。在這種情況下,參數(shù)名稱享有優(yōu)先權(quán),并且在引用屬性時(shí)必須使用一種更嚴(yán)格的方式。這時(shí)你可以使用 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) {
    print("This point is to the right of the line where x == 1.0")
}
// 打印 "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)體和枚舉是值類型。默認(rèn)情況下,值類型的屬性不能在它的實(shí)例方法中被修改。

但是,如果你確實(shí)需要在某個(gè)特定的方法中修改結(jié)構(gòu)體或者枚舉的屬性,你可以為這個(gè)方法選擇 可變(mutating)行為,然后就可以從其方法內(nèi)部改變它的屬性;并且這個(gè)方法做的任何改變都會(huì)在方法執(zhí)行結(jié)束時(shí)寫回到原始結(jié)構(gòu)中。方法還可以給它隱含的 self 屬性賦予一個(gè)全新的實(shí)例,這個(gè)新實(shí)例在方法結(jié)束時(shí)會(huì)替換現(xiàn)存實(shí)例。

要使用 可變方法,將關(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)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// 打印 "The point is now at (3.0, 4.0)"

上面的 Point 結(jié)構(gòu)體定義了一個(gè)可變方法 moveByX(_:y:) 來移動(dòng) Point 實(shí)例到給定的位置。該方法被調(diào)用時(shí)修改了這個(gè)點(diǎn),而不是返回一個(gè)新的點(diǎn)。方法定義時(shí)加上了 mutating 關(guān)鍵字,從而允許修改屬性。

注意,不能在結(jié)構(gòu)體類型的常量(a constant of structure type)上調(diào)用可變方法,因?yàn)槠鋵傩圆荒鼙桓淖儯词箤傩允亲兞繉傩?,詳情參?a href="./10_Properties.html#stored_properties_of_constant_structure_instances">常量結(jié)構(gòu)體的存儲(chǔ)屬性:

let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveByX(2.0, y: 3.0)
// 這里將會(huì)報(bào)告一個(gè)錯(cuò)誤

在可變方法中給 self 賦值

可變方法能夠賦給隱含屬性 self 一個(gè)全新的實(shí)例。上面 Point 的例子可以用下面的方式改寫:

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

新版的可變方法 moveBy(x:y:) 創(chuàng)建了一個(gè)新的結(jié)構(gòu)體實(shí)例,它的 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
        }
    }
}
var ovenLight = TriStateSwitch.Low
ovenLight.next()
// ovenLight 現(xiàn)在等于 .High
ovenLight.next()
// ovenLight 現(xiàn)在等于 .Off

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

類型方法

實(shí)例方法是被某個(gè)類型的實(shí)例調(diào)用的方法。你也可以定義在類型本身上調(diào)用的方法,這種方法就叫做類型方法。在方法的 func 關(guān)鍵字之前加上關(guān)鍵字 static,來指定類型方法。類還可以用關(guān)鍵字 class 來允許子類重寫父類的方法實(shí)現(xiàn)。

注意

在 Objective-C 中,你只能為 Objective-C 的類類型(classes)定義類型方法(type-level methods)。在 Swift 中,你可以為所有的類、結(jié)構(gòu)體和枚舉定義類型方法。每一個(gè)類型方法都被它所支持的類型顯式包含。

類型方法和實(shí)例方法一樣用點(diǎn)語法調(diào)用。但是,你是在類型上調(diào)用這個(gè)方法,而不是在實(shí)例上調(diào)用。下面是如何在 SomeClass 類上調(diào)用類型方法的例子:

class SomeClass {
    class func someTypeMethod() {
        // 在這里實(shí)現(xiàn)類型方法
    }
}
SomeClass.someTypeMethod()

在類型方法的方法體(body)中,self 指向這個(gè)類型本身,而不是類型的某個(gè)實(shí)例。這意味著你可以用 self 來消除類型屬性和類型方法參數(shù)之間的歧義(類似于我們?cè)谇懊嫣幚韺?shí)例屬性和實(shí)例方法參數(shù)時(shí)做的那樣)。

一般來說,在類型方法的方法體中,任何未限定的方法和屬性名稱,可以被本類中其他的類型方法和類型屬性引用。一個(gè)類型方法可以直接通過類型方法的名稱調(diào)用本類中的其它類型方法,而無需在方法名稱前面加上類型名稱。類似地,在結(jié)構(gòu)體和枚舉中,也能夠直接通過類型屬性的名稱訪問本類中的類型屬性,而不需要前面加上類型名稱。

下面的例子定義了一個(gè)名為 LevelTracker 結(jié)構(gòu)體。它監(jiān)測(cè)玩家的游戲發(fā)展情況(游戲的不同層次或階段)。這是一個(gè)單人游戲,但也可以存儲(chǔ)多個(gè)玩家在同一設(shè)備上的游戲信息。

游戲初始時(shí),所有的游戲等級(jí)(除了等級(jí) 1)都被鎖定。每次有玩家完成一個(gè)等級(jí),這個(gè)等級(jí)就對(duì)這個(gè)設(shè)備上的所有玩家解鎖。LevelTracker 結(jié)構(gòu)體用類型屬性和方法監(jiān)測(cè)游戲的哪個(gè)等級(jí)已經(jīng)被解鎖。它還監(jiān)測(cè)每個(gè)玩家的當(dāng)前等級(jí)。

struct LevelTracker {
    static var highestUnlockedLevel = 1
    var currentLevel = 1

    static func unlock(_ level: Int) {
        if level > highestUnlockedLevel { highestUnlockedLevel = level }
    }

    static func isUnlocked(_ level: Int) -> Bool {
        return level <= highestUnlockedLevel
    }

    @discardableResult
    mutating func advance(to level: Int) -> Bool {
        if LevelTracker.isUnlocked(level) {
            currentLevel = level
            return true
        } else {
            return false
        }
    }
}

LevelTracker 監(jiān)測(cè)玩家已解鎖的最高等級(jí)。這個(gè)值被存儲(chǔ)在類型屬性 highestUnlockedLevel 中。

LevelTracker 還定義了兩個(gè)類型方法與 highestUnlockedLevel 配合工作。第一個(gè)類型方法是 unlock(_:),一旦新等級(jí)被解鎖,它會(huì)更新 highestUnlockedLevel 的值。第二個(gè)類型方法是 isUnlocked(_:),如果某個(gè)給定的等級(jí)已經(jīng)被解鎖,它將返回 true。(注意,盡管我們沒有使用類似 LevelTracker.highestUnlockedLevel 的寫法,這個(gè)類型方法還是能夠訪問類型屬性 highestUnlockedLevel

除了類型屬性和類型方法,LevelTracker 還監(jiān)測(cè)每個(gè)玩家的進(jìn)度。它用實(shí)例屬性 currentLevel 來監(jiān)測(cè)每個(gè)玩家當(dāng)前的等級(jí)。

為了便于管理 currentLevel 屬性,LevelTracker 定義了實(shí)例方法 advance(to:)。這個(gè)方法會(huì)在更新 currentLevel 之前檢查所請(qǐng)求的新等級(jí)是否已經(jīng)解鎖。advance(to:) 方法返回布爾值以指示是否能夠設(shè)置 currentLevel。因?yàn)樵试S在調(diào)用 advance(to:) 時(shí)候忽略返回值,不會(huì)產(chǎn)生編譯警告,所以函數(shù)被標(biāo)注為 @ discardableResult 屬性,更多關(guān)于屬性信息,請(qǐng)參考屬性章節(jié)。

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

class Player {
    var tracker = LevelTracker()
    let playerName: String
    func complete(level: Int) {
        LevelTracker.unlock(level + 1)
        tracker.advance(to: level + 1)
    }
    init(name: String) {
        playerName = name
    }
}

Player 類創(chuàng)建一個(gè)新的 LevelTracker 實(shí)例來監(jiān)測(cè)這個(gè)用戶的進(jìn)度。它提供了 complete(level:) 方法,一旦玩家完成某個(gè)指定等級(jí)就調(diào)用它。這個(gè)方法為所有玩家解鎖下一等級(jí),并且將當(dāng)前玩家的進(jìn)度更新為下一等級(jí)。(我們忽略了 advance(to:) 返回的布爾值,因?yàn)橹罢{(diào)用 LevelTracker.unlock(_:) 時(shí)就知道了這個(gè)等級(jí)已經(jīng)被解鎖了)。

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

var player = Player(name: "Argyrios")
player.complete(level: 1)
print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
// 打印 "highest unlocked level is now 2"

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

player = Player(name: "Beto")
if player.tracker.advance(to: 6) {
    print("player is now on level 6")
} else {
    print("level 6 has not yet been unlocked")
}
// 打印 "level 6 has not yet been unlocked"
上一篇:錯(cuò)誤處理下一篇:析構(gòu)過程