Swift 뿌수기 - Methods

Wonbi·2022년 8월 23일
2

Swift 뿌수기

목록 보기
1/12

✅학습 내용

💎 Methods

Methods are functions that are associated with a particular type. Classes, structures, and enumerations can all define instance methods, which encapsulate specific tasks and functionality for working with an instance of a given type. Classes, structures, and enumerations can also define type methods, which are associated with the type itself. Type methods are similar to class methods in Objective-C.

  • 메소드는 특정 타입과 연관된 함수입니다. 클래스, 구조체, 열거형 모두 인스턴스 메소드를 정의해 주어진 타입의 인스턴스로 작업하기 위한 특정 임무와 기능을 캡슐화 할 수 있습니다. 클래스, 구조체, 열거형은 타입 그 자체와 결합한 타입 메소드도 정의할 수 있습니다. 타입 메소드는 Objective-C의 클래스 메소드와 비슷합니다.

  • 스위프트는 클래스, 구조체, 열거형 모두 메소드를 정의할 수 있다. Objective-C에선 클래스만 가능.

💎 인스턴스 메소드

  • 인스턴스 메소드는 특정 클래스나 구조체, 열거형 인스턴스에 속하는 함수이다. 이 함수는 인스턴스 속성에 접근, 수정 방법을 제공하거나, 인스턴스의 목적과 관련된 기능을 제공함으로써 그 인스턴스의 기능을 지원한다. 인스턴스 메소드 구문은 함수와 정확하게 일치한다.
  • 인스턴스 메소드는 자신이 속한 타입의 여는 중괄호와 닫는 중괄호 안에 작성된다. 또한 그 타입의 모든 다른 인스턴스 메소드와 속성에 암시적으로 접근한다.

    암시적으로 접근한다는 건 다른 인스턴스 메소드와 속성에 접근할 때 self. 를 붙이지 않아도 된다는 의미입니다.

  • 인스턴스 메소드는 자신이 속한 타입으로 지정된 인스턴스에서만 호출이 가능하다. 존재하는 인스턴스 없이 호출될 수 없다.
class Counter {
  var count = 0
  
  func increment() {
    count += 1
  }
  
  func increment(by amount: Int) {
    count += amount
  }
  
  func reset() {
    count = 0
  }
}
  • Counter클래스는 3가지 메소드를 정의한다.
    1. increment() 는 횟수를 1 만큼 증가
    2. increment(by : Int) 는 횟수를 지정한 정수만큼 증가
    3. reset() 은 횟수를 0 으로 재설정
  • 현재 횟수를 추적하는 count 프로퍼티도 선언하였다.
  • 프로퍼티와 동일하게 점 구문으로 인스턴스 메소드를 호출할 수 있다.
let counter = Counter()
// 초기 횟수는 0 임
counter.increment()
// 이제 횟수는 1 임
counter.increment(by: 5)
// 이제 횟수는 6 임
counter.reset()
// 이제 횟수는 0 임
  • 함수의 매개변수는 이름과 아규먼트 레이블을 모두 가질 수 있다. 메소드 역시 마찬가지로 이 두가지를 모두 가질 수 있다. 메소드는 그냥 타입과 결합한 함수일 뿐이기 때문.

✏️ self 속성

  • 타입의 모든 인스턴스는 인스턴스 자체를 의미하는 self라는 암시적인 속성을 가진다. self속성을 사용해 자신의 인스턴스 메소드 내에서 현재 인스턴스를 참조할 수 있다.

  • 위 예제의 increment() 메소드는 다음 같이 작성할 수도 있다.

func increment() {
    self.count += 1
}
  • 실제로 코드에서 자주 사용되지는 않는다. Swift는 메소드 안에서 이미 알고 있는 프로퍼티나 메소드를 사용할 때마다 현재 인스턴스의 프로퍼티 또는 메소드를 참조하는 것으로 가정하기 때문이다. 이는 위 예제로 이미 증명되어 있다.
  • 예외적으로, 인스턴스 메소드의 매개변수 이름과 프로퍼티의 이름이 똑같을 때는 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("이 점은 x == 1.0인 선의 오른쪽에 있습니다.")
}
// "이 점은 x == 1.0인 선의 오른쪽에 있습니다."를 인쇄함
  • 이 예제의 isToTheRightOf(x: Double)메소드 내부에서, self접두사가 없다면 Swift는 두 x모두 메소드의 매개변수를 참조한다고 가정할 것이다.

