方法是由特定类型关联起来的函数。类、结构体和枚举都能定义成实例方法。它封装了特定的任务和给定类型的实例的功能函数。类,结构体和方法也能定义类型方法,它只与类型的本身由关联。类型方法和Objective-C的类方法类似。

实际上,Swift与C和Objective-C的主要区别就是结构体和枚举可以定义方法。在Objective-C中,只有类才能定义方法。而在Swift中,你可以有选择性的在类、结构体或枚举中定义方法,并且使得在你创建的类中定义方法时更具有灵活性。

实例方法

实例方法是属于一个特定的类、结构体或枚举的实例的函数,函数是实例的支柱,通过提供访问实例属性和修改实例属性的方法,或过过提供相关实例功能的目的。实例方法和函数有相同的语法,就像功能的描述。

你写一个带有开和闭的实例方法作为它的所属类型,一个实例方法具有隐式访问所有其他的实例方法和属性类型。一个实例方法只能被它所属的类的特定实例调用。不能因为没有现有的实例和把孤立。

这里定义了一个简单的Counter类,它可以用来计算一个操作发生的次数:

class Counter {

  1. var count = 0
  2. func increment() {
  3. count++

}

  1. func incrementBy(amount: Int) {
  2. count += amount
  3. }
  4. func reset() {
  5. count = 0
  6. }

}

Counter类里定义了三个实例方法:

  • increment 计数器增量加1

  • incrementBy(amount: Int)计数器由特定的整数作为增量

  • reset 重置计数器的值为0

该Counter类也定义了一个属性变量count ,用来维持当前计数器的值的轨道。 你调用实例方法和属性时的语法具有相同点:

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

方法的局部和外部参数名

函数的参数可以拥有一个局部名称(在函数体内使用)和一个外部名称(在调用函数时使用),作为外部参数名的描述。方法的参数也是如此,因为方法是与类型相关的函数。然而,局部名称和外部名称的默认行为是不同于函数和方法的。

在Swift中方法相对与Objective-C中的方法非常相似,在Objective-C中和在Swift中的方法名的第一个参数使用介词作为参考,或可以通过前面的Counter类的例子的incrementBy方法可以看出来。使用了一个可用的介词便于调用时就像在阅读句子一样方便。Swift使用这种已制定的命名方法使得在书写时更简单,调用方法时使用默认途径比使用函数参数更简洁。

具体来说,Swift在方法中默认的将第一个参数名作为方法的局部参数名,也默认的根据局部参数和外部参数名去给第二个和后续的参数命名。在你书写Objective-c方法时很熟悉这种典型的命名公约和调用约定,并使参数名称更符合你的调用方法的表达。

细想下这个替代版本的Counter类,它定义了一个更复杂的incrementBy方法:

class Counter {

  1. var count: Int = 0
  2. func incrementBy(amount: Int, numberOfTimes: Int) {
  3. count += amount * numberOfTimes
  4. }

}

这个incrementBy方法有两个参数-amount 和 numberOfTimes.默认情况下,Swift将amount视为唯一的局部名称,但将numberOfTimes同时视为局部名称和外部名称。调用方法如下:

let counter = Counter()

counter.incrementBy(5, numberOfTimes: 3)

// counter value is now 15

你不需要为第一个参数的值而去定义一个外部参数名,因为他的目的是明确incrementBy函数的名称。而第二个参数名则是由外部参数名称进行限定,使方法被调用时的目的更为明确。

