本页包含内容:html
方法是与某些特定类型相关联的函数。类、结构体、枚举均可以定义实例方法;实例方法为给定类型的实例封装了具体的任务与功能。类、结构体、枚举也能够定义类型方法;类型方法与类型自己相关联。类型方法与 Objective-C 中的类方法(class methods)类似。ios
结构体和枚举可以定义方法是 Swift 与 C/Objective-C 的主要区别之一。在 Objective-C 中,类是惟一能定义方法的类型。但在 Swift 中,你不只能选择是否要定义一个类/结构体/枚举,还能灵活地在你建立的类型(类/结构体/枚举)上定义方法。swift
实例方法是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供与实例目的相关的功能,并以此来支撑实例的功能。实例方法的语法与函数彻底一致,详情参见函数。函数
实例方法要写在它所属的类型的先后大括号之间。实例方法可以隐式访问它所属类型的全部的其余实例方法和属性。实例方法只能被它所属的类的某个特定实例调用。实例方法不能脱离于现存的实例而被调用。ui
下面的例子,定义一个很简单的Counter
类,Counter
能被用来对一个动做发生的次数进行计数:spa
class Counter { var count = 0 func increment() { ++count } func incrementBy(amount: Int) { count += amount } func reset() { count = 0 } }
Counter
类定义了三个实例方法:code
increment
让计数器按一递增;incrementBy(amount: Int)
让计数器按一个指定的整数值递增;reset
将计数器重置为0。Counter
这个类还声明了一个可变属性count
,用它来保持对当前计数器值的追踪。htm
和调用属性同样,用点语法(dot syntax)调用实例方法:游戏
let counter = Counter() // 初始计数值是0 counter.increment() // 计数值如今是1 counter.incrementBy(5) // 计数值如今是6 counter.reset() // 计数值如今是0
函数参数能够同时有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用),详情参见指定外部参数名。方法参数也同样(由于方法就是函数,只是这个函数与某个类型相关联了)。ci
Swift 中的方法和 Objective-C 中的方法极其类似。像在 Objective-C 中同样,Swift 中方法的名称一般用一个介词指向方法的第一个参数,好比:with
,for
,by
等等。前面的Counter
类的例子中incrementBy(_:)
方法就是这样的。介词的使用让方法在被调用时能像一个句子同样被解读。
具体来讲,Swift 默认仅给方法的第一个参数名称一个局部参数名称;默认同时给第二个和后续的参数名称局部参数名称和外部参数名称。这个约定与典型的命名和调用约定相适应,与你在写 Objective-C 的方法时很类似。这个约定还让富于表达性的方法在调用时不须要再限定参数名称。
看看下面这个Counter
的另外一个版本(它定义了一个更复杂的incrementBy(_:)
方法):
class Counter { var count: Int = 0 func incrementBy(amount: Int, numberOfTimes: Int) { count += amount * numberOfTimes } }
incrementBy(_:numverOfTimes:)
方法有两个参数: amount
和numberOfTimes
。默认状况下,Swift 只把amount
看成一个局部名称,可是把numberOfTimes
即看做局部名称又看做外部名称。下面调用这个方法:
let counter = Counter() counter.incrementBy(5, numberOfTimes: 3) // counter 的值如今是 15
你没必要为第一个参数值再定义一个外部变量名:由于从函数名incrementBy(_numberOfTimes:)
已经能很清楚地看出它的做用。可是第二个参数,就要被一个外部参数名称所限定,以便在方法被调用时明确它的做用。
上面描述的这种默认行为意味着在 Swift 中,定义方法使用了与 Objective-C 一样的语法风格,而且方法将以天然且富于表达性的方式被调用。
有时为方法的第一个参数提供一个外部参数名称是很是有用的,尽管这不是默认的行为。你本身能够为第一个参数添加一个显式的外部名称。
相反,若是你不想为方法的第二个及后续的参数提供一个外部名称,能够经过使用下划线(_
)做为该参数的显式外部名称,这样作将覆盖默认行为。
类型的每个实例都有一个隐含属性叫作self
,self
彻底等同于该实例自己。你能够在一个实例的实例方法中使用这个隐含的self
属性来引用当前实例。
上面例子中的increment
方法还能够这样写:
func increment() { self.count++ }
实际上,你没必要在你的代码里面常常写self
。不论什么时候,只要在一个方法中使用一个已知的属性或者方法名称,若是你没有明确地写self
,Swift 假定你是指当前实例的属性或者方法。这种假定在上面的Counter
中已经示范了:Counter
中的三个实例方法中都使用的是count
(而不是self.count
)。
使用这条规则的主要场景是实例方法的某个参数名称与实例的某个属性名称相同的时候。在这种状况下,参数名称享有优先权,而且在引用属性时必须使用一种更严格的方式。这时你可使用self
属性来区分参数名称和属性名称。
下面的例子中,self
消除方法参数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) { 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 就认为两次使用的x
都指的是名称为x
的函数参数。
结构体和枚举是值类型。通常状况下,值类型的属性不能在它的实例方法中被修改。
可是,若是你确实须要在某个特定的方法中修改结构体或者枚举的属性,你能够选择变异(mutating)
这个方法,而后方法就能够从方法内部改变它的属性;而且它作的任何改变在方法结束时还会保留在原始结构中。方法还能够给它隐含的self
属性赋值一个全新的实例,这个新实例在方法结束后将替换原来的实例。
要使用变异
方法,将关键字mutating
放到方法的func
关键字以前就能够了:
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
结构体定义了一个可变方法(mutating method)moveByX(_:y:)
用来移动点。该方法在被调用时修改了这个点,而不是返回一个新的点。方法定义时加上了mutating
关键字,从而能够修改属性。
注意,不能在结构体类型的常量上调用可变方法,由于其属性不能被改变,即便属性是变量属性,详情参见常量结构体的存储属性:
let fixedPoint = Point(x: 3.0, y: 3.0) fixedPoint.moveByX(2.0, y: 3.0) // 这里将会报告一个错误
可变方法可以赋给隐含属性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(_:y:)
建立了一个新的结构(它的 x 和 y 的值都被设定为目标值)。调用这个版本的方法和调用上个版本的最终结果是同样的。
枚举的可变方法能够把self
设置为同一枚举类型中不一样的成员:
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 如今等于 .High ovenLight.next() // ovenLight 如今等于 .Off
上面的例子中定义了一个三态开关的枚举。每次调用next()
方法时,开关在不一样的电源状态(Off
,Low
,High
)之间循环切换。
实例方法是被类型的某个实例调用的方法。你也能够定义类型自己调用的方法,这种方法就叫作类型方法。声明结构体和枚举的类型方法,在方法的func
关键字以前加上关键字static
。类可能会用关键字class
来容许子类重写父类的方法实现。
注意
在 Objective-C 中,你只能为 Objective-C 的类定义类型方法(type-level methods)。在 Swift 中,你能够为全部的类、结构体和枚举定义类型方法。每个类型方法都被它所支持的类型显式包含。
类型方法和实例方法同样用点语法调用。可是,你是在类型层面上调用这个方法,而不是在实例层面上调用。下面是如何在SomeClass
类上调用类型方法的例子:
class SomeClass { static func someTypeMethod() { // type method implementation goes here } } SomeClass.someTypeMethod()
在类型方法的方法体(body)中,self
指向这个类型自己,而不是类型的某个实例。这意味着你能够用self
来消除类型属性和类型方法参数之间的歧义(相似于咱们在前面处理实例属性和实例方法参数时作的那样)。
通常来讲,在类型方法的方法体中,任何未限定的方法和属性名称,将会指代本类中其余类型方法和类型属性。一个类型方法能够经过类型方法的名称调用本类中的类型方法,而无需在方法名称前面加上类型名称前缀。一样,也可以直接经过类型属性的名称访问本类中的类型属性,而不须要类型名称前缀。
下面的例子定义了一个名为LevelTracker
结构体。它监测玩家的游戏发展状况(游戏的不一样层次或阶段)。这是一个单人游戏,但也能够存储多个玩家在同一设备上的游戏信息。
游戏初始时,全部的游戏等级(除了等级 1)都被锁定。每次有玩家完成一个等级,这个等级就对这个设备上的全部玩家解锁。LevelTracker
结构体用类型属性和方法监测游戏的哪一个等级已经被解锁。它还监测每一个玩家的当前等级。
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
监测玩家已解锁的最高等级。这个值被存储在类型属性highestUnlockedLevel
中。
LevelTracker
还定义了两个类型方法与highestUnlockedLevel
配合工做。第一个类型方法是unlockLevel
,一旦新等级被解锁,它会更新highestUnlockedLevel
的值。第二个类型方法是levelIsUnlocked
,若是某个给定的等级已经被解锁,它将返回true
。(注意,尽管咱们没有使用相似LevelTracker.highestUnlockedLevel
的写法,这个类型方法仍是可以访问类型属性highestUnlockedLevel
)
除了类型属性和类型方法,LevelTracker
还监测每一个玩家的进度。它用实例属性currentLevel
来监测玩家当前的等级。
为了便于管理currentLevel
属性,LevelTracker
定义了实例方法advanceToLevel
。这个方法会在更新currentLevel
以前检查所请求的新等级是否已经解锁。advanceToLevel
方法返回布尔值以指示是否可以设置currentLevel
。
下面,Player
类使用LevelTracker
来监测和更新每一个玩家的发展进度:
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
实例来监测这个用户的进度。它提供了completedLevel
方法,一旦玩家完成某个指定等级就调用它。这个方法为全部玩家解锁下一等级,而且将当前玩家的进度更新为下一等级。(咱们忽略了advanceToLevel
返回的布尔值,由于以前调用LevelTracker.unlockLevel
时就知道了这个等级已经被解锁了)。
你还能够为一个新的玩家建立一个Player
的实例,而后看这个玩家完成等级一时发生了什么:
var player = Player(name: "Argyrios") player.completedLevel(1) print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)") // 打印输出:highest unlocked level is now 2
若是你建立了第二个玩家,并尝试让他开始一个没有被任何玩家解锁的等级,那么试图设置玩家当前等级将会失败:
player = Player(name: "Beto") if player.tracker.advanceToLevel(6) { print("player is now on level 6") } else { print("level 6 has not yet been unlocked") } // 打印输出:level 6 has not yet been unlocked