Swift

[SWIFT] 클래스와 구조체

동호다찌 2022. 10. 31. 09:51
반응형

전수열님의 GitBook을 기반으로 정리합니다.


클래스Class는 class로 정의하고, 구조체Structure는 struct로 정의합니다.

class Dog {
  var name: String?
  var age: Int?

  func simpleDescription() -> String {
    if let name = self.name {
      return "🐶 \(name)"
    } else {
      return "🐶 No name"
    }
  }
}

struct Coffee {
  var name: String?
  var size: String?

  func simpleDescription() -> String {
    if let name = self.name {
      return "☕️ \(name)"
    } else {
      return "☕️ No name"
    }
  }
}

var myDog = Dog()
myDog.name = "찡코"
myDog.age = 3
print(myDog.simpleDescription()) // 🐶 찡코

var myCoffee = Coffee()
myCoffee.name = "아메리카노"
myCoffee.size = "Venti"
print(myCoffee.simpleDescription()) // ☕️ 아메리카노

 

클래스는 상속이 가능합니다. 구조체는 불가능합니다.

class Animal {
  let numberOfLegs = 4
}

class Dog: Animal {
  var name: String?
  var age: Int?
}

var myDog = Dog()
print(myDog.numberOfLegs) // Animal 클래스로부터 상속받은 값 (4)

클래스는 참조Reference하고, 구조체는 복사Copy합니다.

var dog1 = Dog()  // dog1은 새로 만들어진 Dog()를 참조합니다.
var dog2 = dog1   // dog2는 dog1이 참조하는 Dog()를 똑같이 참조합니다.
dog1.name = "찡코" // dog1의 이름을 바꾸면 Dog()의 이름이 바뀌기 때문에,
print(dog2.name)  // dog2의 이름을 가져와도 바뀐 이름("찡코")이 출력됩니다.

var coffee1 = Coffee()   // coffee1은 새로 만들어진 Coffee() 그 자체입니다.
var coffee2 = coffee1    // coffee2는 coffee1을 복사한 값 자체입니다.
coffee1.name = "아메리카노" // coffee1의 이름을 바꿔도
coffee2.name             // coffee2는 완전히 별개이기 때문에 이름이 바뀌지 않습니다. (nil)

생성자 (Initializer)

클래스와 구조체 모두 생성자를 가지고 있습니다. 생성자에서는 속성의 초깃값을 지정할 수 있습니다.

class Dog {
  var name: String?
  var age: Int?

  init() {
    self.age = 0
  }
}

struct Coffee {
  var name: String?
  var size: String?

  init() {
    self.size = "Tall"
  }
}

만약 속성이 옵셔널이 아니라면 항상 초깃값을 가져야 합니다. 만약 옵셔널이 아닌 속성이 초깃값을 가지고 있지 않으면 컴파일 에러가 발생합니다.

class Dog {
  var name: String?
  var age: Int // 컴파일 에러!
}

stored property 'age' without initial value prevents synthesized initializers

속성을 정의할 때 초깃값을 지정해 주는 방법과,

class Dog {
  var name: String?
  var age: Int = 0 // 속성을 정의할 때 초깃값 지정
}

생성자에서 초깃값을 지정해주는 방법이 있습니다.

class Dog {
  var name: String?
  var age: Int

  init() {
    self.age = 0 // 생성자에서 초깃값 지정
  }
}

생성자도 함수와 마찬가지로 파라미터를 받을 수 있습니다.

class Dog {
  var name: String?
  var age: Int

  init(name: String?, age: Int) {
    self.name = name
    self.age = age
  }
}

var myDog = Dog(name: "찡코", age: 3)

만약 상속받은 클래스라면 생성자에서 상위 클래스의 생성자를 호출해주어야 합니다. 만약 생성자의 파라미터가 상위 클래스의 파라미터와 같다면, override 키워드를 붙여주어야 합니다. super.init()은 클래스 속성들의 초깃값이 모두 설정 된 후에 해야 합니다. 그리고 나서부터 자기 자신에 대한 self 키워드를 사용할 수 있습니다.

