구조체와 클래스
객체(object)
- 클래스의 인스턴스를 다른 언어에서 주로 객체라고 함
그러나 Swift 구조체와 클래스는 다른 언어보다 둘이 기능적으로 가깝고
이 챕터에서 클래스, 구조체 타입의 인스턴스에 적용되는 기능을 설명하므로 인스턴스(instance)라는 용어를 사용할 것임
구조체와 클래스가 동일한 점
- 프로퍼티 정의
- 메서드 정의
- 서브 스크립트 정의
- 초기화(initialization) 정의
- 확장(extension) 가능
- 프로토콜 준수 가능
다른 점 (클래스에만 있는 기능들)
| 클래스 |
구조체 |
| 상속 가능 |
X |
| 타입 캐스팅을 통해 런타임에 클래스 인스턴스의 타입 확인 및 해석 가능 |
X |
| Deinitializer를 사용해 클래스 인스턴스 할당된 리소스 해제 가능 |
X |
| Reference Count는 하나 이상의 클래스 인스턴스 참조를 허락 |
X |
| 동시성(Concurrency)의 actor와 유사 |
X |
정의 구문
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution() // 구조체 인스턴스로 초기화됨
var interlaced = false
var frameRate = 0.0
var name: String?
}
구조체와 클래스 인스턴스
위의 구조체, 클래스의 정의는 모양만 설명하는 것이지 자체적인 해상도, 비디오 모드를 설명하고 있지 않음
→ 자체적인 것을 설명하려면 구조체, 클래스의 인스턴스 생성이 필요함
let someResolution = Resolution() // 구조체 인스턴스 초기화
let someVideoMode = VideoMode() // 클래스 인스턴스 초기화
프로퍼티 접근
. (dot syntex)를 사용해 인스턴스의 프로퍼티에 접근 가능
print("The width of someVideoMode is \(someVideoMode.resolution.width)")
someVideoMode.resolution.width = 1280 // 프로퍼티에 접근해 값 할당
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
구조체 타입에 대한 멤버별 초기화 구문
모든 구조체는 구조체 인스턴스를 초기화해서 생성할 때 자동적으로 생성된 멤버별 초기화 구문을 가지고있음
let macbookM1Pro16 = Resolution(width: 1024, height: 512)
🚨 반면 클래스 인스턴스는 기본 멤버별 초기화를 받지 않는다 (즉 default memberwise initialize X)
구조체와 열거형은 값 타입
값 타입(value type)은 변수, 상수에 할당 or 함수에 전달될 때 값이 복사되는 타입
Swift의 기본 타입들은 거의 모두 값 타입이고 구조체로 구현되어 있음
Swift의 모든 구조체와 열거형은 값 타입 → 생성된 인스턴스, 프로퍼티 등은 코드에서 전달될 때 복사됨
배열, 딕셔너리, 문자열과 같은 표준 라이브러리에 정의된 콜렉션 타입은 최적화를 사용해 복사 성능 비용을 줄임
이러한 콜렉션 타입은 즉시 복사본을 만드는 것이 아니라 원본 인스턴스와 복사본 간에 요소가 저장된 메모리를 공유하고 있다가, 실제로 수정이 일어나기 직전에 복사를 함 → CoW (Copy on Write)
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.width = 2048
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"
hd 상수 값을 cinema 변수에 할당해준다.
이때 구조체는 값 타입이므로 기존 인스턴스의 복사본이 생성되고 새로운 복사본이 cinema에 할당되게 됨
따라서 아래 그립과 같이 hd, cinema는 완벽히 분리된 인스턴스이고, 그렇기에 cinema의 width를 변경해도 hd에 영향을 미치지 않음

열거형에서도 동일한 동작이 이뤄짐
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)")
// Prints "The current direction is north"
// Prints "The remembered direction is west"
클래스는 참조 타입
값 타입과 반대로 클래스는 참조 타입이다.
참조 타입은 변수, 상수에 할당 or 함수로 전달될 때 복사되지 않고 이미 존재하는 인스턴스에 대한 참조를 사용하게 됨
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution() // 구조체 인스턴스로 초기화됨
var interlaced = false
var frameRate = 0.0
var name: String?
}
let tenEighty = VideoMode()
let hd = Resolution(width: 1920, height: 1080)
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// Prints "The frameRate property of tenEighty is now 30.0"
클래스는 참조타입이라서 alsoTenEighty 에 값을 할당할 때 실질적으로 같은 VideoMode 인스턴스를 참조하게 된다.
그래서 아래와 같은 인스턴스에 2개의 다른 이름을 가지게 되는 것이다.
tenEighty, alsoTenEighty는 둘 다 변수가 아닌 상수(let)으로 선언되었지만 내부 프로퍼티 값들은 변경이 가능하다.
즉, tenEighty, alsoTenEighty 자체는 VideoMode 인스턴스를 저장하지 않고 둘 다 참조하며,
이때 변경되는 것은 VideoMode에 대한 상수 참조의 값이 아니라 VideoMode의 frameRate 프로퍼티이기 때문에 가능하다.

이 예시를 통해 참조타입이 추론하기 어려운 것을 확인할 수 있다.
tenEighty, alsoTenEighty가 코드에서 멀리 떨어져 있다면, 비디오 모드가 변경되는 모든 방법을 찾기 어려움
또한 tenEighty를 사용할 때 alsoTenEighty도 생각해야하고 그 반대도 마찬가지다.
반면 값 타입은 동일한 값과 상호작용하는 모든 코드가 소스 파일에 가까이 있기 때문에 추론하기 쉽다.
식별 연산자 (Identity Operators)
클래스는 참조 타입이기 때문에 하나의 클래스 인스턴스를 참조하는 여러 개의 상수와 변수가 존재할 수 있다.
let vd = ViewMode()
var vd2 = vd
var vd3 = vd
이때 2개의 상수, 변수가 같은 클래스 인스턴스를 참조하는지 확인해야할 필요가 있을 때 아래 식별연산자를 사용할 수 있다.
=== : 동일 인스턴스
!== : 동일하지 않은 인스턴스
if tenEighty === alsoTenEighty {
print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// Prints "tenEighty and alsoTenEighty refer to the same VideoMode instance."
🚨 이때 == 와 === 는 다른 것임
== 는 인스턴스의 값이 동일하거나 동등하다는 것을 의미
=== 는 2개의 상수, 변수가 동일한 클래스 인스턴스를 참조한다는 의미
포인터
C, C++, Objective-C에서는 메모리의 주소를 참조하기 위해 포인터를 사용한다.
일부 참조 타입의 인스턴스를 참조하기 위한 Swift 상수, 변수는 C 포인터와 유사하지만 메모리의 주소에 대한 직접적인 포인터가 아니므로 포인터 표시를 위해 *(별표)를 작성할 필요는 없다.
대신 이러한 참조는 Swift의 다른 상수, 변수처럼 정의된다.
표준 라이브러리는 포인터와 직접 상호작용이 필요한 경우에 사용할 수 있는 포인터와 버퍼 타입을 제공하고,
이는 MMM(Manual Memory Management)를 참고해라!!
구조체와 클래스
구조체와 클래스가 동일한 점
다른 점 (클래스에만 있는 기능들)
정의 구문
구조체와 클래스 인스턴스
위의 구조체, 클래스의 정의는 모양만 설명하는 것이지 자체적인 해상도, 비디오 모드를 설명하고 있지 않음
→ 자체적인 것을 설명하려면 구조체, 클래스의 인스턴스 생성이 필요함
프로퍼티 접근
.(dot syntex)를 사용해 인스턴스의 프로퍼티에 접근 가능구조체 타입에 대한 멤버별 초기화 구문
모든 구조체는 구조체 인스턴스를 초기화해서 생성할 때 자동적으로 생성된 멤버별 초기화 구문을 가지고있음
🚨 반면 클래스 인스턴스는 기본 멤버별 초기화를 받지 않는다 (즉 default memberwise initialize X)
구조체와 열거형은 값 타입
값 타입(value type)은 변수, 상수에 할당 or 함수에 전달될 때 값이 복사되는 타입
Swift의 기본 타입들은 거의 모두 값 타입이고 구조체로 구현되어 있음
Swift의 모든 구조체와 열거형은 값 타입 → 생성된 인스턴스, 프로퍼티 등은 코드에서 전달될 때 복사됨
hd 상수 값을 cinema 변수에 할당해준다.
이때 구조체는 값 타입이므로 기존 인스턴스의 복사본이 생성되고 새로운 복사본이 cinema에 할당되게 됨
따라서 아래 그립과 같이 hd, cinema는 완벽히 분리된 인스턴스이고, 그렇기에 cinema의 width를 변경해도 hd에 영향을 미치지 않음
열거형에서도 동일한 동작이 이뤄짐
클래스는 참조 타입
값 타입과 반대로 클래스는 참조 타입이다.
참조 타입은 변수, 상수에 할당 or 함수로 전달될 때 복사되지 않고 이미 존재하는 인스턴스에 대한 참조를 사용하게 됨
클래스는 참조타입이라서
alsoTenEighty에 값을 할당할 때 실질적으로 같은 VideoMode 인스턴스를 참조하게 된다.그래서 아래와 같은 인스턴스에 2개의 다른 이름을 가지게 되는 것이다.
tenEighty, alsoTenEighty는 둘 다 변수가 아닌 상수(let)으로 선언되었지만 내부 프로퍼티 값들은 변경이 가능하다.
즉, tenEighty, alsoTenEighty 자체는 VideoMode 인스턴스를 저장하지 않고 둘 다 참조하며,
이때 변경되는 것은 VideoMode에 대한 상수 참조의 값이 아니라 VideoMode의 frameRate 프로퍼티이기 때문에 가능하다.
이 예시를 통해 참조타입이 추론하기 어려운 것을 확인할 수 있다.
tenEighty, alsoTenEighty가 코드에서 멀리 떨어져 있다면, 비디오 모드가 변경되는 모든 방법을 찾기 어려움
또한 tenEighty를 사용할 때 alsoTenEighty도 생각해야하고 그 반대도 마찬가지다.
반면 값 타입은 동일한 값과 상호작용하는 모든 코드가 소스 파일에 가까이 있기 때문에 추론하기 쉽다.
식별 연산자 (Identity Operators)
클래스는 참조 타입이기 때문에 하나의 클래스 인스턴스를 참조하는 여러 개의 상수와 변수가 존재할 수 있다.
이때 2개의 상수, 변수가 같은 클래스 인스턴스를 참조하는지 확인해야할 필요가 있을 때 아래 식별연산자를 사용할 수 있다.
===: 동일 인스턴스!==: 동일하지 않은 인스턴스포인터
C, C++, Objective-C에서는 메모리의 주소를 참조하기 위해 포인터를 사용한다.
일부 참조 타입의 인스턴스를 참조하기 위한 Swift 상수, 변수는 C 포인터와 유사하지만 메모리의 주소에 대한 직접적인 포인터가 아니므로 포인터 표시를 위해 *(별표)를 작성할 필요는 없다.
대신 이러한 참조는 Swift의 다른 상수, 변수처럼 정의된다.
표준 라이브러리는 포인터와 직접 상호작용이 필요한 경우에 사용할 수 있는 포인터와 버퍼 타입을 제공하고,
이는 MMM(Manual Memory Management)를 참고해라!!