[Swift] Methods (메소드)

Heeel·2022년 5월 10일
0

Study-Swift 5.6

목록 보기
10/22

참고사이트
:The Swift Programming Language

Methods (메소드)

Class, structure, enumeration과 관련되어 있는 function을 method(메소드)라고 한다. Class, structure, enumeration은 주어진 타입의 인스턴스로 특정한 작업과 기능을 캡슐화하는 instance method(인스턴스 메소드)와 형식 자체와 연관된 type method(타입 메소드)를 정의할 수 있다. Type methods는 Objective-C의 class methods와 비슷하다.

Swift와 C, Objective-C 메소드 간의 가장 큰 차이점은 Objective-C에서는 오직 class에서 methods를 정의할 수 있다. 그러나 Swift에서 methods는 class, structure, enumeration 모두정의할 수 있다. 이는 코드를 작성할 때 선택의 유연성을 제공한다.


Instance Methods (인스턴스 메소드)

Instance methods(인스턴스 메소드)는 특정 class, structure, enumeration의 인스턴스에 속한 함수이다. 인스턴스의 프로퍼티를 접근하고 수정하는 방법을 제공하거나, 인스턴스의 목적과 관련된 기능을 제공함으로써 해당 인스턴스의 기능을 지원한다. Instance methods는 함수의 문법과 동일하다.
함수의 문법은 다음 링크에서 설명한다. Functions

Instance methods는 속한 타입의 다른 instance method와 프로퍼티에 관한 접근을 암시적으로 가진다. 그리고 instance method가 속한 타입의 인스턴스에 의해서만 호출된다. 예제를 살펴보자.

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

Counter class는 3개의 instance method를 가지고, 모두 인스턴스 프로퍼티에 접근하고 수정한다.

  • increment()
  • increment(by: Int)
  • reset()

프로퍼티와 동일한 점(.) 구문을 통해 instance methods를 호출할 수 있다.

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

함수 파라미터는 함수를 호출할 때 사용하는 argument label(인자 라벨)과 함수의 body 내에서 사용되는 parameter name(파라미터 이름)이 있고 이는 메소드 파라미터와 동일하다.(method도 특정한 타입의 함수이므로)
Function Arguemnt Labels and Parameter Names


The self Property (self 프로퍼티)

모든 인스턴스는 인스턴스 자체와 완전히 동일한 self라고 불리는 프로퍼티를 암시적으로 가진다. Instance method 내에서 self프로퍼티를 이용하여 현재의 인스턴스를 참조할 수 있다.

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

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

메소드 내에서 프로퍼티나 메소드를 사용할 때 Swift는 현재의 프로퍼티나 메소드를 참조한다고 가정하기 때문에 self를 명시적으로 작성할 필요가 없다. 이는 위 예제에서 확인할 수 있는데 count+=1은 사실 self.count+=1과 같다.

위 규칙이 적용되지 않는 예외적인 상황이 있다. instance method의 parameter(인자)이름과 인스턴스 프로퍼티의 이름이 같은 경우다. 이 경우는 parameter 이름이 우선시 된다. 이 경우 모호함을 해결하기 위해 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키워드를 생략하면 x는 parameter의 x로 가정된다. 이는 아래 예제에서 확인이 가능하다.

struct Point {
    var x = 0.0, y = 0.0
    func isToTheRightOf(x: Double) -> Bool {
        return 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")
} else {
    print("Return false")
}
// Prints "Return false"

Modifying Value Types from Within Instance Methods (인스턴스 메소드 내에서 값 타입 변경)

구조체와 열거형은 value type이다. 기본적으로 value type의 프로퍼티는 instance method 내에서 수정될 수 없다.

NOTE
value type은 값을 변경할 때 값을 복사하여 새로운 인스턴스를 생성한다. 이때 Swift는 COW(copy on write) 방식을 채택하고 있는데 이는 value type을 수정하면 즉시 복사하는 것이 아닌 복사된 값이 사용될 때 복사를 하는 방식이다. 만약 value type의 프로퍼티가 인스턴스 메소드 내에서 수정이 된다면 Swift의 컴파일러는 수정된 값을 가지는 인스턴스를 언제 복사해야 할지 결정을 할 수가 없다. 그러므로 mutating 키워드를 사용하여 컴파일러에게 프로퍼티의 상태(값)을 바꾼다고 명시적으로 알려줘야 한다.

그러나 구조체나 열거형의 특정 메소드 내에서 프로퍼티를 수정이 필요하다면 메소드에 mutating키워드를 붙이면 수정이 가능하다. mutating 키워드가 붙은 메소드는 메소드내에서 변경이 가능하고 이 변경사항들은 메소드가 종료될 때 원래의 구조체에 덮여 쓰여진다. 또한 mutating 메소드는 암시적 프로퍼티인 self에 새로운 인스턴스를 할당하고 이 새로운 인스턴스는 메소드가 종료될 때 기존의 것을 대체할 수 있다.

다음은 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)"

