Swift/Swift 공식 문서

[Swift] 구조체와 클래스 (Structures and Classes)

hyunjuntyler 2023. 4. 30. 23:08

프로그램을 구성하는 데 있어서 구조체와 클래스 (Structures and Classes) 는 필수 요소이다. 속성과 방법을 정의하게 된다. 스위프트에서는 구조체와 클래스를 정의하기만 하면 자동으로 사용할 수 있게 된다고 한다.

Note : 인스턴스 (instance) 라는 단어가 자주 등장하는데 인스턴스가 도대체 뭘까? 클래스나 구조체, 열거형에서 생성된 것 (Object) 를 이야기한다. 하지만 Swift에서는 구조체 또는 클래스가 기능에 가깝기 때문에 인스턴스 (instance) 라는 용어를 사용한다고 한다. 아래 처럼 상수 human1 을 인스턴스라고 부른다.
let human1 = Human(name: "Tyler", age: 30)

구조체와 클래스의 비교 (Comparing Structures and Classes)

공통점

  • 값을 저장하는 속성 (Properties)
  • 기능을 제공하는 방법 (Methods)
  • 값에 접근을 제공하는 서브스크립트 (Subscripts)
  • 초기화 상태 설정 (Initialization)
  • 기본기능에서 기능확장 (Extensions)
  • 기능을 구현하기 위한 약속 (Protocols)

 

결국 PropertyMethodSubscriptInitializationExtensionProtocol 이 공통점으로 볼 수 있겠다.

차이점

클래스 (Class) 에는 구조체 (Structure) 에 없는 추가적인 기능이 있다.

  • 다른 클래스의 특성을 상속받을 수 있다 (Inheritance)
  • 클래스의 인스턴스를 해석하고 확인한다 (Type Casting)
  • 클래스 안에 있는 값들을 메모리에서 해제해 준다 (Deinitialization)
  • 클래스안에 있는 인스턴스들에 대한 여러 개의 참조를 허용한다 (Automatic Reference Counting)

 

결국 클래스는 구조체보다 복잡하고, 꼭 필요할 때에만 클래스를 쓰라는 것이 공식문서에서 주는 가이드이다. 실질적으로는 구조체와 열거형으로 충분한 것들이 많다.

구문 정의 (Definition Syntax)

