🔥 객체와 클래스

845자
11분

클래스를 생성하려면 class 키워드 다음에 클래스 이름을 사용하면 됩니다. 클래스 내에서 프로퍼티를 선언할 때는 상수나 변수를 선언하는 것과 동일한 방식으로 작성하되, 클래스의 컨텍스트 내에서 작성한다는 점이 다르지요. 마찬가지로, 메서드와 함수 선언도 동일한 방식으로 작성합니다.

class Shape {
    var numberOfSides = 0 // 변수 numberOfSides를 선언하고 초기값으로 0을 할당합니다.
    func simpleDescription() -> String { // String을 반환하는 simpleDescription() 메서드를 선언합니다.
        return "A shape with \(numberOfSides) sides." // numberOfSides 값을 포함한 문자열을 반환합니다.
    }
}
swift

클래스의 인스턴스를 생성하려면 클래스 이름 뒤에 괄호를 붙이면 됩니다. 인스턴스의 프로퍼티와 메서드에 접근할 때는 점 문법을 사용하면 되겠죠.

var shape = Shape() // Shape 클래스의 인스턴스를 생성합니다.
shape.numberOfSides = 7 // numberOfSides 프로퍼티에 7을 할당합니다.
var shapeDescription = shape.simpleDescription() // simpleDescription() 메서드를 호출하고 결과를 shapeDescription 변수에 할당합니다.
swift

위의 Shape 클래스 버전에는 중요한 부분이 빠져 있습니다. 바로 인스턴스가 생성될 때 클래스를 설정하는 이니셜라이저입니다. init을 사용하여 이니셜라이저를 만들어 볼까요?

class NamedShape {
    var numberOfSides: Int = 0 // 프로퍼티 numberOfSides를 선언하고 초기값으로 0을 할당합니다.
    var name: String // 프로퍼티 name을 선언합니다.
 
    init(name: String) { // 이니셜라이저를 선언하고 name 매개변수를 받습니다.
        self.name = name // self를 사용하여 name 프로퍼티에 매개변수 값을 할당합니다.
    }
 
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}
 
swift

self를 사용하여 name 프로퍼티와 이니셜라이저의 name 매개변수를 구분하는 방법에 주목하세요. 이니셜라이저의 매개변수는 클래스의 인스턴스를 생성할 때 함수 호출처럼 전달됩니다. 모든 프로퍼티는 값을 할당받아야 합니다. 선언 시점에 할당하거나(numberOfSides처럼) 이니셜라이저에서 할당하는 거죠(name처럼).

객체가 할당 해제되기 전에 정리 작업을 수행해야 한다면 deinit을 사용하여 디이니셜라이저를 생성하면 됩니다.

서브클래스는 클래스 이름 뒤에 콜론으로 구분하여 슈퍼클래스 이름을 포함합니다. 클래스가 표준 루트 클래스를 서브클래싱할 필요가 없으므로 필요에 따라 슈퍼클래스를 포함하거나 생략할 수 있습니다.

슈퍼클래스의 구현을 재정의하는 서브클래스의 메서드는 override로 표시합니다. 실수로 override 없이 메서드를 재정의하면 컴파일러가 오류로 감지하죠. 컴파일러는 또한 override가 있지만 실제로 슈퍼클래스의 어떤 메서드도 재정의하지 않는 경우도 감지합니다.

class Square: NamedShape {
    var sideLength: Double // 프로퍼티 sideLength를 선언합니다.
 
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength // sideLength 프로퍼티에 매개변수 값을 할당합니다.
        super.init(name: name) // 슈퍼클래스의 이니셜라이저를 호출합니다.
        numberOfSides = 4 // numberOfSides 프로퍼티에 4를 할당합니다.
    }
 
    func area() -> Double {
        return sideLength * sideLength // 정사각형의 넓이를 계산하여 반환합니다.
    }
 
    override func simpleDescription() -> String {
        return "A square with sides of length \(sideLength)." // 재정의된 메서드로 정사각형에 대한 설명을 반환합니다.
    }
}
let test = Square(sideLength: 5.2, name: "my test square") // Square 인스턴스를 생성합니다.
test.area() // 넓이를 계산합니다.
test.simpleDescription() // 정사각형에 대한 설명을 출력합니다.
 
swift

단순히 저장되는 프로퍼티 외에도 프로퍼티는 게터와 세터를 가질 수 있습니다.