如果你在numberOfTimes参数前写了一个散列符号(#),这种行为则是有效的默认处理方法:

func incrementBy(amount: Int, #numberOfTimes: Int) {

  1. count += amount * numberOfTimes

}

上面描述的默认行为是指,在Swift中定义方法时使用和Objective-c相同语法风格书写,被称之为自然的表达方式。

方法的外部参数名称修饰行为

有时为方法的第一个参数提供一个外部参数名是非常有用的。尽管这不是默认的行为。你可以添加一个你自己明确的外部名称,或者你也可以使用局部名称作为外部名称并在参数名前加一个散列符号作为参数名的前缀。

相反的,如果你不想为一个方法的第二个参数或后续参数提供外部参数名,可通过使用下划线符号(_)作为该参数的显式外部参数名称来覆盖默认行为。

self 属性

每一个类型的实例都有一个称为 self 的隐式属性,它是完全等同于该实体本身的。你可以使用这个隐式的self属性饮用当前实例的实例方法。

在上面的例子中,increment方法也可以写成这样:

func increment() {

  1. self.count++

}

在实践中,你不需要很经常的在代码中书写self,当你使用了一个已知的属性或方法名的方法时,如果你没用明确写self,Swift会假设你是指当前实例的属性或方法。Counter类里的三个使用了count(而不是self.count)实例方法就证明了这个假设。

主要的异常发生在一个实例方法的参数名和实例的属性名相同。在这种情况下,参数名优先,有必要参考属性更多的合格方式。你可以使用隐式的self属性区分参数名和属性名。

在这里,使用了self来区分名称同为x的方法参数和一个实例属性:

struct Point {

  1. var x = 0.0, y = 0.0
  2. func isToTheRightOfX(x: Double) -> Bool {
  3. return self.x > x
  4. }

}

let somePoint = Point(x: 4.0, y: 5.0)

if somePoint.isToTheRightOfX(1.0) {

  1. 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将假设它们使用的 x 都是方法的参数x.

修改值类型的实例方法

结构体和枚举都是值类型。默认情况下,值类型的属性不能从它的内部实例方法修改。

然而,如果你需要修改你的结构体或枚举的属性在一个特定的方法中,你可以在这个方法中选择加入变异行为。然后该方法可以变异(即改变)它的属性,任何的更改在方法结束时写回原来的结构里。该方法还可以分配一个新的实例到其隐含的self属性,而这个新的实例将取代现有的实例,当该方法结束时。

你可以加入这个行为,通过将mutating关键字写在方法的func关键字前:

struct Point {

  1. var x = 0.0, y = 0.0
  2. mutating func moveByX(deltaX: Double, y deltaY: Double) {
  3. x += deltaX
  4. y += deltaY
  5. }

}

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))")

// prints "The point is now at (3.0, 4.0)"

上面的Point结构体定义了一个变异的moveByX方法,它通过一定量移动一个Point实例。而不是返回一个新的点,这个方法实际上修改了在其上调用的点。mutating关键字添加到方法的定义上,使它能够修改它的属性。

请注意,你不能使用一个常量的结构类型去调用变异方法,因为它的属性不能被改变。即使它们使可变的属性,如常量结构体实例的存储属性的描述:

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 {

  1. var x = 0.0, y = 0.0
  2. mutating func moveByX(deltaX: Double, y deltaY: Double) {
  3. self = Point(x: x + deltaX, y: y + deltaY)
  4. }

}

moveByX方法的mutating 版本创建了一个全新的结构体,并将x和y的值设置到目标位置。调用这个版本的方法的最终返回结果和调用之前版本的结果完全一样。

枚举的变异方法可以在同一个枚举里为隐式的self设置不同的值:

enum TriStateSwitch {

  1. case Off, Low, High
  2. mutating func next() {
  3. switch self {
  4. case Off:
  5. self = Low
  6. case Low:
  7. self = High
  8. case High:
  9. self = Off
  10. }
  11. }

}

var ovenLight = TriStateSwitch.Low

ovenLight.next()

// ovenLight is now equal to .High

ovenLight.next()

// ovenLight is now equal to .Off

这个例子定义了一个具有三种状态供选择的枚举。每一次它的next方法被调用, 三种不同的电源状态(Off,Low和High)之间循环选择。

类型方法

如上所述,实例方法是由一个特定类型的实例调用的方法。你还可以定义由类型自身调用的方法。这种方法被称为类型方法。你可以通过在func 关键字前写上class 关键字来声明类的类型方法,而在结构体或枚举内定义类型方法则需在func关键字前书写static 关键字来声明。

NOTE
在Objective-C中,你只能为Objective-C类定义类型级方法。在Swift中,你可以为类、结构体和枚举定义类型级方法。每种类型方法由它的类型来明确范围的。

类型方法的调用语法和实例方法的调用方法很像。但是,你只能通过类来调用类型方法,而不是通过这个类的实例来调用。这儿有一个叫做SomeClass的类为您展示了如何调用一个类型方法:

class SomeClass {

  1. class func someTypeMethod() {
  2. // type method implementation goes here
  3. }

}

SomeClass.someTypeMethod()

在类型方法体内,隐式的self属性引用类型本身,而不是该类型的一个实例。对于 结构体和枚举,这意味着你可以使用self来消除静态你操作静态属性、静态方法参数和实例属性和实例方法参数的歧义。