class Dog: Animal {
  var name: String?
  var age: Int

  override init() {
    self.age = 0 // 초깃값 설정
    super.init() // 상위 클래스 생성자 호출
    print(self.simpleDescription()) // 여기서부터 `self` 접근 가능
  }

  func simpleDescription() -> String {
    if let name = self.name {
      return "🐶 \(name)"
    } else {
      return "🐶 No name"
    }
  }
}

만약, 위 예시 코드를 아래처럼 바꿔서 super.init()을 하기 전에 self에 접근한다면 컴파일 에러가 발생합니다.

override init() {
  self.age = 0
  print(self.simpleDescription()) // 컴파일 에러!
  super.init()
}

error: use of 'self' in method call 'simpleDescription' before super.init initializes self

deinit은 메모리에서 해제된 직후에 호출됩니다.

class Dog {
  // ...

  deinit {
    print("메모리에서 해제됨")
  }
}

속성 (Properties)

속성은 크게 두 가지로 나뉩니다. 값을 가지는 속성Stored Property과 계산되는 속성Computed Property인데요. 한글말로 쓰니까 굉장히 어색하네요. 쉽게 말하면 속성이 값 자체를 가지고 있는지, 혹은 어떠한 연산을 수행한 뒤 그 결과를 반환하는지의 차이입니다.

우리가 지금까지 정의하고 사용한 name, age와 같은 속성들은 모두 Stored Property입니다. Computed Property는 get, set을 사용해서 정의할 수 있습니다. set에서는 새로 설정될 값을 newValue라는 예약어를 통해 접근할 수 있습니다.

struct Hex {
  var decimal: Int?
  var hexString: String? {
    get {
      if let decimal = self.decimal {
        return String(decimal, radix: 16)
      } else {
        return nil
      }
    }
    set {
      if let newValue = newValue {
        self.decimal = Int(newValue, radix: 16)
      } else {
        self.decimal = nil
      }
    }
  }
}

var hex = Hex()
hex.decimal = 10
hex.hexString // "a"

hex.hexString = "b"
hex.decimal // 11

위 코드에서 hexString은 실제 값을 가지고 있지는 않지만, decimal로부터 값을 받아와 16진수 문자열로 만들어서 반환합니다. decimal은 Stored Property, hexString은 Computed Property입니다.

참고로, get만 정의할 경우에는 get 키워드를 생략할 수 있습니다. 이런 속성을 읽기 전용Read Only이라고 합니다.

class Hex {
  // ...

  var hexCode: String? {
    if let hex = self.hexString {
      return "0x" + hex
    }
    return nil
  }
}

get, set과 비슷한 willSet, didSet을 이용하면 속성에 값이 지정되기 직전과 직후에 원하는 코드를 실행할 수 있습니다.

struct Hex {
  var decimal: Int? {
    willSet {
      print("\(self.decimal)에서 \(newValue)로 값이 바뀔 예정입니다.")
    }
    didSet {
      print("\(oldValue)에서 \(self.decimal)로 값이 바뀌었습니다.")
    }
  }
}

마찬가지로, willSet에서는 새로운 값을 newValue로 얻어올 수 있고, didSet에서는 예전 값을 oldValue라는 예약어를 통해 얻어올 수 있습니다.

willSet과 didSet은 일반적으로 어떤 속성의 값이 바뀌었을 때 UI를 업데이트하거나 특정 메서드를 호출하는 등의 역할을 할 때에 사용됩니다.

반응형

'Swift' 카테고리의 다른 글

[SWIFT] Enum  (0) 2022.11.02
[SWIFT] 튜플(Tuple)  (0) 2022.11.02
[SWIFT] 함수와 클로저  (0) 2022.10.28
[SWIFT] 옵셔널 (Optional)  (0) 2022.10.27
[SWIFT]조건문과 반복문  (0) 2022.10.27