본문 바로가기

Swift 문서 탐방

[Swift 문서 탐방] Structures and Classes

구조체(Structure)와  클래스(Class)는 프로그램 코드의 기본적인 구성 요소로 프로퍼티(Property)와 메서드(Method)를 함게 정의하여 데이터와 기능을 묶어 관리하는 커스텀 타입입니다.

Comparing Structures and Classes

구조체와 클래스은 아래와 같은 공통점을 가지고 있습니다.

  • 데이터를 저장하기 위한 프로퍼티(Property)가 존재합니다.
  • 기능을 정의하기 위한 메서드(Method)가 존재합니다.
  • 서브스크립트 구문을 사용하여 값에 대한 액세스를 제공하기 위해 서브스크립트를 정의할 수 있습니다.
  • 초기값 설정을 위해 생성자(Iitializer)를 가지고 있습니다.
  • 기본 구현을 넘어 기능을 확장하도록 확장이 가능합니다.
  • 특정 종류의 표준 기능을 제공하기 위한 프로토콜을 준수합니다.

클래스에는 구조체에는 없는 추가적인 특징들을 가지고 있습니다.

  • 상속을 통해 한 클래스의 특성을 다른 클래스에게 물려줄 수 있습니다.
  • 런타임에 타입 캐스팅을 통해 클래스의 인스턴스 타입을 확인 및 해석할 수 있습니다.
  • 해제자(Deinitializer)는 클래스 인스턴스가 할당한 리소스를 해제할 수 있습니다.
  • 참조 계산(Reference Counting)은 한 클래스 인스턴스에 대한 두 개 이상의 참조를 허용합니다.

클래스가 지원하는 추가 기능은 복잡성 증가라는 것입니다. 따라서 일반적으로 구조체 사용이 선호됩니다. 그 이유는 구조체가 더 추론되기 쉽기 때문입니다. 반면 클래스는 정말 필요할 때만 사용됩니다. 따라서 실무에서 이 말은 대부분의 커스텀 타입이 구조체나 혹은 열거형(Enum)가 더 많이 사용된다는 의미입니다.

Definition Syntax

Swift 언어에서 구조체와 클래스의 정의 문법은 거의 동일합니다. 유일한 차이는 사용되는 키워드가 다르다는 것 뿐입니다.

(구조체는 struct, 클래스는 class)

struct SomeStructure {
    // 
}
class SomeClass {
    // 
}

Structure and Class Instances

구조체와 클래스 인스턴스 생성은 다음과 같습니다.

let someResolution = Resolution()
let someVideoMode = VideoMode()

Accessing Properties

구조체와 클래스에서 프로퍼티에 접근할 때 점 문법을 사용해서 접근하시면 됩니다.

점 문법은 아래 코드에서 보실 수 있듯이 인스턴스 이름 바로 뒤에 점(.)을 작성한 다음 접근하고자는 프로퍼티 이름을 작성해주시면 됩니다.

print("The width of someResolution is \(someResolution.width)")

Memberwise Initializers for Structure Types

모든 구조체 자동적으로 멤버와이즈 이니셜라이저(Memberwise Initializer)를 가지고 있습니다. 이는 새로운 구조체 인스턴스의 멤버 프로퍼티들을 초기화시켜줍니다. 새로운 인스턴스에 대한 초기값은 이름으로 멤버별 생성자에 전달할 수 있습니다.

let vga = Resolution(width: 640, height: 480)

 

반면 구조체와 달리 클래스는 멤머와이즈 이니셜라이저가 존재하지 않습니다.

Structures and Enumerations Are Value Types

값 타입(Value Type)이란 변수에 할당되었거나 함수로 전달되었을 때 값이 복사되는 타입을 의미합니다.

그런 의미에서 Swift에서는 구조체(Structure)와 열거형(Enumeration)은 모두 값 타입에 속합니다. 

이외에도 Swift의 기본 타입인 Int, Float, Bool, String, Array, Dictionary 등 모두 내부적으로 구조체로 구현되어 있습니다. 

 

이해를 더 돞기 위해 예를 한번 들어보겠습니다. 