更为普遍的是,你在一个类型方法体内使用任何不合格的方法和属性名称它都将引用其它类型级的方法和属性。一个类型方法可以通过其它的方法名来调用另一个类型方法,而不需要为类型名加前缀。同样,结构体和枚举的类型方法也能通过使用静态属性名访问静态属性,而不需要类型名做前缀。

下面的例子定义了一个名为LevelTracker结构体,它通过游戏的不同级别或阶段跟踪玩家的进步。这是一个单人游戏,但可以在一个设备上为多个玩家存储信息。当游戏第一次玩时所有的级别都被上锁(除了第一级)。每当一个玩家完成一个级别,该级别将对设备上的所有玩家解锁。该LevelTracker结构体采用静态属性和方法来跟踪游戏的那些级别被解锁。它还跟踪当前级别的个别玩家水平。

struct LevelTracker {

  1. static var highestUnlockedLevel = 1
  2. static func unlockLevel(level: Int) {
  3. if level > highestUnlockedLevel { highestUnlockedLevel = level }
  4. }
  5. static func levelIsUnlocked(level: Int) -> Bool {
  6. return level <= highestUnlockedLevel
  7. }
  8. var currentLevel = 1
  9. mutating func advanceToLevel(level: Int) -> Bool {
  10. if LevelTracker.levelIsUnlocked(level) {
  11. currentLevel = level
  12. return true
  13. } else {
  14. return false
  15. }
  16. }

}

该LevelTracker结构体跟中任何玩家所解锁的最高的级别。这个值被存储在一个名为highestUnlockedLevel的静态属性里。

LevelTracker还定义了两个类型函数为highestUnlockedLevels工作。首先时一个叫unlockLevel的函数,负责当一个新的级别被解锁时去更新highestUnlockedLevel的值。第二个是一个便利的levelIsUnlocked类型函数,当一个特定的级别被解锁,它返回true。(注意,这些类型方法可以访问highestUnlockedLevel静态属性,你不需要把它写成LevelTracker.highestUnlockedLevel)

除了静态属性和类型方法,LevelTracker游戏通过一个currentLevel实例属性来追踪每个单个玩家当前正在玩的游戏级别。

为了帮助管理currentLevel属性,LevelTracker定义了一个名为advanceToLevel的实例方法。在更新currentLevel之前,该方法会检查新的游戏级别是否已经解锁。advanceToLevel方法通过返回一个bool值来指示是否能够设置currentLevel。

LevelTracker结构体被一个Player类所使用,如下所示,追踪和更新单个玩家的进度:

class Player {

  1. var tracker = LevelTracker()
  2. let playerName: String
  3. func completedLevel(level: Int) {
  4. LevelTracker.unlockLevel(level + 1)
  5. tracker.advanceToLevel(level + 1)
  6. }
  7. init(name: String) {
  8. playerName = name
  9. }

}

Player类创建了一个LevelTracker的新实例用来跟踪玩家的进度。它也提供了一个名为completedLevel方法,每当玩家完成一个特定的级别时被调用。这个方法会为所有玩家解锁一个新的等级和更新玩家的进度,并将它们带入到一个新的级别里。(advanceToLevel返回的bool值将被忽略,因为在上一行LevelTracker.unlockLevel被调用时,该等级的被解锁已经是已知的了)

你可以通过Player类创建一个新的玩家实例,看看当玩家完成一个级别会发生什么:

var player = Player(name: "Argyrios")

player.completedLevel(1)

println("highest unlocked level is now (LevelTracker.highestUnlockedLevel)")

// prints "highest unlocked level is now 2"

如果你创建了第二个玩家,若你想进入到一个任何一个玩家都没能解锁的游戏等级中,试图设置玩家的当前等级失败:

player = Player(name: "Beto")

if player.tracker.advanceToLevel(6) {

  1. println("player is now on level 6")

} else {

  1. println("level 6 has not yet been unlocked")

}

// prints "level 6 has not yet been unlocked"