class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0 // 프로퍼티 sideLength를 선언하고 초기값으로 0.0을 할당합니다.
 
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3 // numberOfSides 프로퍼티에 3을 할당합니다.
    }
 
    var perimeter: Double { // 둘레를 나타내는 연산 프로퍼티입니다.
        get {
            return 3.0 * sideLength // 둘레를 계산하여 반환합니다.
        }
        set {
            sideLength = newValue / 3.0 // 새로운 값으로부터 변의 길이를 설정합니다.
        }
    }
 
    override func simpleDescription() -> String {
        return "An equilateral triangle with sides of length \(sideLength)."
    }
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle") // 인스턴스를 생성합니다.
print(triangle.perimeter) // 둘레를 출력합니다.
// "9.3"이 출력됩니다.
triangle.perimeter = 9.9 // 둘레에 새로운 값을 할당합니다.
print(triangle.sideLength)
// "3.3000000000000003"이 출력됩니다.
swift

perimeter의 세터에서 새로운 값은 암시적인 이름 newValue를 가집니다. set 뒤에 괄호로 명시적인 이름을 제공할 수도 있습니다.

EquilateralTriangle 클래스의 이니셜라이저가 세 가지 다른 단계를 가지고 있다는 점에 주목하세요:

  1. 서브클래스에서 선언한 프로퍼티의 값을 설정합니다.
  2. 슈퍼클래스의 이니셜라이저를 호출합니다.
  3. 슈퍼클래스에서 정의된 프로퍼티의 값을 변경합니다. 메서드, 게터 또는 세터를 사용하는 추가 설정 작업도 이 시점에 할 수 있습니다.

프로퍼티를 계산할 필요는 없지만 새로운 값을 설정하기 전후에 실행될 코드를 제공해야 한다면 willSetdidSet을 사용하면 됩니다. 제공한 코드는 이니셜라이저 외부에서 값이 변경될 때마다 실행됩니다. 예를 들어, 아래 클래스는 삼각형의 변 길이가 항상 정사각형의 변 길이와 같도록 보장합니다.

class TriangleAndSquare {
    var triangle: EquilateralTriangle {
        willSet {
            square.sideLength = newValue.sideLength // 삼각형의 변 길이가 변경되기 전에 정사각형의 변 길이를 업데이트합니다.
        }
    }
    var square: Square {
        willSet {
            triangle.sideLength = newValue.sideLength // 정사각형의 변 길이가 변경되기 전에 삼각형의 변 길이를 업데이트합니다.
        }
    }
    init(size: Double, name: String) {
        square = Square(sideLength: size, name: name)
        triangle = EquilateralTriangle(sideLength: size, name: name)
    }
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
// "10.0"이 출력됩니다.
print(triangleAndSquare.triangle.sideLength)
// "10.0"이 출력됩니다.
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
// "50.0"이 출력됩니다.
swift

옵셔널 값을 다룰 때는 메서드, 프로퍼티 및 서브스크립팅 앞에 ?를 작성할 수 있습니다. ? 앞의 값이 nil이면 ? 뒤의 모든 것이 무시되고 전체 표현식의 값이 nil이 됩니다. 그렇지 않으면 옵셔널 값이 언래핑되고 ? 뒤의 모든 것이 언래핑된 값에 대해 작동합니다. 두 경우 모두 전체 표현식의 값은 옵셔널 값입니다.

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") // 옵셔널 Square 인스턴스를 생성합니다.
let sideLength = optionalSquare?.sideLength // 옵셔널 체이닝을 사용하여 sideLength에 접근합니다.
swift

이렇게 클래스와 객체에 대해 살펴봤습니다. 클래스를 정의하고 프로퍼티와 메서드를 추가하는 방법, 서브클래싱을 통해 기존 클래스를 확장하는 방법, 그리고 이니셜라이저와 디이니셜라이저를 사용하여 객체의 생성과 소멸을 관리하는 방법을 배웠습니다. 또한 프로퍼티 옵저버와 옵셔널 체이닝을 사용하여 값의 변화에 반응하고 안전하게 옵셔널 값에 접근하는 방법도 알아봤습니다. 클래스와 객체는 Swift에서 데이터와 기능을 모델링하는 강력한 도구이기 때문에 반드시 잘 알아야 합니다.