아래 코드와 같이 Resolution 구조체가 생성하여 hd 변수에 할당한 뒤 이 hd 변수의 값을 또 다른 변수인 cinema에 할당했다고 해봅시다.

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd

 

이때 cinema 변수에는 hd의 값이  복사되어 이 새로운 Resolution 인스턴스를 할당되게 됩니다. 이를 즉시 복사라고 불립니다.

Note: Swift의 Copy-on-Write(CoW)와 할당 시 복사의 차이
Swift에서 Array나 Dictionary와 같은 일부 값 타입은 Copy-on-Write(CoW) 최적화를 적용하여, 수정될 때에만 실제 복사가 일어납니다. 그러나 일반적인 구조체(Struct)와 열거형(Enum)은 CoW 최적화가 자동으로 적용되지 않으며, 새로운 변수에 할당되는 순간 값 복사가 발생해 독립적인 인스턴스가 생성됩니다. 이는 복잡한 데이터 공유 없이 구조체와 열거형이 단순하고 명확하게 동작할 수 있도록 설계된 결과입니다.

 

정말 복사가 일어났는지 확인해보기 위해 아래와 같이 cinema의 프로퍼티인 width에 2048라는 새로운 변수를 할당해보겠습니다.

cinema.width = 2048

 

아래 코드에서 보실 수 있다시피 출력 결과 cinema의 width의 값은 2048이고, hd의 width는 1920로 값이 변경되지 않은 채로 출력되어 서로 영향을 미치지 않았다는 사실을 알 수 있습니다.

print("cinema is now \(cinema.width) pixels wide") // "cinema is now 2048 pixels wide" 출력
print("hd is still \(hd.width) pixels wide") // "hd is still 1920 pixels wide" 출력

열거형에서도 마찬가지입니다.

아래 코드 예시를 보시면 rememberDirection에 currentDirection의 값이 할당될 때 실제로 해당 값의 복사본으로 설정됩니다. 그 후 currentDirection의 값을 변경해도 rememberDirection에 저장된 원래 값의 복사본에는 영향을 미치지 않습니다.

enum CompassPoint {
    case north, south, east, west
    mutating func turnNorth() {
        self = .north
    }
}
var currentDirection = CompassPoint.west
let rememberedDirection = currentDirection
currentDirection.turnNorth()


print("The current direction is \(currentDirection)")
print("The remembered direction is \(rememberedDirection)")
// "The current direction is north" 출력
// "The remembered direction is west" 출력

Classes Are Reference Types

값 유형과 달리 참조 유형(Reference Type)은 변수나 상수에 할당되거나 함수에 전달될 때 복사되지 않습니다. 복사 대신 동일한 기존 인스턴스에 대한 참조가 사용됩니다.

 

예를 들어, 아래와 같이 클래스 ViewMode의 인스턴스가 생성되어 tenEighty 변수에 할당해주고 ViewMode의 각 프로퍼티들을 값을 할당해준 상황이 있다고 가정해 보겠습니다.

let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

 

이제 tenEighty에 할당된 메모리 주소를 새로운 alsoTenEighty 변수에 할당해주고, 그 내부 프로퍼티 중 alsoTenEighty의 frameRate 프로퍼티의 값을 변경해보겠습니다. 

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

 

이제 아래 코드에서 보시는 것처럼 tenEighty.frameRate의 값이 변경된 채 출력된다는 사실을 알 수 있습니다. 이는 클래스는 참조타입이므로 가능한 일인데요. tenEighty 변수를 alsoTenEighty 변수에 할당했을 때, tenEighty가 참조하는 ViewMode 인스턴스 주소를 alsoTenEighty에 할당하여 tenEighty와 alsoTenEighty가 같은 ViewMode 인스턴스를 가리키게 됩니다. 따라서 alsoTenEighty.frameRate의 값을 변경했을 때,  tenEighty와 alsoTenEighty가 동일하게 가리키느 ViewMode 인스턴스의 frameRate 값이 변경되면서 tenEighty.frameRate을 출력했을 때, 기존과는 수정되 값이 출력된 것입니다.

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// "The frameRate property of tenEighty is now 30.0" 출력

참고 사이트

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/classesandstructures/ (Swift 공식 문서 - Structures and Classes)