Skip to content

메서드 (Methods) #4

Description

@jryoun1

메서드 (Methods)

특정 타입과 연관된 함수이다. 즉, 메서드는 함수의 일종인데 특정 타입과 관련이 되어있는 것을 의미한다.

클래스, 구조체, 열거형은 해당 타입의 인스턴스 동작을 위한 특정 작업과 기능을 캡슐화하는 인스턴스 메서드 정의 가능하다.

클래스, 구조체, 열거형은 타입 자체와 연관된 타입 메서드를 정의할 수도 있다.

Swift에서 구조체와 열거형에서도 메서드를 정의할 수 있는 것이 C, Objective-C와의 가장 큰 차이점이다.

Objective-C는 클래스만 메서드 정의가 가능하다.


인스턴스 메서드 (Instance Methods)

특정 클래스, 구조체, 열거형의 인스턴스에 속하는 함수이다.

인스턴스 메서드

  • 속하는 타입에 중괄호 내에 작성
  • 다른 인스턴스 메서드나 타입의 프로퍼티에 암시적으로 접근 가능
  • 자신이 속한 타입의 특정 인스턴스에서만 호출될 수 있음
class Counter {
    var count = 0
    func increment() {  // 👈🏻 카운터를 1씩 증가시키는 인스턴스 메서드 
        count += 1
    }
    func increment(by amount: Int) { // 👈🏻 카운터를 매개변수(정수)만큼 증가시키는 인스턴스 메서드 
        count += amount
    }
    func reset() { // 👈🏻 카운터를 0으로 재설정하는 인스턴스 메서드 
        count = 0
    }
}

let counter = Counter() // 초기 카운터의 값은 0
counter.increment() // count = 1
counter.increment(by: 5) // count = 6
counter.reset() // count = 0

💡 함수의 파라미터는 함수 인자 라벨과 파라미터 명에서 설명했듯이 인자라벨: 함수의 바디내에서 사용, 파라미터: 함수를 호출할 때 사용하는 라벨을 둘 다 가질 수 있다.

메서드는 타입과 관련된 함수이므로 메서드의 파라미터도 동일하게 적용된다.


self 프로퍼티 (The self Property)

타입의 모든 인스턴스는 인스턴스 자체와 일치하는 self 라는 암시적 프로퍼티를 가지고 있다.

자체 인스턴스 메서드 내에서 현재 인스턴스를 참조하기 위해서는 self 프로퍼티를 사용한다.

func increment() {
  self.count += 1 // 👈🏻 현재 인스턴스를 참조하기 위해서 self 사용 (필수는 아님)
}

물론 self 는 필수가 아니다. 다만 예외사항이 존재한다.

인스턴스 메서드에 파라미터 명이 해당 인스턴스에 프로퍼티 명과 동일한 경우, 파라미터 명이 더 우선시 되기 때문에 프로퍼티를 참조하려면 self 를 작성해야한다.

즉, 파라미터 명과 프로퍼티 명을 분리하기 위해 self 를 사용할 수 있다.