moveBy(x:y:) 메소드는 새로운 Point를 반환하는 것이 아니라 기존의 점을 수정한다.

NOTE
위 예제는 variable(변수)로 선언되어 프로퍼티의 값을 수정할 수 있다. 그러나 구조체 타입의 상수에서는 mutating method를 호출할 수 없다. 이유는 다음 링크에서 확인이 가능하다.Stored Properties of Constant Structure Instances


Assigning to self Within a Mutating Method (Mutating 메소드 내에서 self 할당)

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 method는 같은 열거형에서 각기 다른 case로 self parameter를 설정할 수 있다. 다음 예제에서 확인해 보자.

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 (타입 메소드)

Instance method는 특정 타입의 인스턴스에서 호출되고 type method는 타입 자체에서 호출하여 사용한다. Type method는 2가지 종류가 있다. 사용 방법은 메소드 선언 앞에 static 혹은 class키워드를 추가하면 된다. static메소드는 서브 클래스에서 override 할 수 없고 class메소드는 override 가능하다.

NOTE
Objective-C에서는 클래스 타입에서만 타입 메소드를 선언할 수 있던 것에 반해 Swift에서는 클래스, 구조체, 열거형에서 모두 타입 메소드를 사용할 수 있다.

인스턴스 메소드처럼 type method는 점(.) 구문으로 호출이 가능하다. 그러나 인스턴스의 타입이 아니라 타입 자체에서 호출된다. 다음 예제는 type method를 호출하는 예제다.

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

Type methods 내에서 self프로퍼티는 인스턴스의 타입이 아니라 타입 자체를 참조한다. 즉 type property 내에서도 self사용이 가능하다. 또한 type method 안에서 다른 type method를 호출하는 것이 가능하다. 아래 예제를 통해 확인해 보자.

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 구조체는 모든 플레이어가 잠금 해제한 최고 레벨을 추적한다. 이는 type property인 highestUnlockedLevel에 저장되어 있다.

또한 Leveltracker는 2개의 type function을 정의한다. isUnlocked(_:) type function은 특정 레벨이 이미 잠금 해제되었으면 true를 반환한다. (여기서 주의할 것은 이 type methods들은 점(.) 구문 없이 type property(highestUnlockedLevel)를 접근하고 있는 것을 확인할 수 있다.

Instance method인 advance(to:)currentLevel을 업데이트하기 전에 새로운 레벨이 잠금 해제 되었는지 확인한다. 이 메소드는 Bool 값을 이용하여 새로운 값을 할당할지 결정한다. advance(to:) 앞에 붙은 @discardableResult 키워드는 리턴 값이 있는 메소드에서 리턴 값을 사용하지 않으면 컴파일러 경고가 발생하는데, 그 경고를 발생하지 않도록 한다.

Player 클래스는 LevelTracker 구조체와 함께 사용되어 플레이어 개인들의 진행상황을 추적한다.

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 class에서 complete(level:) 메소드는 특정 레벨을 완료할 때마다 호출된다. 이 메소드는 다음 레벨을 잠금 해제하여 다음 레벨로 사용자를 이동시킨다.

다음, 새 플레이어를 위한 인스턴스를 만들고 플레이어가 레벨 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"

레벨 1에서 레벨 2로 넘어간 것을 확인할 수 있다.

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"

0개의 댓글