✏️ 인스턴스 메소드 안에서 값 타입 수정하기

  • 구조체와 열거형은 값타입 (value types) 이다. 기본적으로 값 타입의 프로퍼티는 인스턴스 메소드 내에서 수정할 수 없다.

  • 하지만, 프로퍼티의 값을 수정할 필요가 있다면, 함수 선언 앞에 mutating키워드를 넣어줌으로써 프로퍼티의 값을 수정할 수 있다.

  • 접두사에 mutating키워드가 붙은 메소드는 메소드 안에서 자신의 프로퍼티를 바꿀 수 있으며, 어떤 방식으로 바꾸던 메소드가 끝날 때 원본 구조체 안에 다시 작성한다.

  • 또 메소드는 자신의 암시적 self속성에 완전히 새로운 인스턴스를 할당할 수도 있으며, 이 새 인스턴스는 메소드가 끝날 때 기존 것 대신 자리잡는다.

struct Point {
  var x = 0.0, y = 0.0
  mutating func moveBy(x deltaX: Double, deltaY: Double) {
    x += deltaX
    y += deltaY
  }
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("이제 점이 (\(somePoint.x), \(somePoint.y))에 있습니다.")
// "이제 점이 (3.0, 4.0)에 있습니다." 를 인쇄함
  • Point구조체는 정해진 양 만큼 이동하는 moveBy(x:y:) 변경 메소드를 정의한다. 이 메소드는 새 점 (point) 을 반환하지 않고, 실제로 호출하는 점으로 이동한다.
  • 당연한 말이지만, 구조체는 값 타입이므로 상수로 할당된 구조체 타입 인스턴스는 변경 메소드를 호출할 수 없다.
let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)
// 이는 에러를 보고할 것임

✏️ 변경 메소드 안에서 self에 할당하기

  • 앞서 말했듯 변경 메소드는 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)
  }
}
  • 이 예제의 moveBy(x:y:)변경 메소드는 xy 값을 목표 위치로 설정한 새로운 구조체를 생성하여 자신의 인스턴스에 대입한다. 이 예제의 메소드 호출 결과는 앞선 버전의 호출과 정확히 일치할 것이다.

  • 열거형의 변경 메소드는 암시적 self 매개 변수에 동일 열거형의 다른 case를 설정할 수 있다.

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 는 이제 .off 임

💎 타입 메소드

  • 인스턴스 메소드는 앞서 설명했듯이 특정 타입의 인스턴스에서 호출하는 메소드이다. 하지만, 타입 그 자체에서 호출하는 메소드도 정의할 수 있다. 이런 종류의 메소드를 타입 메소드 (type methods) 라고 한다.
  • 타입 메소드는 접두사 static키워드를 작성해서 지정할 수 있다. 클래스의 경우 class키워드를 사용하여 그 메소드의 슈퍼 클래스 구현을 하위 클래스가 재정의하도록 허용할 수 있다.
  • 타입 메소드는 인스턴스 메소드와 동일하게 점 구문으로 호출할 수 있지만, 그 타입의 인스턴스에서가 아닌, 타입 에서 호출한다.
