Methods

Swift/Language Guide 2018.08.30 22:21

[최종 수정일 : 2018.08.29]

원문 : 애플 개발 문서 Swift 4.2 Language Guide - Methods

메소드(Methods)

메소드(Methods)는 특정 타입과 관련된 함수입니다. 클래스, 구조체, 열거형 모두 주어진 타입의 인스턴스로 작업하기 위한 특정 작업과 함수를 캡슐화하는 인스턴스 메소드(instance methods)를 정의할 수 있습니다. 또한, 클래스, 구조체, 열거형은 타입 자체와 관련되어있는 타입 메소드(type methods)를 정의할 수 있습니다. 타입 메소드(type methods)는 Objective-C에서의 클래스 메소드와 비슷합니다.

Swift에서 구조체와 열거형에서 메소드를 정의할수 있다는 사실은, C와 Objective-C와는 가장큰 차이점입니다. Objective-C에서, 클래스는 메소드를 정의할수 있는 유일한 타입입니다. Swift에서는, 클래스, 구조체, 열거형에서 선택할수 있고, 생성한 타입에 대한 메소드 정의를 유연하게 할 수 있습니다.

인스턴스 메소드(Instance Methods)

인스턴스 메소드(instance methods)는 특정 클래스, 구조체, 열거형의 인스턴스에서 속해있는 함수 입니다. 해당 인스턴스 기능을 지원하며, 인스턴스 프로퍼티를 사용하고 수정하는 방법을 제공하거나 인스턴스의 목적과 관련된 함수를 제공합니다. 인스턴스 메소드는 함수와 정확히 같은 문법이며, 함수(Functions)에 설명되어 있습니다.

인스턴스 메소드는 타입의 열고 닫는 괄호(opening and closing braces { }) 안쪽에 작성합니다. 인스턴스 메소드는 다른 모든 인스턴스 메소드와 타입의 프로퍼티를 암시적으로 접근할수 있습니다. 인스턴스 메소드는 타입이 속해있는 특정 인스턴스에서만 호출될수 있습니다. 기존 인스턴스 없이 따로(isolatiion) 호출될 수는 없습니다.

다음은 동작할때마다 숫자를 세는데 사용할 수 있는 간단한 Counter 클래스를 정의한 예제입니다.

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

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

  • increment() 개수를 1만큼 증가 시킵니다.
  • increment(by: Int) 특정 정수 값만큼 개수를 증가 시킵니다.
  • reset() 개수를 0으로 재설정 합니다.

또한, Counter클래스는 현재 개수 값을 유지하기 위해 변수 프로퍼티 count를 선언합니다.

인스턴스 메소드를 프로퍼티 처럼 점 문법(dot syntax)로 호출합니다.

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

함수 매개변수는 이름(함수의 본문에서 사용)과 인자 라벨(함수를 호출할때 사용) 모두 가질수 있으며, 함수 인자 라벨과 매개변수 이름(Function Argument Labels and Parameter Names)에 설명되어 있습니다. 메소드는 타입과 관련된 함수이기 때문에, 메소드 매개변수도 마찬가지 입니다.

self 프로퍼티(The self Property)

언제나 타입의 인스턴스는 암시적으로 인스턴스 자신과 똑같은 프로퍼티 self를 가지고 있습니다. 자신의 인스턴스 메소드에서 현재 인스턴스를 참조하기 위해서 self 프로퍼티를 사용합니다

위의 예제에서 increment()메소드는 다음과 같이 작성할 수 있습니다.

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

실제로(In practice), 코드에서 self를 자주 작성할 필요가 없습니다. 명시적으로 self를 작성하지 않는 경우에, Swift는 메소드안에서 프로퍼티나 메소드 이름을 사용할때마다, 현재 인스턴스의 프로퍼티나 메소드에 참조한다고 가정합니다. 이러한 가정(assumption)은 Counter에 대한 3개의 인스턴스 메소드 안쪽에 (self.count 보다는)count로 사용하는 것으로 증명되었습니다(demonstrated).

이러한 규직은 인스턴스 메소드에 대한 매개변수 이름이 인스턴스의 프로퍼티와 같은 이름을 가질때 중요한 예외가 발생합니다. 이러한 상황에서, 매개변수 이름이 우선순위를 가지고, 더 적합한 방법으로 프로퍼티를 참조해야 합니다. 매개변수 이름과 프로퍼티 이름간에 구별(distinguish)하기 위해 self 프로퍼티를 사용합니다.

여기에서, self는 메소드 매개변수 x와 인스턴스 프로퍼티 x간에 애매모호함을 없애줍니다(disambiguates).

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를 참조하는 것으로 가정할 것입니다.

인스턴스 메소드 안에서 값 타입 수정하기(Modifying Value Types from Within Instance Methods)

구조체와 열거형은 값 타입(value type) 입니다. 기본적으로, 값 타입의 프로퍼티는 인스턴스 메소드 안에서 수정될수 없습니다.

하지만, 특정 메소드에서 구조체나 열거형의 프로퍼티를 수정하길 원하는 경우에, 메소드에 대해 mutaing 동작을 선택할 수 있습니다. 그 메소드는 메소드 안에서 그 프로퍼티를 수정(변경)할 수 있고, 메소드가 끝날때 변경된 모든 내용이 원래 구조체에 다시 작성됩니다. 그 메소드는 암시적인 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 구조체는 특정 값 만큼 Point 인스턴스를 이동시키는, 변경하는(mutating) moveBy(x:y:) 메소드를 정의하였습니다. mutating 키워드는 프로퍼티를 수정하기 위해 그것이 가능하도록 정의하기 위해 추가되었습니다.

프로퍼티가 변경되지 않기 때문에, 변수 매개변수일지라도, 구조체 타입의 상수에서 수정가능한 메소드를 호출할수 없는 것에 주의하며, 상수 구조체 인스턴스의 저장 프로퍼티(Stored Properties of Constant Structure Instances)에 설명되어 있습니다.

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

Mutaing moveBy(x:y:) 메소드 버젼은 x와 y 값을 원하는 위치(target location)로 설정하는 새로운 구조체를 생성합니다. 대체한 버젼의 호출한 최종 결과는 이전 버젼의 호출과 똑같을 것입니다.

열거형에 대한 Mutating 메소드는 같은 열거형의 다른 케이스(case)가 되도록 암시적으로 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

이 예제는 3가지 상태의 switch에 대한 열거형을 정의합니다. 스위치는 next() 메소드가 호출될때마다 3가지 다른 전원 상태(off, low, high)사이에서 순환됩니다.

타입 메소드(Type Methods)

위에서 설명했던 것 처럼 인스턴스 메소드(instance methods)는 특정 타입의 인스턴스에서 호출되는 메소드 입니다. 타입 자체에서 호출되는 메소드를 정의할 수 있습니다. 이러한 종류의 메소드를 타입 메소드(type method)라고 부릅니다. 메소드의 func키워드 앞에 static 키워드를 작성하여 타입 메소들 가리킵니다. 클래스는 하위클래스가 슈퍼클래스의 메소드 구현을 오버라이드(override) 할수 있도록 허용하기 위해 class 키워드를 사용합니다.

주의
Objective-C 에서는, Objective-C 클래스에 대해서만 타입 레벨(type-level) 메소드를 정의할 수 있습니다. Swift에서는, 모든 클래스, 구조체, 열거형에서 타입 레벨(type-level) 메소드를 정의할 수 있습니다. 각 타입 메소드는 명시적으로 지원하는 타입의 범위가 됩니다.

타입 메소드는 인스턴스 메소드처럼, 점 문법(dot syntax)으로 호출됩니다. 하지만, 타입의 인스턴스가 아니라, 타입에서 타입 메소드를 호출합니다. 다음은 SomeClass 클래스에서 타입 메소드를 호출하는 방법입니다.

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

