공식 문서로 공부하는 Swift (10) - 메소드

ci·2020년 5월 29일
1

Methods

메소드는 특정 타입과 연관된 함수이다. 클래스, 구조체, 열거형은 인스턴스 메소드를 정의할 수 있다. 이는 주어진 타입의 인스턴스와 작동하는 기능 또는 특정한 업무를 캡슐화한다. 클래스, 구조체, 열거형은 타입 그 자체와 연관된 타입 메소드 역시 정의할 수 있다. 타입 메소드는 Objective-C의 클래스 메소드와 비슷하다.

Swift에서 구조체와 열거형이 메소드를 정의할 수 있다는 사실은 클래스만이 메소드를 정의할 수 있는 C, Objective-C와의 중요한 차이점 중 하나다.



인스턴스 메소드 (Instance Methods)

인스턴스 메소드(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라 불리는 암시적 프로퍼티를 갖고 이다. 이 프로퍼티는 인스턴스 그 자신과 동일하다. 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)를 호출할 수 없다. 변수 프로퍼티일지라도 변경될 수 없기 때문이다.


변경 메소드(Mutating Method) 안에서 self에 할당하기

변경 메소드는 새로운 인스턴스를 암시적인 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)

인스턴스 메소드는 특정 타입의 인스턴스에서 호출하는 메소드다. 타입 자체에서 호출하는 메소드도 정의할 수 있다. 이러한 메소드를 타입 메소드(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 프로퍼티의 관리를 돕기 위해, LevelTrackeradvance(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"

0개의 댓글