Welcome-to-Swift-11方法(Methods)的更多相关文章

  1. Swift中方法(method)所谓的lazy绑定简介

    我们知道在ruby之类的动态语言中对象方法可以先从类中预先抽取,然后再应用到某个具体对象上.这称为无绑定的method对象,也可以叫做lazy绑定. 下面举个例子: irb(main):004:0&g ...

  2. 11 Clever Methods of Overfitting and how to avoid them

    11 Clever Methods of Overfitting and how to avoid them Overfitting is the bane of Data Science in th ...

  3. Swift中方法闭包参数不能省略括号的一种情况

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 我们知道在swift中,如果方法的最后一个参数是一个闭包类型, ...

  4. Swift - 初始化方法返回nil表示初始化失败

    自Swift1.1开始,初始化init方法便有返回nil的能力.我们可以在init声明的时候在其后面加上一个 ? 或者 ! 来表示初始化失败时可能返回nil. 比如,给Int类添加一个将字符串初始化成 ...

  5. [swift]初始化方法自己主动继承

    子类默认不会继承父类的初始化方法,然而,假设某种条件满足的话.父类的初始化方法还是能够继承给子类.在通常情况下,这意味着你不必复写父类的初始化方法.在安全的前提下能够以最低的代价继承父类的初始化方法. ...

  6. Vue.js 源码分析(五) 基础篇 方法 methods属性详解

    methods中定义了Vue实例的方法,官网是这样介绍的: 例如:: <!DOCTYPE html> <html lang="en"> <head&g ...

  7. 初学swift笔记 方法(九)

    import Foundation /* 方法 */ //实例方法 一定需要依附于对象 class MyPoint { var x: Double = 0.0 var y: Double = 0.0 ...

  8. Swift - 数组排序方法(附样例)

    下面通过一个样例演示如何对数组元素进行排序.数组内为自定义用户对象,最终要实现按用户名排序,数据如下: 1 2 3 4 var userList = [UserInfo]() userList.app ...

  9. swift向方法传数组参数的语法

    总是记不住向方法中传数组参数的语法,所以记录一下. func calculateStatistics(scores:[Int]) -> (min:Int,max:Int,sum:Int) { v ...

随机推荐

  1. HDU 2639 Bone Collector II (01背包,第k解)

    题意: 数据是常规的01背包,但是求的不是最大容量限制下的最佳解,而是第k佳解. 思路: 有两种解法: 1)网上普遍用的O(V*K*N). 2)先用常规01背包的方法求出背包容量限制下能装的最大价值m ...

  2. 关系代数演算So Easy

    关系代数运算So Easy 关系代数是以关系为运算的一组高级运算的集合.由于定义为属性个数 相同的元组的集合,因此集合代数的操作就可以引入到关系代数中.关系代数也可以看做是一种抽象的查询语言,是对关系 ...

  3. [OpenMP] 并行计算入门

    OpenMP并行计算入门 个人理解 OpenMP是一种通过共享内存并行系统的多处理器程序设计的编译处理方案,通过预编译指令告诉编译器哪些代码块需要被并行化,通过拷贝代码块实现并行程序.对于循环的并行化 ...

  4. 如何使用TensorFlow Hub和代码示例

    任何深度学习框架,为了获得成功,必须提供一系列最先进的模型,以及在流行和广泛接受的数据集上训练的权重,即与训练模型. TensorFlow现在已经提出了一个更好的框架,称为TensorFlow Hub ...

  5. python 基础之while无限循环

    用户登录程序 username = "chenxi" passwed = "testki" counter = 0 while counter < 3: ...

  6. axios的post请求方法---以Vue示例

    Axios向后端提交数据的参数格式是json,而并非用的是form传参,post表单请求提交时,使用的Content-Type是application/x-www-form-urlencoded,而使 ...

  7. C#经典面试题——递归运算

    今天开始写递归,然而始终不得甚解.借鉴别人的理解:假设我们现在都不知道什么是递归,我们自然想到打开浏览器,输入到谷歌的网页,我们点击搜索递归,然后我们在为维基百科中了解到了递归的基本定义,在了解到了递 ...

  8. React动态import()

    React动态import() react-router@v4代码分离,推荐的import().这里分享webpack配置和使用方法. 首先安装两个必须的包 cnpm i react-loadable ...

  9. const 修饰成员函数 前后用法(effective c++ 03)

    目录 const在函数后面 const修饰成员函数的两个作用 const在函数前面 总结 const在函数后面 类的成员函数后面加 const,表明这个函数不会对这个类对象的数据成员(准确地说是非静态 ...

  10. 如何使用postman做接口测试

    1.get请求传参 只要是get请求都可以在浏览器中直接发: 在访问地址后面拼  ?key=value&key=value 例如: 在浏览器中直接输入访问地址,后面直接拼需要传给服务器的参数 ...