본문 바로가기
프로그래밍/Swift

[Swift] Methods

by 별준 2021. 12. 26.

References

Contents

  • Instance Methods
  • mutating
  • Type Methods

메소드(methods)는 특정한 타입과 연관된 함수입니다. 클래스, 구조체, 열거형은 인스턴스 메소드(instance methods)를 정의할 수 있는데, 이는 주어진 타입의 인스턴스에서 특정한 태스크나 기능을 수행합니다. 또한, 타입 메소드(type methods)를 정의할 수 있는데, 이는 타입 그자체와 연관되어 있습니다. 

 


Instance Methods

인스턴스 메소드는 특정 클래스, 구조체, 열거형의 인스턴스에 종속된 함수입니다. 이 메소드들은 그 인스턴스들의 기능을 서포트하는데, 인스턴스 속성에 액세스하거나 수정하는 방법을 제공하거나 인스턴스의 목적과 관련된 기능들을 제공합니다. 인스턴스 메소드는 함수의 문법과 완전히 동일합니다.

 

인스턴스 메소드는 이 메소드가 속한 타입의 괄호 안에서 작성하면 됩니다. 인스턴스 메소드는 암시적으로 다른 모든 인스턴스 메소드나 그 타입의 속성에 액세스할 수 있습니다. 인스턴스 메소드는 오직 그 타입의 지정된 인스턴스에서 호출될 수 있는데, 존재하는 인스턴스 없이 홀로 호출될 수는 없습니다.

 

아래 예제 코드는 간단한 Counter 클래스입니다. 이 클래스는 액션이 발생한 횟수를 카운트합니다.

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

Counter 클래스는 3개의 인스턴스 메소드를 정의하고 있습니다.

  • increment() : count 1 증가
  • increment(by: Int) : 지정된 정수만큼 count 증가
  • reset() : count를 0으로 reset

 

속성와 동일한 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

 

The self Property

타입의 모든 인스턴스는 self라는 암시적인 속성을 가지고 있습니다. 이는 인스턴스 그 자체와 동등합니다. self 속성을 사용하여 인스턴스 메소드 내에서 현재 인스턴스를 참조할 수 있습니다.

increment() 메소드는 self를 사용하여 다음과 같이 작성될 수 있습니다.

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

실제로는 self를 자주 사용할 필요는 없습니다. 만약 명시적으로 self를 사용하지 않는다면, Swift는 메소드 내에서 인스턴스의 속성이나 메소드를 사용할 때 현재 인스턴스의 속성이나 메소드를 참조한다고 가정합니다. 즉, Counter 클래스에 정의된 3개의 인스턴스 메소드에서 count의 사용은 self. 이 생략된 것이라고 볼 수 있습니다.

 

self를 사용해야하는 주요한 예외는 인스턴스의 메소드의 파라미터 이름이 인스턴스의 속성의 이름과 동일할 때 입니다. 이러한 상황에서는 파라미터의 이름이 우선시되므로 self를 사용하여 파라미터와 구분하여 속성을 참조할 필요가 있습니다.

다음 예제 코드는 메소드의 파라미터 x와 인스턴스이 속성 x를 구분하기 위해 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"

 

Modifying Value Types from Within Instance Methods

구조체와 열거형은 value type입니다. 기본적으로 value type의 속성은 해당 인스턴스 메소드 내에서 수정될 수 없습니다. 

그러나 만약 특정 메소드에서 구조체나 열거형의 속성을 수정해야 한다면, 수정할 수 있도록 동작을 변경할 수 있습니다. 이 메소드는 메소드 내에서 속성을 변경할 수 있고, 메소드가 종료될 때 모든 변경 사항이 원래 구조체에 기록됩니다. 메소드는 변경되는 자신의 속성을 완전히 새로운 인스턴스에 할당할 수 있고, 새로운 인스턴스는 메소드가 끝날 때 기존 인스턴스를 대체합니다.

 

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)"

 