타입 메소드의 본문에서, 암시적인 self 프로퍼티는 타입의 인스턴스가 보다는 타입 자체를 참조합니다. 이는 타입 프로퍼티와 타입 메소드 매개변수간에 애매모호함 없이 self를 사용할 수 있다는 뜻이며, 인스턴스 프로퍼티와 인스턴스 메소드 매개변수에서도 마찬가지 입니다.

더 일반적으로, 규정되지 않은(unqualified) 메소드와 프로퍼티 이름은 타입 메소드의 본문에서 사용하는 것은 타른 타입 레벨(type-level) 메소드와 프로퍼티를 참조할 것입니다. 하나의 타입 메소드는 타입 이름에 접두어를 붙이지 않고, 다른 메소드 이름으로 다른 타입 메소드를 호출할 수 있습니다. 비슷하게, 구조체와 열거형에서의 타입 메소드는 타입 이름 접두어 없이, 타입 프로퍼티의 이름을 사용하여 타입 프로퍼티에 접근할 수 있습니다.

아래 예제는 게임의 레벨과 스테이지의 플레이어의 진행상황을 추적하는 구조체 LevelTracker를 정의합니다. 1인용(single-player) 게임이지만, 한 기기에서 여러명의 플레이어에 대한 정보를 저장할 수 있습니다.

게임을 처음 시작할때 게임의 모든 레벨(레벨 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 프로퍼티로 작업하는 두개의 타입 함수를 정의합니다. 첫번째 타입 함수는 새로운 레벨이 잠금해제 될때, highestUnlockedLevel의 값을 업데이트하는 unlock(_:) 입니다. 두번째 편리한 타입 함수는 특정 레벨이 이미 잠금해제된 경우에 true를 반환하는 isUnlocked(_:) 입니다. (이 타입 메소드가 LevelTracker.highestUnlockedLever 처럼 작성할 필요없이, highestUnlockedLevel 타입 프로퍼티를 접근할수 있다는 것을 주의합니다.)

타입 프로퍼티(type property)와 타입 메소드(type methods) 외에도, LevelTracker은 게임에서 개별 플레이어의 진행을 추적합니다. 현재 게임중인 플레이어의 레벨을 추적하기 위해 인스턴스 프로퍼티 currentLevel를 사용합니다.

currentLevel 프로퍼티를 관리하는데 도움을 주기 위해, LevelTracker은 인스턴스 메소드 advance(to:)를 정의합니다. currentLevel을 업데이트하기 전에, 이 메소드는 요청된 새로운 레벨이 이미 잠금해제되었는지를 검사합니다. advance(to:) 메소드는 currentLevel을 설정가능한지 아닌지를 가리키는 Boolean 값을 반환합니다. advance(to:) 메소드를 호출하는 코드가 반환 값을 무시하는 것이, 꼭 실수라고하는 것이 아니기 때문에, 이 함수는 @discardableResut 속성으로 표시됩니다. 이 속성에 대한 자세한 정보는 속성(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:) 메소드를 제공합니다. 이 메소드는 모든 플레이어에 대해 다음 레벨을 잠금해제하고 플레이어의 진행과정을 다음 레벨로 이동하여 업데이트 합니다. (이전 줄(line)에서 LevelTracker.unlock(_:) 호출해서 그 레벨이 잠금해제 되었는지를 알고 있기 때문에, advance(to:)이 반환하는 Boolean 값은 무시됩니다.)

새로운 플레이어에 대한 Player 클래스의 인스턴스를 생성할 수 있고, 플레이어가 레벨을 완료할때 무슨 일이 생기는지 볼 수 있습니다.

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"


'Swift > Language Guide' 카테고리의 다른 글

Inheritance  (0) 2018.08.30
Subscripts  (0) 2018.08.30
Methods  (0) 2018.08.30
Properties  (0) 2018.08.30
Structures and Classes  (0) 2018.08.30
Enumerations  (0) 2018.08.30
Posted by 까칠코더