티스토리 뷰

iOS

[Swift] 메소드 Methods

Peppo 2022. 3. 23. 07:39
728x90

들어가기전

 

함수와 메소드의 차이!

  • 메소드 - 클래스, 구조체, 열거형 안에 있는 함수 !!
  • 함수 - 클래스, 구조체, 열거형 외부에 있는 함수 !

 

특정 타입의 클래스, 구조체, 열거형과 관련된 함수를 메소드(Methods)라고 합니다. 

메소드에는 두가지 종류가 있습니다.

  • 인스턴스 메소드: 특정 작업과 기능을 캡슐화 하여 실행할 수 있는 메소드
  • 타입 메소드: 특정 타입과 관련된 메소드

타입 메소드는 Objective-C의 클래스 메소드와 유사합니다.

 

Swift에서 메소드와 Objective-C 메소드간의 차이점은 Objective-C에서는 클래스 타입에서만 메소드를 선언할 수 있고, Swift에서는 클래스, 구조체, 열거형에서도 메소드를 선언하여 사용할 수 있다는 점입니다.

 


인스턴스 메소드 (Instance Methods)

 

인스턴스 메소드는 특정한 클래스, 구조체, 열거형의 인스턴스에 속한 메소드 입니다.

인스턴스 프로퍼티에 접근하고, 수정하는 방법을 제공하거나, 인스턴스의 관련된 기능을 제공합니다.

인스턴스 메소드는 해당 인스턴스가 속한 특정타입의 인스턴스에서만 실행가능 합니다.

 

class Counter {
    var count = 0
    
    func increment() {                 // count 를 1 올려주는 메소드
        count += 1
    }
    
    func increment(by amount: Int) {   // 파라미터로 int 값을 받은 값에 count를 더해주는 메소드
        count += amount
    }
    func reset() {                     // count를 0으로 초기화 시켜주는 메소드
        count = 0
    }
}

 

클래스 Counter를 선언 하고 이 안에 세개의 인스턴스 메소드로

increment(),

increment(by amount: Int),

reset()이 있습니다.

 

아래 예제와 같이 프로퍼티와 같은 .(dot 구문)을 이용해 인스턴스 메소드를 호출할 수 있습니다 

let counter = Counter()  // count = 0

counter.increment()      // count = 1
counter.increment(by: 5) // count = 6
counter.reset()          // count = 0

 

self 프로퍼티 (self Property)

 

모든 프로퍼티 암시적으로 인스턴스 자체를 의미하는 self라는 프로퍼티를 갖습니다.

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

class Counter {
    var count = 0
    
    func increment() {
        self.count += 1  // self 추가
    }
}

이전의 예제에서 count += 1 이라고 표현 했지만, 사실 self.count += 1 의 의미였습니다. 

Swift에서는 특정 메소드에서 해당 인스턴스에 등록된 메소드나 프로퍼티를 호출하면 현재 인스턴스의 메소드나 프로퍼티를 사용하는것으로 간주합니다. 

 

다만, 아래 예제처럼 인자 이름프로퍼티 이름같을 경우엔 다르게 적용이 됩니다.

struct Point {
    var x = 0.0, y = 0.0
    func isToTheRightOf(x: Double) -> Bool {
        print("self.x:", self.x)         // self.x: 4.0
        print("x:", x)                   // x: 1.0
        return self.x > x                // self.x를 이용해 프로퍼티 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")
}
// This point is to the right of the line where x == 1.0

여기서 self.x프로퍼티 x를 나타내며, 

 x  는 isToTheRightOf(x: Double) 의 인자 x를 나타냅니다.

만약 인자 이름프로퍼티의 이름같을때 self 키워드를 사용하지 않으면 Swift는 자동으로 인자 이름으로 가정합니다.

 

 

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

 

구조체와 열거형은 값 타입이므로, 인스턴스 메소드 내에서 프로퍼티를 변경할 수 없지만,

mutating 이라는 키워드를 사용하면 메소드 내부의 프로퍼티를 변경할 수 있습니다.

 

아래 예제를 보고 응용을 해봅시다.

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        print("x:", x)              // x: 1.0
        print("deltaX:", deltaX)    // deltaX: 2.0
        print("y:", y)              // y: 1.0
        print("deltaY:", deltaY)    // deltaY: 3.0

        x += deltaX
        y += deltaY

        print("x:", x)              // x: 3.0
        print("y:", y)              // y: 4.0
    }
}

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))")
// The point is now at (3.0, 4.0)

 

언급했던것 처럼 위의 Point 는 구조체

즉, 값 타입 이므로 mutating func moveBy(x:y:) 키워드를 사용해서 메소드 내부의 프로퍼티를 변경해줄 수 있습니다.

 

코드풀이 

더보기

 

  • somePoint 변수는 Point(x: 1.0, y: 1.0) 으로 각 x, y 에 초기값을 세팅 해줘서 
    Point 내부의 프로퍼티는 var x = 1.0, y = 1.0 로 변경이 됩니다.

  • somePoint.moveBy(x: 2.0, y: 3.0) 으로 moveBy(x:y:)의 인자에 값을 넣어줘서
    deltaX의 값은 2.0, deltaY의 값은 3.0이 됩니다 

  • y와 deltaY값도 위와 같이 진행 됩니다.

  • x += deltaX , y += deltaY 는 이런 꼴이 되겠죠 
    x = 1.0 + 2.0 , y = 1.0 + 3,0 

  • mutating으로 변경된 프로퍼티의 값은 (3.0, 4.0)이 됩니다.

 