struct Point {
  var x = 0.0, y = 0.0
  func isToTheRightOf(x: Double) -> Bool {
    return self.x > x // 👈🏻 이 경우 self를 사용하지 않으면 Swift는 2개의 x가 모두 메서드의 파라미터를 참조한다고 가정하게된다
  }
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isTotheRightOf(x: 1.0) {
  print("somePoint의 x 좌표는 1보다 오른쪽에 위치합니다.")
}

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

구조체와 열거형은 값 타입이고, 기본적으로 값 타입의 프로퍼티는 인스턴스 메서드 내에서 수정될 수 없다.

그러나 필요하다면 변경하도록 할 수 있고, 해당 메서드 내에서 프로퍼티를 변경한 이후 메서드가 끝나면 기존 구조체에 변경사항이 적용되게 된다.

메서드는 암시적 self 프로퍼티에 새로운 인스턴스를 할당하고, 새로운 인스턴스는 메서드가 종료되면 기존에 존재하던 인스턴스를 대체하게 된다.

struct Point {
  var x = 0.0, y = 0.0
  mutating func moveBy(x deltaX: Double, y deltaY: Double) { // 👈🏻 mutating 이 붙어서 프로퍼티 변경 가능 
    x += deltaX // 👈🏻 암시적 self 프로퍼티 x에 새로운 인스턴스 할당
    y += deltaY // 👈🏻 암시적 self 프로퍼티 y에 새로운 인스턴스 할당
  }
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0) 
print("새로운 포인트는 \(somePoint.x), \(somePoint.y)") // 새로운 포인트는 2.0, 3.0  

💡 이때 상수 구조체 인스턴스의 저장된 프로퍼티에서 말했듯이 위에서 Point 인스턴스의 x, y 프로퍼티가 변수여도 somePoint 가 상수 구조체 인스턴스라면 변경할 수 없기 때문에 상수 구조체 타입에서는 변경 메서드 호출이 불가능하다.

// 🚨 에러 발생
let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)

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) // 👈🏻 위의 예시를 아예 새로운 구조체 인스턴스를 할당하는 방향으로 수정 가능 (기존의 코드와 동일한 기능을 수행함)
  }
}

열거형의 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는 이제 .high와 같음
ovenLight.next() // ovenLight는 이제 .low와 같음

타입 메서드 (Type Methods)

인스턴스 메서드특정 타입의 인스턴스에서 호출하는 메서드이다.

타입 메서드타입 자체에서 호출되는 메서드이다.

  • static 키워드를 작성
  • 클래스의 경우 class 키워드를 사용해 하위 클래스가 해당 메서드의 수퍼 클래스 구현을 재정의 할 수 있다
  • 인스턴스 메서드 처럼 . 점 구문으로 호출되나, 해당 인스턴스가 아닌 타입으로 타입메서드를 호출

💡 Objective-C에서는 클래스만 타입 레벨 메서드를 정의할 수 있다.

반면 Swift에서는 모든 클래스, 구조체, 열거형에서 타입 레벨 메서드를 정의할 수 있고, 각 타입 메서드는 지원하는 타입으로 명시적 범위가 지정된다.

class SomeClasss {
  class func someTypeMethod() { 
  }
}
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 { // 👈🏻 특정 레벨이 이미 풀려있다면 true 반환
    return level <= highestUnlockedLevel // 👈🏻 여기서 LevelTracker.highestUnlockedLevel가 아니라 highestUnlockedLevel로 타입 프로퍼티에 접근 가능
  }
  
  @discardableResult // 👈🏻 이 메서드의 반환 값을 무시하는 코드가 실수가 아니므로 @discardableResult 속성 표시
  mutating func advance(to level: Int) -> Bool { // 👈🏻 currentLevel 관리를 하기 위한 인스턴스 메서드
    if LevelTracker.isUnlocked(level) { 
      currentLevel = level
      return true
    } else {
      return false
    }
  }
}

첫 번째 레벨을 제외한 게임의 모든 레벨은 처음 플레이 할 때 잠겨있게 된다.

플레이어가 레벨을 끝낼 때 마다 디바이스에 모든 플레이어가 플레이 할 수 있도록 레벨이 풀리게 된다.

@discardableResult에 대해서는 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
  }
}

var player = Player(name: "Jake")
player.complete(level: 1)
print("아직 풀리지 않은 가장 높은 레벨은 현재 \(LevelTracker().highestUnlockedLevel)") 
// 아직 풀리지 않은 가장 높은 레벨은 현재 2

player = Player(name: "HotDog") 
if player.tracker.advanced(to: 6) { // 👈🏻 다른 플레이어를 아직 풀리지 않은 레벨로 이동하려고 하면 실패하게 된다.
  print("플레이어는 현재 6 레벨에 있습니다.")
} else {
  print("레벨 6은 아직 풀리지 않았습니다.")
}
// 레벨 6은 아직 풀리지 않았습니다.

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions