[Swift] 구조체와 클래스 (Structures and Classes)
프로그램을 구성하는 데 있어서 구조체와 클래스 (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)
결국 Property, Method, Subscript, Initialization, Extension, Protocol 이 공통점으로 볼 수 있겠다.
차이점
클래스 (Class) 에는 구조체 (Structure) 에 없는 추가적인 기능이 있다.
- 다른 클래스의 특성을 상속받을 수 있다 (Inheritance)
- 클래스의 인스턴스를 해석하고 확인한다 (Type Casting)
- 클래스 안에 있는 값들을 메모리에서 해제해 준다 (Deinitialization)
- 클래스안에 있는 인스턴스들에 대한 여러 개의 참조를 허용한다 (Automatic Reference Counting)
결국 클래스는 구조체보다 복잡하고, 꼭 필요할 때에만 클래스를 쓰라는 것이 공식문서에서 주는 가이드이다. 실질적으로는 구조체와 열거형으로 충분한 것들이 많다.
구문 정의 (Definition Syntax)
구조체 (Structure) 와 클래스 (Class) 는 구문 정의가 유사하다. 구조체 (Structure) 는 Struct
키워드로 클래스 (Class) 는 Class
키워드로 시작한다. 중괄호 {}
를 사용하며 그 안에 정의가 들어간다.
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
hd
와 cinema
는 같은 width
와 height
값을 가지지만, 다른 인스턴스이다. 그다음 cinema
의 width
프로퍼티를 수정해보자.
cinema.width = 2048
cinema
의 width
에 접근해보면 2048
의 값으로 바뀐 것을 확인 할 수 있따. 하지만 기존 hd
의 width
는 그대로 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"
cinema
와 hd
는 분리된 인스턴스이기 때문에 서로에게 영향이 가지 않는다.
참조 타입인 클래스 (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
클래스는 참조 타입이기 때문에 tenEighty
와 alsoTenEighty
는 실질적으로 같은 VideoMode
인스턴스를 참조하게 된다. 복사가 되어 따로 저장이 되지 않는다.
따라서 tenEighty
도 아래와 같이 frameRate
가 30.0
이 된 것을 확인 할 수 있다.
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// Prints "The frameRate property of tenEighty is now 30.0"
따라서 참조 타입을 사용하게 되면 값이 한꺼번에 변경된다. alsoTenEighty
를 변경하면서 tenEighty
까지 고려해야 한다는 것이다. 수정을 하게 되면 alsoTenEighty
나 tenEighty
를 통해 VideoMode
의 프로퍼티에 접근하게 된다. alsoTenEighty
나 tenEighty
는 단지 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."
위와 같은 코드를 통해 tenEighty
와 alsoTenEighty
는 동일한 클래스의 인스턴스를 참조한다는 것을 알 수 있다.