메소드는 특정 타입과 연관된 함수이다. 클래스, 구조체, 열거형은 인스턴스 메소드를 정의할 수 있다. 이는 주어진 타입의 인스턴스와 작동하는 기능 또는 특정한 업무를 캡슐화한다. 클래스, 구조체, 열거형은 타입 그 자체와 연관된 타입 메소드 역시 정의할 수 있다. 타입 메소드는 Objective-C의 클래스 메소드와 비슷하다.
Swift에서 구조체와 열거형이 메소드를 정의할 수 있다는 사실은 클래스만이 메소드를 정의할 수 있는 C, Objective-C와의 중요한 차이점 중 하나다.
인스턴스 메소드(instance method)
는 특정 클래스, 구조체, 열거형의 인스턴스에 속한 함수다. 인스턴스 프로퍼티에 접근하고 수정하는 방식을 제공하거나, 인스턴스의 목적과 관계된 기능을 제공함으로써 이들 인스턴스의 기능을 지원한다. 인스턴스 메소드는 함수와 정확히 같은 문법을 갖고 있다.
인스턴스 메소드는 그 타입의 다른 인스턴스 메소드와 프로퍼티에 암시적으로 접근할 수 있다. 인스턴스 메소드는 그것이 속한 타입의 특정 인스턴스에서만 호출될 수 있다. 존재하는 인스턴스 없이 고립되어 호출될 수는 없다.
class Counter {
var count = 0
func increment() {
count += 1
}
func increment(by amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
Counter
클래스는 세 가지 인스턴스 메소드와 count
변수 프로퍼티를 정의한다.
increment()
: count
를 1 증가시킨다.increment(by: Int)
: count
를 특정 정수만큼 증가시킨다.reset()
: count
를 0으로 되돌린다.다음과 같이 점 문법을 사용하여 인스턴스 메소드를 호출할 수 있다.
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는 메소드 안에서 알려진 이름의 프로퍼티나 메소드를 사용할 때 현재 인스턴스의 프로퍼티나 메소드를 참조하는 것으로 가정한다.
인스턴스 메소드를 위한 매개 변수 이름과 인스턴스의 프로퍼티 이름이 같을 때는 예외다. 이 경우에 매개 변수 이름은 우선권을 가지며, 프로퍼티를 우선적으로 참조하는 방법에 대한 필요성이 생긴다. self
프로퍼티를 사용한다면 매개 변수 이름과 프로퍼티 이름을 구별할 수 있다.
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로 가정한다.
구조체와 열거형은 값타입이다. 기본적으로, 값 타입의 프로퍼티는 인스턴스 메소드 안에서 수정할 수 없다.
만약 특정 메소드 안에서 구조체나 열거형의 프로퍼티를 수정해야 할 경우, mutating
키워드를 명시한다. 메소드는 그 안에서 프로퍼티를 수정할 수 있게 되며, 그것이 만드는 모든 수정 사항은 메소드가 끝날 때 원본 구조체에 그 결과를 덮어 쓰는 방식으로 적용된다.
이 메소드는 완전히 새로운 인스턴스를 암시적인 self
프로퍼티에 할당할 수도 있다. 이 새로운 인스턴스는 메소드가 끝나는 시점에서 기존의 인스턴스를 대체한다.
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)"
상수 구조체에는 변경 메소드(mutating method)
를 호출할 수 없다. 변수 프로퍼티일지라도 변경될 수 없기 때문이다.
변경 메소드는 새로운 인스턴스를 암시적인 self
프로퍼티에 할당할 수 있다.
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 is now equal to .high
ovenLight.next()
// ovenLight is now equal to .off
인스턴스 메소드는 특정 타입의 인스턴스에서 호출하는 메소드다. 타입 자체에서 호출하는 메소드도 정의할 수 있다. 이러한 메소드를 타입 메소드(type method)
라 부른다. 메소드의 func
키워드 이전에 static
키워드를 작성하여 타입 메소드임을 알린다. 클래스는 자식 클래스가 부모 클래스의 메소드 구현을 오버라이드하는 것을 허용하기 위해 class
인스턴스를 사용할 수도 있다.
Objective-C에서 타입 레벨 메소드는 오직 Objective-C 클래스로만 정의할 수 있었다. Swift에서는 모든 클래스, 구조체, 열거형에 대해 타입 레벨 메소드를 정의할 수 있다. 각 타입 메소드는 병시적으로 그 타입이 지원하는 범위에 맞춰진다.
타입 메소드는 다른 인스턴스 메소드처럼 점 문법으로 호출된다. 하지만 의 인스턴스가 아닌 타입 자체에서만 타입 메소드를 호출할 수 있다.
class SomeClass {
class func someTypeMethod() {
// type method implementation goes here
}
}
SomeClass.someTypeMethod()
타입 메소드의 바디에서, 암시적인 self
프로퍼티는 타입의 인스턴스가 아니라 타입 그 자체를 참조한다. 이는 인스턴스 프로퍼티와 인스턴스 매개 변수에서 그랬던 것처럼 타입 프로퍼티와 타입 메소드 프로퍼티를 명확히 구분하기 위해 self
를 사용할 수 있다는 뜻이다.
더 보편적으로, 타입 메소드의 바디 안에서 사용하는 모든 자격이 없는 메소드와 프로퍼티 이름은 다른 타입 레벨의 메소드와 프로퍼티를 참조하게 된다. 타입 이름의 접두사 없이 다른 타입 메소드를 사용하는 것이 가능하다.
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
타입 프로퍼티에 저장된다. 첫 번째 타입 함수인 unlock(_:)
은 새로운 레벨이 풀릴 때마다 highestUnlockedLevel
값을 업데이트 한다. 두 번째 타입 함수 isUnlocked(_:)
는 특정 레벨이 이미 풀렸을 경우 true
를 반환한다.
타입 프로퍼티와 타입 메소드에 더해, LevelTracker
는 각 플레이어들의 진행도를 추적한다. currentLevel
인스턴스 프로퍼티를 사용하여 플레이어가 현재 플레이 중인 레벨을 확인한다.
currentLevel
프로퍼티의 관리를 돕기 위해, LevelTracker
는 advance(to:)
인스턴스 메소드를 정의한다. currentLevel
가 업데이트 되기 전에, 이 메소드는 요구되는 새로운 레벨이 이미 풀렸는지를 확인한다. advance(to:)
메소드는 currentLevel
을 설정하는 게 가능한지 불가능한지를 Boolean
값으로 반환한다. 반환 값을 무시하고 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
}
}
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 has not yet been unlocked")
}
// Prints "level 6 has not yet been unlocked"