class SomeClass {
  class func someTypeMethod() {
    // 타입 메소드 구현은 여기에 둠
  }
}
SomeClass.someTypeMethod()
  • 타입 메소드 안에서 암시적 self 속성은 그타입의 인스턴스가 아닌 타입 그 자체를 참조한다. 이는 인스턴스 프로퍼티와 인스턴스 메소드의 매개변수에서 처럼, 타입 인스턴스와 타입 메소드의 매개변수를 헷갈리지 않게 하는데 self를 사용할 수 있다는 의미이다.

  • 더 일반적으로, 타입 메소드는 타입 이름을 접두사로 붙이지 않은 메소드 이름을 가지고 또 다른 타입 메소드를 호출할 수 있다. 이와 비슷하게, 구조체와 열거형에 대한 타입 메소드 역시 타입 이름 접두사를 붙이지 않고 타입 프로퍼티 이름으로 타입 프로퍼티에 접근이 가능하다.

  • 아래의 예제는 게임의 레벨 또는 단계별 진행 사항을 추적하는 LevelTracker라는 구조체를 정의한다. 이 게임은 1인용 게임이지만 여러 플레이어의 정보를 단일 장치에 저장할 수 있다.

  • 게임을 처음 플레이할 때 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라는 타입 프로퍼티에 저장된다.

  • LevelTrackerhighestUnlockedLevel프로퍼티와 작업하기 위한 두가지의 타입 메소드를 정의한다.

    1. 새 레벨을 완료할 때마다 highestUnlockedLevel 프로퍼티 값을 갱신할 unlock(_:)타입 메소드
    2. 특정 레벨을 이미 완료한 상태라면 true를 반환하는 isUnlocked(_:)라는 편의를 위한 타입 메소드
  • 위 두개의 타입 메소드에서 highestUnlockedLevel타입 프로퍼티에 접근하기 위해 LevelTracker.highestUnlockedLevel와 같이 타입 접두사를 작성하지 않는 것을 기억하자.

  • 자신의 타입 속성과 타입 메소드에 더해, LevelTracker는 개별 플레이어의 게임 진행 상황도 추적한다. 이는 currentLevel라는 인스턴스 프로퍼티로 플레이어의 게임 진행 상황을 추적한다.

  • currentLevel프로퍼티의 관리를 돕기위해, LevelTrackeradvance(to:)라는 인스턴스 메소드를 정의한다. advance(to:)메소드는 currentLevel을 업데이트하기 전에 새로 요청한 레벨이 이미 완료되었는지 검사한다. advance(to:)메소드는 currentLevel값을 설정하는 것이 실제로 가능한지 지시하는 불리언 값을 반환한다.

  • advance(to:)메소드를 호출하는 코드가 반환 값을 무시하는 것은 항상 실수가 되는 것이 아니므로, 이 함수는 @discardableResult특성으로 표시된다.

  • @discardableResult특성으로 표시되면 메소드가 반환값을 가지고 있고, 그 반환값을 사용하지 않더라도 컴파일러가 경고를 주지 않는다.

class Player {
  var tracker = LevelTracker()
  let playerName: String
  
  init(name: String) {
    playerName = name
  }
  
  func complete(level: Int) {
    LevelTracker.unlock(level + 1)
    tracker.advance(to: level + 1)
  }
}
  • Player클래스는 플레이어의 진행상황을 추적하기 위해 LevelTracker 인스턴스를 생성한다. 또 플레이어가 특정 레벨을 완료했을 때마다 호출되는 complete(level:)메소드를 제공한다. 이 메소드는 모든 플레이어에 대한 다음 레벨을 언락하고 플레이어의 진행상황을 갱신하여 그 다음 레벨로 이동한다.
  • advance(to:)의 불리언 반환 값은 무시한다. 이는 이전 줄에서 LevelTracker.unlock(_:)을 호출함으로써 레벨이 완료된 것을 이미 알고 있기 때문이다.
  • 새로운 플레이어를 위한 Player클래스 인스턴스를 생성하고, 플레이어가 레벨을 완료하였을 때 무슨일이 일어나는지 보면:
var player = Player(name: "Wonbi")
player.complete(level: 1)
print("언락된 가장 높은 레벨은 \(LevelTracker.highestUnlockedLevel)입니다.")
// Prints "언락된 가장 높은 레벨은 2입니다."
  • 만약 두번째 플레이어가 게임에서 어떤 플레이어도 언락하지 못한 레벨로 이동하려 하면, 플레이어의 현재 레벨을 설정하려는 시도가 실패하게 된다.
player = Player(name: "Beto")
if player.tracker.advance(to: 6) {
    print("현재 플레이어의 레벨은 6입니다.")
} else {
    print("레벨 6은 아직 언락되지 않았습니다.")
}
// Prints "레벨 6은 아직 언락되지 않았습니다."

0개의 댓글