Methods
Methods
메서드(method)는 어떤 타입에 속한 함수예요. 클래스, 구조체, 열거형 모두 인스턴스 메서드를 정의할 수 있고, 거기에 더해 타입 자체에 속한 타입 메서드도 정의할 수 있죠. 구조체와 열거형에 메서드를 붙일 수 있다는 점은 Swift가 C나 Objective-C와 크게 달라진 부분이에요. Objective-C에서는 클래스만 메서드를 가질 수 있었는데, Swift에서는 어떤 타입을 만들든 그 위에 메서드를 자유롭게 정의할 수 있거든요.
본문
인스턴스 메서드 (Instance Methods)
인스턴스 메서드(instance method)는 특정 클래스, 구조체, 열거형의 인스턴스에 속한 함수예요. 인스턴스의 프로퍼티를 읽고 바꾸는 방법을 제공하거나, 인스턴스가 맡은 일과 관련된 기능을 제공하는 식으로 그 인스턴스를 돕는 역할을 하죠. 인스턴스 메서드의 문법은 함수와 똑같아요. 함수 챕터에서 본 그대로예요.
인스턴스 메서드는 자신이 속한 타입의 중괄호 안에 작성해요. 그리고 그 타입의 다른 인스턴스 메서드와 프로퍼티에 암시적으로 접근할 수 있어요. 한 가지 주의할 점은, 인스턴스 메서드는 자기 타입의 특정 인스턴스에서만 부를 수 있다는 거예요. 인스턴스 없이 홀로 호출할 수는 없어요.
간단한 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()은 카운터를 0으로 되돌려요.
그리고 현재 카운터 값을 기억하는 변수 프로퍼티 count도 선언돼 있죠.
인스턴스 메서드는 프로퍼티와 똑같이 점(dot) 문법으로 호출해요.
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
함수 파라미터는 함수 안에서 쓰는 이름(parameter name)과 호출할 때 쓰는 인자 레이블(argument label)을 따로 가질 수 있어요. 메서드 파라미터도 마찬가지예요. 메서드는 결국 타입에 속한 함수니까 같은 규칙이 그대로 적용돼요.
self 프로퍼티 (The self Property)
타입의 모든 인스턴스에는 self라는 암시적 프로퍼티가 있어요. 이 프로퍼티는 바로 인스턴스 자신과 정확히 같은 값이에요. 인스턴스 메서드 안에서는 이 self를 써서 현재 인스턴스를 가리킬 수 있어요.
아까 예시의 increment() 메서드는 이렇게 쓸 수도 있었어요.
func increment() {
self.count += 1
}
하지만 실제 코드에서 self를 매번 쓸 필요는 거의 없어요. self를 생략하면, 메서드 안에서 알고 있는 프로퍼티나 메서드 이름을 쓸 때 Swift가 자동으로 '현재 인스턴스의 프로퍼티나 메서드'라고 가정하거든요. Counter의 인스턴스 메서드 세 개 안에서 self.count 대신 그냥 count를 쓴 것도 바로 그 이유예요.
이 규칙에는 중요한 예외가 있어요. 인스턴스 메서드의 파라미터 이름이 그 인스턴스의 프로퍼티 이름과 같을 때예요. 이런 상황에서는 파라미터 이름이 우선이라서, 프로퍼티를 가리키려면 더 명확하게 표현해야 해요. 바로 그때 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 접두사를 빼면, 두 곳의 x를 모두 메서드 파라미터인 x로 해석해 버려요. 그래서 프로퍼티 쪽에는 self.x를 붙여 명확히 해 주는 거예요.
인스턴스 메서드 안에서 값 타입 수정하기 (Modifying Value Types from Within Instance Methods)
구조체와 열거형은 **값 타입(value type)**이에요. 값 타입은 기본적으로 인스턴스 메서드 안에서 자신의 프로퍼티를 수정할 수 없어요.
하지만 특정 메서드 안에서 구조체나 열거형의 프로퍼티를 바꿔야 한다면, 그 메서드에 mutating(변경) 동작을 허용할 수 있어요. 그렇게 하면 메서드는 자신의 프로퍼티를 메서드 안에서 변경(mutate)할 수 있고, 메서드가 끝나면 바뀐 내용이 원래 구조체에 그대로 기록돼요. 게다가 암시적 self 프로퍼티에 완전히 새로운 인스턴스를 통째로 할당할 수도 있는데, 이때는 메서드가 끝나면 새 인스턴스가 기존 인스턴스를 대체해요.
이 동작을 허용하려면 해당 메서드의 func 키워드 앞에 mutating 키워드를 붙이면 돼요.
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 구조체는 mutating 메서드 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
mutating 메서드 안에서 self에 할당하기 (Assigning to self Within a Mutating Method)
mutating 메서드는 암시적 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)
}
}
이 버전의 mutating moveBy(x:y:) 메서드는 x와 y 값을 목표 위치로 설정한 새로운 구조체를 만들어요. 호출 결과는 앞선 버전과 완전히 똑같아요.
열거형의 mutating 메서드는 암시적 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
이 예시는 삼단 스위치(tri-state switch)를 위한 열거형이에요. next() 메서드를 부를 때마다 스위치가 off, low, high 세 가지 전원 상태 사이를 순환해요.
타입 메서드 (Type Methods)
지금까지 본 인스턴스 메서드는 특정 타입의 인스턴스에서 호출하는 메서드였어요. 그런데 타입 자체에서 호출하는 메서드도 정의할 수 있어요. 이걸 *타입 메서드(type method)*라고 불러요. 타입 메서드는 메서드의 func 키워드 앞에 static 키워드를 붙여서 표시해요. 클래스에서는 class 키워드를 쓸 수도 있어요. 그러면 서브클래스가 슈퍼클래스의 해당 메서드 구현을 오버라이드할 수 있게 돼요.
참고: Objective-C에서는 Objective-C 클래스에 대해서만 타입 수준 메서드를 정의할 수 있었어요. Swift에서는 모든 클래스, 구조체, 열거형에 타입 수준 메서드를 정의할 수 있고, 각 타입 메서드는 자신이 지원하는 타입에 명확히 한정돼요.
타입 메서드도 인스턴스 메서드처럼 점 문법으로 호출해요. 다만 인스턴스가 아니라 타입에 대해 호출한다는 점이 다르죠. 여기 SomeClass라는 클래스에 타입 메서드를 호출하는 방법이 있어요.
class SomeClass {
class func someTypeMethod() {
// type method implementation goes here
}
}
SomeClass.someTypeMethod()
타입 메서드 본문 안에서는 암시적 self 프로퍼티가 인스턴스가 아니라 타입 그 자체를 가리켜요. 그래서 인스턴스 프로퍼티와 인스턴스 메서드 파라미터를 구분하던 것처럼, self를 써서 타입 프로퍼티와 타입 메서드 파라미터를 구분할 수 있어요.
더 일반적으로 말하면, 타입 메서드 본문 안에서 한정되지 않고 쓴 메서드·프로퍼티 이름은 모두 다른 타입 수준의 메서드와 프로퍼티를 가리켜요. 타입 메서드는 다른 타입 메서드를 타입 이름 접두사 없이 그냥 이름만으로 호출할 수 있고, 구조체와 열거형의 타입 메서드도 타입 프로퍼티 이름만으로 접근할 수 있어요.
아래 예시는 LevelTracker라는 구조체를 정의해요. 게임의 여러 레벨·단계를 지나가는 플레이어의 진행 상황을 추적하는 구조체예요. 1인용 게임이지만, 한 기기에서 여러 플레이어의 정보를 저장할 수 있어요.
게임을 처음 시작하면 1레벨 빼고 모든 레벨이 잠겨 있어요. 플레이어가 레벨을 끝낼 때마다 그 레벨이 기기에 있는 모든 플레이어에게 잠금 해제돼요. 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를 반환해요. (이 타입 메서드들은 LevelTracker.highestUnlockedLevel이라고 쓰지 않고도 highestUnlockedLevel 타입 프로퍼티에 접근할 수 있어요.)
타입 프로퍼티와 타입 메서드에 더해, 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 클래스의 인스턴스를 만들고, 플레이어가 1레벨을 끝내면 어떤 일이 생기는지 살펴볼게요.
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".
더 알아보기
- 인스턴스 메서드: 특정 인스턴스에 속한 함수로, 점 문법으로 호출하며 그 타입의 다른 메서드·프로퍼티에 암시적으로 접근해요.
self프로퍼티: 인스턴스 자신을 가리키는 암시적 프로퍼티. 파라미터 이름과 프로퍼티 이름이 겹칠 때 구분하는 데 주로 쓰여요.mutating: 구조체·열거형 같은 값 타입의 메서드가 자신의 프로퍼티를 바꾸도록 허용하는 키워드.self에 새 인스턴스를 통째로 할당할 수도 있어요.- 타입 메서드:
static(또는 클래스에서class) 키워드로 정의하며 타입 자체에서 호출해요.self는 타입 자신을 가리켜요. - 관련 챕터: 함수(Functions), 프로퍼티(Properties), 속성(Attributes)