상수 구조체에서는 mutating 메소드를 호출할 수 없는데, 이는 상수 구조체의 속성은 변경될 수 없기 때문입니다. 구조체 내에 변수 속성이 존재하더라도 해당 구조체가 상수로 정의되었다면 변경은 불가능합니다.

let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)
// this will report an error

 

 

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)
    }
}

새로 작성한 Point 구조체에서의 mutating moveby(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

 


Type Methods

타입 자체로 호출할 수 있는 메소드를 정의할 수 있는데, 이러한 종류의 메소드를 타입 메소드(type methods)라고 합니다. 타입 메소드는 static 키워드를 func 키워드 앞에 붙여서 정의할 수 있습니다. 클래스에서는 대신 class 키워드를 사용합니다. class 키워드는 subclass가 superclass의 메소드 구현을 오버라이드할 수 있도록 해주기도 합니다.

 

타입 메소드도 dot 문법으로 호출할 수 있습니다. 그러나 타입의 인스턴스가 아닌 타입으로 호출합니다.

아래는 SomeClass라는 클래스의 타입 메소드를 호출하는 방법을 보여줍니다.

class SomeClass {
    class func someTypeMethod() {
        // type method implementation goes here
    }
}
SomeClass.someTypeMethod()

타입 메소드 바디안에서 암시적인 self 속성은 타입의 인스턴스가 아닌 타입 그 자체를 참조합니다. 이는 self를 타입 속성과 타입 메소드 파라미터를 구분하는데 사용할 수 있다는 것을 의미합니다.

 

일반적으로 타입 메소드 내에서 사용하는 (unqualified)메소드나 속성 이름은 다른 타입 레벨의 메소드와 속성을 참조합니다. 타입 메소드는 타입 이름을 붙일 필요 없이 다른 타입 메소드를 호출할 수 있습니다. 비슷하게, 구조체나 열거형의 타입 메소드는 타입 이름을 붙이지 않고 타입 속성의 이름으로 타입 속성에 액세스할 수 있습니다.

 

아래 예제 코드는 LevelTracker라는 구조체를 정의하고 있는데, 이 구조체는 게임 스테이지나 레벨로 플레이어의 진행 상태를 추적합니다. 싱글 플레이어 게임이지만, 여러 플레이어의 정보를 하나의 디바이스 저장할 수 있습니다.

게임을 처음 플레이하면 레벨 1을 제외한 나머지 레벨의 게임은 잠겨있습니다. 플레이어가 하나의 레벨을 끝날 때마다, 디바이스의 모든 플레이어에게 그 레벨이 언락됩니다. 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를 반환합니다.

 

또한, LevelTracker는 개별 플레이어의 게임 진행 상태를 추적하는데, 이는 인스턴스 속성인 currentLevel을 사용하여 현재 플레이어가 진행중인 레벨 값을 저장합니다.

currentLevel 속성을 관리하기 위해서 advance(to:)라는 인스턴스 메소드를 정의합니다. currentLevel의 값을 업데이트하기 전에 이 메소드는 요청된 새로운 레벨이 이미 언락되어 있는지 체크합니다. advance(to:) 메소드는 요청된 레벨로 currentLevel를 설정할 수 있는지에 대한 Boolean 값을 반환합니다. 

여기서 @discardableResult attribute를 사용하고 있는데, 이는 advance 메소드가 호출될 때 리턴 값을 무시하는 것이 실수라고 체크할 필요가 없기 때문에 이 attribute를 사용하였습니다. 이에 관련된 내용은 다음에 attribute에 관해 살펴볼 때 더 자세히 알아보겠습니다.

 

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:)이라는 메소드를 제공하는데, 이는 플레이어가 특정 레벨을 완료했을 때 호출됩니다. 이 메소드는 모든 플레이어에게 다음 레벨을 언락시키고, 플레이어의 진행상태를 한 단계 업데이트합니다.

 

새로운 플레이어를 위해서 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"

 

댓글