Coding Log

스위프트 찔러보기 - (4) 객체와 클래스(Objects and Classes)

본 블로그에 올라가는 포스팅은 애플의 공식 사이트를 대강 번역해서 작성하는 것이기 때문에 아래 링크에서 찔러보기 포스팅에서 쓸 예제 코드를 받을 수 있다.

참고 애플의 Playground를 이용한 Swift Example 다운로드 링크

클래스

클래스(class)를 만들기 위해서는 클래스명과 class 키워드를 사용하면 된다.

클래스의 속성은 클래스 선언 범위 안에 있을 뿐 변수나 상수와 같은 형태로 작성되며, 함수의 선언 또한 같은 방식으로 작성된다.

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

클래스명 다음에 괄호를 사용하여 클래스의 인스턴스를 생성할 수 있다.

.을 사용해서 생성된 인스턴스의 속성이나 함수에 접근할 수 있다.

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

위에서 본 예제를 다시 가져와보자.

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

위의 Shape클래스에는 중요한 것이 빠져있는 데, 바로 초기화를 위한 생성자인 init이다.

init키워드를 사용하여 작성할 수 있다.

class NamedShape {
    var numberOfSides: Int = 0
    var name: String
 
    init(name: String) {
        self.name = name
    }
 
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

NamedShape 클래스의 name 속성을 초기화하는 init부분을 보면 self라는 키워드를 볼 수 있다.

self는 호출된 클래스 자기자신을 가리키는 java의 this와 동일한 역할을 하는 것을 확인할 수 있다.

init의 파라미터는 클래스의 인스턴스를 생성하면서 함께 전달되며 클래스의 모든 속성은 선언과 동시에 값이 저장되거나 init을 통해 값이 저장되어야 한다.

앞으로는 특정 변수나 상수에 값을 저장한다는 것을 프로그래밍 용어인 초기화라고 표현하겠다.

클래스의 인스턴스를 메모리에서 제거하기 위해서는 init과 마찬가지로 deinit을 이용해서 제거한다.

class NamedShape {
    var numberOfSides: Int = 0
    var name: String
 
    init(name: String) {
        self.name = name
    }
 
    deinit() {
      print("Deallocate from memory")
    }
 
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

서브 클래스와 수퍼 클래스

서브 클래스는 하위 클래스라고도 한다.

아래가 있으면 위가 있듯 상위 클래스도 있으며, 이는 수퍼 클래스라고도 부른다.

서브 클래스는 클래명 뒤에 콜론과 함께 수퍼 클래스의 이름을 붙여서 표현할 수 있다.

모든 클래스가 수퍼 클래스를 가지는 것은 아니기 때문에 수퍼 클래스를 꼭 지정해야만 하는 것은 아니다.

class Square: NamedShape {
    var sideLength: Double
 
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 4
    }
 
    deinit() {
      print("Deallocate from memory")
    }
 
    func area() ->  Double {
        return sideLength * sideLength
    }
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()

위의 Square클래스는 서브 클래스로서 NamedShape클래스를 수퍼 클래스로 한다.

서브 클래스에서 수퍼 클래스에 이미 구현된 함수를 재정의(Override)하려면 override 키워드를 사용해서 해당 함수를 무조건 override 키워드를 사용해서 명시적으로 재정의하도록 해야 한다.

만약 override 키워드 없이 재정의를 하는 경우 컴파일러가 오류로 표시한다.

반대로 서브 클래스에서 override 키워드가 사용되어 재정의된 함수임을 명시했는데, 수퍼 클래스에 해당하는 함수가 없다면 이 또한 오류로 표시한다.

class Square: NamedShape {
    var sideLength: Double
 
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 4
    }
 
    deinit() {
      print("Deallocate from memory")
    }
 
    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")
test.area()
test.simpleDescription()

속성의 Getter/setter

앞으로 속성을 프로퍼티라고 표현하겠다.

클래스의 프로퍼티는 단순히 값을 초기화하는 것 뿐만 아니라 값을 가져오고 다시 초기화 할 수 있는 getter/setter를 포함할 수 있다.

class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0
 
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        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.3 출력 

둘레를 뜻하는 perimeter 프로퍼티를 보자.

setter 부분에 newValue라는 것이 있는 이는 perimeter를 초기화하기 위한 값의 암시적인 이름이다.

class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0
 
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }
 
    var perimeter: Double {
        get {
            return 3.0 * sideLength
        }
        set (value) {
            sideLength = value / 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.3 출력 

물론 set뒤에 괄호를 붙인 set()을 이용해 명시적으로 이름을 부여할 수도 있다.

init(sideLength: Double, name: String) {
    self.sideLength = sideLength // 1 
    super.init(name: name) // 2 
    numberOfSides = 3 // 3 
}

위의 EquilateralTriangle클래스의 initializer는 크게 3가지 단계로 나뉜다.

  1. 서브 클래스 EquilateralTriangle에서 선언된 프로퍼티의 초기화

  2. 수퍼 클래스 NamedShape의 initializer 호출

  3. 수퍼 클래스에서 선언된 프로퍼티의 값 변경

3단계에서 특정 함수나 getter/setter를 사용하지 않고도 변경할 수 있음을 확인할 수 있다.

앞으로 클래스에 관련된 함수는 메소드로 표현하겠다.

willSet / didSet

클래스의 프로퍼티에 새로운 값을 초기화하기 전/후에 처리해야할 코드가 있다면 willSet/didSet 키워드를 사용할 수 있다.

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)
print(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)

위의 TriangleAndSquare클래스는 triangle과 square의 값이 초기화될때마다 실행되어 triangle의 sideLength와 square의 sideLength의 값을 항상 균일하게 유지해준다.

Optional

클래스의 메소드, 프로퍼티, .을 이용한 접근에도 옵셔널을 이용할 수 있다.

코드를 실행하기 전에 옵셔널이 부여된 메소드, 프로퍼티 등의 값이 nil이라면 뒷부분의 코드는 무시되면 nil값을 가지게 된다.

기존의 옵셔널과 마찬가지로 메소드, 프로퍼티의 값이 존재한다면 뒷부분의 코드도 부여된 값을 바탕으로 정상 작동하게 된다.

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength


DISQUS 로드 중…
댓글 로드 중…

트랙백을 확인할 수 있습니다

URL을 배껴둬서 트랙백을 보낼 수 있습니다