구조체 (Structure) 와 클래스 (Class는 구문 정의가 유사하다. 구조체 (Structure) 는 Struct 키워드로 클래스 (ClassClass 키워드로 시작한다. 중괄호 {} 를 사용하며 그 안에 정의가 들어간다.

Struct SomeStructure {
	// structure 정의
}
Class SomeClass {
	// class 정의
}
Note : 구조체 또는 클래스를 정의할 때, UpperCamelCase, 프로퍼티와 메서드는 lowerCamelCase로 지정하는 것이 좋다.

구조체와 클래스 정의의 예시는 아래와 같다.

struct Resolution {
    var width = 0
    var height = 0
}
class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

위의 구조체에서는 해상도 (Resolution) 라는 새로운 구조체를 정의한다. 구조체의 이름은 Resolution 이고, 이 구조체는 width, height 라 불리는 속성, 즉 프로퍼티 (property) 를 가지고 있다. 여기서 프로퍼티란 구조체나 클래스에 있는 상수 (constants) 나 변수 (variables) 를 이야기 한다. Resolution 이라는 구조체의 프로퍼티는 정수값 0 이 초기값이므로 Int 타입으로 여겨진다.

위의 클래스에서는 VideoMode 라는 새로운 클래스를 정의한다. 이 클래스는 4개의 상수 프로퍼티를 가지고 있다. 첫번재 resolution 은 Resolution 구조체 인스턴스를 가져오게  되고, 다른 프로퍼티 경우 interlaced 는 false frameRate 는 0.0 으로 그리고 name 은 옵셔널 String 값으로 초기값이 설정 된다.

구조체와 클래스 인스턴스 (Structure and Class Instances)

이렇게 구조체와 클래스를 선언한 것은 Resolution 이나 VideoMode 의 "정의" 만 한 것이다. 이 구조체와 클래스를 가져오기 사용하기 위해서는 인스턴스로 만들어주는 과정이 필요하다.

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

인스턴스를 만들어 주는 과정은 클래스나 구조체나 일정하다. 이렇게 만들어주면 someResolution 이나 someVideoMode 는 정의된 구조체나 클래스의 초기값으로 설정이 된다. 여기서 초기화 (Initialization) 라는 과정도 구조체나 클래스에서 중요한데, 이 부분은 뒤에서 좀더 자세히 언급된다. 우선 맨 처음 설정한 값으로 설정이 되는구나 정도만 알면 좋을 듯 하다.

프로퍼티 접근 (Accessing Properties)

. 점 구문 (dot syntax) 을 사용하여 인스턴스의 프로퍼티에 접근이 가능하다. 예시와 같이 위에서 만들어준 someResolution 라는 인스턴스 뒤에 .width 를 통해 초기값인 0 을 가져올 수 있다.

print("The width of someResolution is \(someResolution.width)")
// Prints "The width of someResolution is 0"

VideoMode 클래스로 만든 someVideoMode 인스턴스 안의 resolution 프로퍼티 안의 width 라는 서브프로퍼티에 접근도 가능하다.

Note : 이때 아래 예시에서는 클래스에서 불러온 구조체안의 프로퍼티를 Subproperty라고 한다. 가져온 프로퍼티가 프로퍼티를 가지고 있을때 쓴다.
print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is 0"

someVideoMode.resolution.width 에 접근해서 1280 라는 새로운 값을 지정해 줄 수 있다.

someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is now 1280"

구조체 타입의 멤버별 초기화 구문 (Memberwise Initializers for Structure Types)

구조체는 인스턴스를 만들때, 아래와 같이 width, height 같은 멤버들에 대한 새로운 초기값을 지정해 줄 수 있다.

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

클래스 인스턴스는 위와 같은 멤버별 초기화값 지정이 불가능하다.

값 타입인 구조체 (Structures Are Value Types)

실제로 Swift에서 볼 수 있는 정수, 문자열, 배열, 딕셔너리 같은 값 타입들은 이미 구조체로 구현된 값 타입들이다. 모든 구조체와 열거형은 값 타입으로 볼 수 있다. 

아래와 같이 Resolution 구조체의 인스턴스인 hd 라는 상수를 선언하고, hd 를 복사한 cinema 라는 변수를 선언한다.

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

hdcinema 는 같은 widthheight 값을 가지지만, 다른 인스턴스이다. 그다음 cinemawidth 프로퍼티를 수정해보자.

cinema.width = 2048

cinemawidth 에 접근해보면 2048 의 값으로 바뀐 것을 확인 할 수 있따. 하지만 기존 hdwidth 는 그대로 1920 이다.

print("cinema is now \(cinema.width) pixels wide")
// Prints "cinema is now 2048 pixels wide"

print("hd is still \(hd.width) pixels wide")
// Prints "hd is still 1920 pixels wide"

cinemahd 는 분리된 인스턴스이기 때문에 서로에게 영향이 가지 않는다.

참조 타입인 클래스 (Classes Are Reference Types)

값 타입과 반대로 참조 타입은 변수 또는 상수에 할당 될 때나 함수로 전달될 때 복사되지 않는다. 예시를 든 VideoMode 의 경우를 보자.

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

tenEighty 라는 새로운 상수를 선언하고, 각 프로퍼티들의 값을 설정해준다. 그리고 아래와 같이 alsoTenEighty 라는 새로운 상수를 설정해 준다. 또한 frameRate 프로퍼티를 30.0 으로 설정해준다.

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

클래스는 참조 타입이기 때문에 tenEightyalsoTenEighty 는 실질적으로 같은 VideoMode 인스턴스를 참조하게 된다. 복사가 되어 따로 저장이 되지 않는다.

따라서 tenEighty 도 아래와 같이 frameRate30.0 이 된 것을 확인 할 수 있다.

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

따라서 참조 타입을 사용하게 되면 값이 한꺼번에 변경된다. alsoTenEighty 를 변경하면서 tenEighty 까지 고려해야 한다는 것이다. 수정을 하게 되면 alsoTenEightytenEighty 를 통해 VideoMode 의 프로퍼티에 접근하게 된다. alsoTenEightytenEighty 는 단지 VideoMode 를 참조한다.

식별 연산자 (Identity Operators)

따라서 두개의 상수 또는 변수가 같은 클래스의 인스턴스를 참조하는지 확인할 수 있다. 동일 인스턴스는 ===, 동일하지 않은 인스턴스는 !== 로 확인 할 수 있다.

if tenEighty === alsoTenEighty {
    print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// Prints "tenEighty and alsoTenEighty refer to the same VideoMode instance."

위와 같은 코드를 통해 tenEightyalsoTenEighty 는 동일한 클래스의 인스턴스를 참조한다는 것을 알 수 있다.