Swift5.4 语言指南(十三) 方法
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/)
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/10470993.html
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
方法是与特定类型关联的函数。类,结构和枚举都可以定义实例方法,这些实例方法封装了用于处理给定类型的实例的特定任务和功能。类,结构和枚举也可以定义与类型本身关联的类型方法。类型方法类似于Objective-C中的类方法。
结构和枚举可以在Swift中定义方法这一事实与C和Objective-C的主要不同之处。在Objective-C中,类是唯一可以定义方法的类型。在Swift中,您可以选择是定义类,结构还是枚举,并且仍然可以灵活地在创建的类型上定义方法。
实例方法
实例方法是属于特定类,结构或枚举的实例的函数。它们通过提供访问和修改实例属性的方式,或者通过提供与实例目的相关的功能,来支持那些实例的功能。实例方法具有与函数完全相同的语法,如Functions中所述。
您可以在实例方法的所属类型的大括号内编写一个实例方法。实例方法可以隐式访问该类型的所有其他实例方法和属性。实例方法只能在其所属类型的特定实例上调用。没有现有实例就不能孤立地调用它。
这是定义一个简单Counter
类的示例,该类可用于计算动作发生的次数:
- class Counter {
- var count = 0
- func increment() {
- count += 1
- }
- func increment(by amount: Int) {
- count += amount
- }
- func reset() {
- count = 0
- }
- }
在Counter
类定义了三个实例方法:
increment()
将计数器增加1
。increment(by: Int)
将计数器增加指定的整数量。reset()
将计数器重置为零。
本Counter
类还声明一个变量属性count
,以跟踪当前计数器值。
您使用与属性相同的点语法调用实例方法:
- let counter = Counter()
- // the initial counter value is 0
- counter.increment()
- // the counter's value is now 1
- counter.increment(by: 5)
- // the counter's value is now 6
- counter.reset()
- // the counter's value is now 0
函数参数可以具有名称(供在函数体内使用)和参数标签(供在调用函数时使用),如功能参数标签和参数名称中所述。方法参数也是如此,因为方法只是与类型关联的函数。
自我财产
类型的每个实例都有一个称为的隐式属性self
,它与实例本身完全等效。您可以使用该self
属性在其自己的实例方法中引用当前实例。
increment()
上面示例中的方法可能是这样编写的:
- func increment() {
- self.count += 1
- }
实际上,您不需要self
经常编写代码。如果您未明确编写self
,则Swift会假定您在方法中使用已知的属性或方法名称时,都在引用当前实例的属性或方法。在的三个实例方法中使用count
(而不是self.count
)可以证明这一假设Counter
。
当实例方法的参数名称与该实例的属性具有相同的名称时,将发生此规则的主要例外。在这种情况下,参数名称优先,因此有必要以更限定的方式引用该属性。您可以使用self
属性来区分参数名称和属性名称。
在这里,可以self
消除方法参数x
和实例属性之间的歧义x
:
- struct Point {
- var x = 0.0, y = 0.0
- func isToTheRightOf(x: Double) -> Bool {
- return self.x > x
- }
- }
- let somePoint = Point(x: 4.0, y: 5.0)
- if somePoint.isToTheRightOf(x: 1.0) {
- print("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 {
- var x = 0.0, y = 0.0
- mutating func moveBy(x deltaX: Double, y deltaY: Double) {
- x += deltaX
- y += deltaY
- }
- }
- var somePoint = Point(x: 1.0, y: 1.0)
- somePoint.moveBy(x: 2.0, y: 3.0)
- print("The point is now at (\(somePoint.x), \(somePoint.y))")
- // Prints "The point is now at (3.0, 4.0)"
Point
上面的结构定义了一种变异moveBy(x:y:)
方法,该方法将Point
实例移动一定量。实际上,此方法不是返回新的点,而是修改了调用它的点。将mutating
关键字添加到其定义中,以使其能够修改其属性。
请注意,您无法在结构类型的常量上调用mutating方法,因为它的属性即使是可变属性也无法更改,如“常量结构实例的存储属性”中所述:
- let fixedPoint = Point(x: 3.0, y: 3.0)
- fixedPoint.moveBy(x: 2.0, y: 3.0)
- // this will report an error
在变异方法中分配给自己
变异方法可以为隐式self
属性分配一个全新的实例。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)
- }
- }
此版本的mutationmoveBy(x: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 is now equal to .high
- ovenLight.next()
- // ovenLight is now equal to .off
本示例定义了三态开关的枚举。每次调用其方法时off
,开关都会在三种不同的电源状态(low
和high
)之间循环next()
。
类型方法
如上所述,实例方法是您在特定类型的实例上调用的方法。您还可以定义在类型本身上调用的方法。这些方法称为类型方法。您可以通过static
在方法的func
关键字之前编写关键字来指示类型方法。类可以改用class
关键字,以允许子类覆盖该方法的超类实现。
笔记
在Objective-C中,您只能为Objective-C类定义类型级别的方法。在Swift中,您可以为所有类,结构和枚举定义类型级别的方法。每个类型方法都明确地限定于它支持的类型。
类型方法使用点语法来调用,就像实例方法一样。但是,您在类型上而不是在该类型的实例上调用类型方法。这是在名为的类上调用类型方法的方式SomeClass
:
- class SomeClass {
- class func someTypeMethod() {
- // type method implementation goes here
- }
- }
- SomeClass.someTypeMethod()
在类型方法的主体内,隐式self
属性引用类型本身,而不是该类型的实例。这意味着您可以self
用来区分类型属性和类型方法参数,就像处理实例属性和实例方法参数一样。
更一般而言,您在类型方法主体中使用的任何不合格的方法和属性名称都将引用其他类型级别的方法和属性。一个类型方法可以使用另一个方法的名称来调用另一个类型方法,而无需在其前面加上类型名称。同样,结构和枚举上的类型方法可以使用类型属性的名称访问类型属性,而无需使用类型名称前缀。
下面的示例定义了一个名为的结构LevelTracker
,该结构可跟踪玩家在游戏的不同级别或阶段的进度。这是一款单人游戏,但可以在单个设备上存储多个玩家的信息。
首次玩游戏时,所有游戏级别(除了第一级)都被锁定。每次玩家完成一个关卡时,设备上的所有玩家都将解锁该关卡。该LevelTracker
结构使用类型属性和方法来跟踪游戏的哪些级别已解锁。它还跟踪单个玩家的当前级别。
- 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
结构跟踪所有玩家已解锁的最高级别。此值存储在名为的类型属性中highestUnlockedLevel
。
LevelTracker
还定义了两个类型函数来使用该highestUnlockedLevel
属性。第一个是称为的类型函数unlock(_:)
,该函数highestUnlockedLevel
会在解锁新级别时更新值。第二个是便捷类型的函数,称为isUnlocked(_:)
,true
如果特定级别的数字已被解锁,则返回。(请注意,这些类型方法可以访问highestUnlockedLevel
type属性,而无需将其写为LevelTracker.highestUnlockedLevel
。)
除了其type属性和type方法外,还LevelTracker
跟踪单个玩家在游戏中的进度。它使用称为的实例属性currentLevel
来跟踪玩家当前正在玩的级别。
为了帮助管理currentLevel
属性,LevelTracker
定义了一个称为的实例方法advance(to:)
。在更新之前currentLevel
,此方法检查所请求的新级别是否已被解锁。该advance(to:)
方法返回一个布尔值,以指示它是否真的可以设置currentLevel
。因为调用该advance(to:)
方法忽略返回值的代码不一定是错误的,所以该函数将带有@discardableResult
属性标记。有关此属性的更多信息,请参见Attributes。
该LevelTracker
结构与Player
班级配合使用,如下所示,用于跟踪和更新单个玩家的进度:
- 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
类创建的新实例LevelTracker
来跟踪玩家的进展。它还提供了一种称为的方法complete(level:)
,只要玩家完成特定关卡就将调用该方法。此方法为所有玩家解锁下一个级别,并更新玩家的进度以将他们移至下一个级别。(由于advance(to:)
忽略了布尔返回值,因为已知该级别已被LevelTracker.unlock(_:)
上一行的调用解锁了。)
您可以Player
为新玩家创建该类的实例,并查看该玩家完成第一级后会发生什么:
- var player = Player(name: "Argyrios")
- player.complete(level: 1)
- print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
- // Prints "highest unlocked level is now 2"
如果您创建第二个玩家,尝试将其移动到游戏中尚未被任何玩家解锁的级别,则设置该玩家当前级别的尝试将失败:
- player = Player(name: "Beto")
- if player.tracker.advance(to: 6) {
- print("player is now on level 6")
- } else {
- print("level 6 hasn't yet been unlocked")
- }
- // Prints "level 6 hasn't yet been unlocked"