Swift编程十二(方法)

2019-06-21  本文已影响0人  酒茶白开水

案例代码下载

方法

方法是与特定类型相关联的函数。类,结构和枚举都可以定义实例方法,这些方法封装了用于处理给定类型的实例的特定任务和功能。类,结构和枚举也可以定义类型方法,它们与类型本身相关联。类型方法类似于Objective-C中的类方法。

事实是在Swift中结构和枚举可以定义方法是与C和Objective-C的主要区别。在Objective-C中,类是唯一可以定义方法的类型。在Swift中,可以选择是定义类,结构还是枚举,并且仍然可以灵活地在创建的类型上定义方法。

实例方法

实例方法是属于特定类,结构或枚举的实例的函数。它们通过提供访问和修改实例属性的函数,或通过提供与实例用途相关的函数。实例方法与函数具有完全相同的语法,如函数的描述。

在其所属类型的开始和结束括号内编写实例方法。实例方法可以隐式访问该类型的所有其他实例方法和属性。实例方法只能在其所属类型的特定实例上调用。如果没有现有实例,则无法单独调用它。

这是一个定义简单Counter类的示例,可用于计算操作发生的次数:

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

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

Counter类还声明一个变量属性count,以跟踪当前计数器值。

使用与属性相同的点语法调用实例方法:

let counter = Counter()
counter.increment()
counter.increment(by:)

函数参数既可以具有名称(在函数体内使用),也可以具有参数标签(在调用函数时使用),如函数参数标签和参数名称中所述。方法参数也是如此,因为方法只是与类型相关联的函数。

self属性

类型的每个实例都有一个名为self的隐式属性,它与实例本身完全等效。可以使用self属性在其自己的实例方法中引用当前实例。

上面示例中的increment()方法可能是这样编写的:

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

实际上,不需要经常编写self代码。如果没有显式写入self,则Swift会假定在方法中使用已知属性或方法名称时引用当前实例的属性或方法。这个假设通过在三个Counter实例方法中使用count(而不是self.count)来证明。

当实例方法的参数名称与该实例的属性具有相同的名称时,会发生此规则的主要例外。在这种情况下,参数名称优先,并且有必要以更合格的方式引用属性。可以使用该self属性来区分参数名称和属性名称。

这里,self消除了方法参数x和实例属性义x之间的歧义:

struct Point {
    var x = 0.0, y = 0.0
    func isToTheRightOf(x: Double) -> Bool {
        return self.x > x
    }
}
let point = Point(x: 4.0, y: 5.0)
if point.isToTheRightOf(x: 1.0) {
    print("This point is to the right of the line where x == 1.0")
}

如果没有self前缀,Swift会假设方法都引用了被调用的方法参数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 point = Point(x: 1.0, y: 1.0)
point.moveBy(x: 2.0, y: 3.0)
print(point)

Point上面的结构定义了一个变异moveBy(x:y:)方法,它将Point实例移动一定量。此方法实际上修改了调用它的点,而不是返回一个新点。该mutating关键字被添加到它的定义,使之能够修改其属性。

请注意,不能在结构类型的常量上调用变异方法,因为它的属性不能更改,即使它们是变量属性,如常量结构实例的存储属性中所述:

let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)//会报错

在变异方法中赋值给self

变异方法可以为隐式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)
    }
}

此版本的变异moveBy(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.next()
print(ovenLight)

此示例定义三态切换的枚举。每次调用其next()方法时开关在三种不同的电源状态(off,low和high)之间循环。

类型方法

如上所述,实例方法是在特定类型的实例上调用的方法。还可以定义在类型本身上调用的方法。这些方法称为类型方法。通过在方法的func关键字之前写入static关键字来指示类型方法。类也可以使用class关键字来允许子类覆盖超类的该方法的实现。

注意: 在Objective-C中,只能为Objective-C类定义类型级方法。在Swift中,可以为所有类,结构和枚举定义类型级方法。每种类型方法都明确限定为它支持的类型。

使用点语法调用类型方法,如实例方法。但是,在类型上调用类型方法,而不是在该类型的实例上调用。以下是在类调用上调用类型方法SomeClass的方法:

class SomeClass {
    class func someTypeMethod() {
        print("someTypeMethod")
    }
}
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。(请注意,这些类型方法可以访问highestUnlockedLeveltype属性,而无需将其写为LevelTracker.highestUnlockedLevel。)

除了类型属性和类型方法之外,LevelTracker还可以跟踪单个玩家在游戏中的进度。它使用一个名为currentLevel的实例属性来跟踪玩家当前正在玩的等级。

要帮助管理currentLevel属性,LevelTracker定义一个名为advance(to:)的实例方法。在更新currentLevel之前,此方法检查所请求的新级别是否已解锁。advance(to:)方法返回一个布尔值,以指示它是否实际上能够设置currentLevel。因为调用advance(to:)方法忽略返回值的代码不一定是错误,所以此函数用@discardableResult属性标记。有关此属性的更多信息,请参阅属性。

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(LevelTracker.highestUnlockedLevel)

如你创建了第二个玩家,试图将其移动到游戏中任何玩家尚未解锁的等级,那么设置玩家当前等级的尝试将失败:

player = Player(name: "Beto")
if player.tracker.advance(to: 6) {
    print("player is now on level 6")
} else {
    print("evel 6 has not yet been unlocked")
}
上一篇下一篇

猜你喜欢

热点阅读