NOTE

mutating으로 프로퍼티 값이 변경 가능하다 하더라도
인스턴스화 한 somePoint가 상수(let)로 선언될 경우엔 값을 변경할 수 없습니다.

 

 

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

 

mutating 키워드가 있는 메소드에서 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)
        print("self:", self)      // self: Point(x: 3.0, y: 4.0)
    }
}

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))")
// The point is now at (3.0, 4.0)

 

 

 

훨씬더 간결해진걸 볼 수 있죠? 

 

아래는 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()  // high
ovenLight.next()  // off
ovenLight.next()  // low
ovenLight.next()  // high

 

ovenLight 인스턴스에서 next() 메소드를 호출해 상태를 변경할 수 있습니다.

 

 


 

타입 메소드 (Type Methods)

 

타입 메소드는 특정 타입 자체에서 호출하여 사용합니다. 

선언 하는 방법은 func 앞에 static 또는 class 키워드를 추가해주면 됩니다.

 

Static, Class 키워드의 차이

Override를 할 수 있는지의 차이가 있습니다. 
static의 경우엔 하위클래스에서 override 할 수 없는 타입이고,
class의 경우에는 override를 할 수 있습니다.

 

타입메소드도 인스턴스 메소드와 같이 점(.) 문법으로 호출할 수 있습니다.

하지만 인스턴스에서 호출하는것이 아닌 타입 이름에서 메소드를 호출합니다.

class SomeClass {
    class func someTypeMethod() {
        // code
    }
    
    static func otherTypeMethod() {
        // code
    }
}

SomeClass.someTypeMethod()
SomeClass.otherTypeMethod()

var someTypeMethods = SomeClass() 와 같이 인스턴스를 생성하지 않고 바로 접근이 가능합니다.

 

타입메소드 안에서도 self 키워드를 사용할 수 있습니다.

하지만 타입메소드에서의 self는 인스턴스가 아닌, 타입 자신을 의미합니다.

또, 타입메소드 안에 다른 타입메소드를 사용할 수도 있습니다.

 

아래 예제를 보면서 타입 메소드를 좀더 익혀 봅시다.

 

하나의 게임을 예로 들어볼겁니다. 

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

 

두개의 타입 메소드가 눈에 띄이네요.

 

level이 지정된 최고레벨보다 높으면 최고레벨이 갱신되는 기능을 하는 unlock 타입메소드

level이 지정된 최고레벨 이하 일 경우 true를 반환해주는 isUnlocked 타입메소드

 

두 타입 메소드는 highestUnlockedLevel 타입 프로퍼티에 접근할 수 있으므로 

LevelTracker.highestUnlockedLevel로 작성할 필요가 없습니다.

 

그리고 currentLevel 프로퍼티를 관리할 수 있도록 advance(to:) 인스턴스 메소드가 정의 되어있네요.

func 앞에 static, class가 없는걸 보니 인스턴스 메소드라는걸 알 수 있습니다.

 

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

 

tracker는 LevelTracker를 인스턴스 입니다.

playName은 기본값이 없으므로 초기값(init)을 설정해주었구요.

 

complete(level:) 인스턴스 메소드 안에는

unlock이라는 메소드가 보이는데 타입 메소드라 LevelTracker.unlock 이렇게 자체적으로 접근을 한것을 볼 수 있습니다. 

tracker로 unlock에 접근이 안되는 사진도 보여드릴게요.

 

다음 advance 메소드는 인스턴스 메소드이니 LevelTracker를 인스턴스화 했던 tracker로 접근을 해야겠죠.

 

Player 클래스의 내부 구조는 한번 훑어봤으니 작동을 시켜보겠습니다.

var player = Player(name: "Peppo")
player.complete(level: 1)
print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")

 

Peppo라는 플레이어가 level 1 을 완료했네요 (complete)

 

그럼 complete(level:)의 메소드에서 level 1을 각각의 메소드에 넣어 주게됩니다.

 

LevelTracker.unlock(level(1) + 1)

먼저 unlock 메소드 내부를 보죠

if level(2) > highestUnlockedLevel(1) {
  highestUnlockedLevel(1) = level(2)
}

이렇게 highestUnlockedLevel 은 2로 바뀌게 됩니다.

 

tracker.advance(to: level(1) + 1)의 내부에는

isUnlocked 메소드의 반환 값 (Bool) 에 따라서 currentLevel이 업데이트 되네요.

if LevelTracker.isUnlocked(level(2)) {
            currentLevel(1) = level(2)
            return true
        } else {
            return false
        }

 

LevelTracker.isUnlocked 메소드의 내부를 보면 

static func isUnlocked(_ 2) -> Bool {
        return level(2) <= highestUnlockedLevel(2)
    }

highestUnlockedLevel은 2가 되었으니 level과 같거나 작으니 true를 반환 합니다.

 

advance(to:) 로 돌아가서 true 부분을 보면 되겠네요.

currentLevel = level 로 업데이트 되어

print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)"의 값은 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")
}
// "level 6 has not yet been unlocked"

 

위에서 최종레벨(highestUnlockedLevel)은 2로 되어있는데 

6이라는 level이 들어왔네요. 

 

.isUnlocked 타입메소드에서 false를 반환하게 되면서 

"level 6 has not yet been unlocked" 를 출력하게 됩니다.

 


마지막 예제를 이해하는데 시간이 꽤 걸렸네요. 

덕분에 타입메소드와 인스턴스 메소드의 접근방법이나 mutating 부분도 이해하는데 도움이 많이 된것 같습니다.

 

 

728x90