🔥 클래스는 참조 타입
Swift에서 클래스는 참조 타입(Reference Type)으로 정의됩니다. 이는 클래스의 인스턴스가 할당되거나 함수에 전달될 때, 복사되지 않고 동일한 인스턴스에 대한 참조가 사용된다는 것을 의미하죠.
VideoMode
라는 클래스를 예로 들어볼까요?
class VideoMode { var resolution = Resolution() var interlaced = false var frameRate = 0.0 var name: String? }
swift
VideoMode
클래스는 비디오의 해상도, 비월 주사 여부, 프레임 속도, 이름 등의 속성을 가지고 있습니다. 이제 이 클래스의 인스턴스를 생성하고 속성 값을 설정해보겠습니다.
let tenEighty = VideoMode() // tenEighty 상수에 VideoMode 인스턴스 할당 tenEighty.resolution = hd // 해상도를 HD로 설정 tenEighty.interlaced = true // 비월 주사로 설정 tenEighty.name = "1080i" // 이름을 "1080i"로 설정 tenEighty.frameRate = 25.0 // 프레임 속도를 25.0으로 설정
swift
이렇게 tenEighty
라는 상수를 선언하고 VideoMode
의 새 인스턴스를 할당했습니다. 그리고 해상도를 HD(1920x1080)로, 비월 주사를 사용하도록, 이름을 "1080i"로, 프레임 속도를 25.0으로 각각 설정했죠.
그런 다음 tenEighty
를 alsoTenEighty
라는 새 상수에 할당하고, alsoTenEighty
의 frameRate
를 변경해보겠습니다.
let alsoTenEighty = tenEighty // alsoTenEighty에 tenEighty 할당 alsoTenEighty.frameRate = 30.0 // alsoTenEighty의 프레임 속도를 30.0으로 변경
swift
클래스가 참조 타입이기 때문에 사실 tenEighty
와 alsoTenEighty
는 동일한 VideoMode
인스턴스를 가리키게 됩니다. 효과적으로 단일 인스턴스에 대해 서로 다른 두 개의 이름을 사용하는 셈이죠. 아래 그림과 같이 말이에요.
tenEighty
의 frameRate
속성을 확인해보면 VideoMode
인스턴스의 프레임 속도가 30.0
으로 변경된 것을 알 수 있습니다.
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)") // "The frameRate property of tenEighty is now 30.0" 출력
swift
이 예제는 참조 타입으로 인해 코드를 이해하기 어려워질 수 있음을 보여주기도 합니다. 만약 tenEighty
와 alsoTenEighty
가 프로그램 코드에서 멀리 떨어져 있다면, 비디오 모드가 변경되는 모든 방식을 찾기가 어려울 수 있겠죠. tenEighty
를 사용하는 곳에서는 alsoTenEighty
를 사용하는 코드도 고려해야 하고, 그 반대의 경우도 마찬가지입니다. 반면에 값 타입은 동일한 값과 상호 작용하는 모든 코드가 소스 파일에서 가깝기 때문에 추론하기가 더 쉽습니다.
tenEighty
와 alsoTenEighty
가 상수로 선언되었음에 주목해주세요. 그럼에도 tenEighty.frameRate
와 alsoTenEighty.frameRate
를 변경할 수 있는데요, tenEighty
와 alsoTenEighty
상수 자체의 값은 실제로 변경되지 않기 때문입니다. tenEighty
와 alsoTenEighty
는 VideoMode
인스턴스를 "저장"하지 않고, 내부적으로 VideoMode
인스턴스를 "참조"하는 거예요. 변경되는 것은 기본 VideoMode
의 frameRate
속성이지, VideoMode
를 참조하는 상수 값이 아닙니다.
식별 연산자
클래스가 참조 타입이기 때문에 여러 상수와 변수가 내부적으로 동일한 클래스 인스턴스를 참조하는 것이 가능합니다. (구조체와 열거형의 경우 상수나 변수에 할당되거나 함수에 전달될 때 항상 복사되므로 동일한 경우가 아닙니다.)
때로는 두 상수나 변수가 정확히 동일한 클래스 인스턴스를 참조하는지 확인하는 것이 유용할 수 있는데요, 이를 위해 Swift는 두 가지 식별 연산자를 제공합니다:
- 같음 (
===
) - 같지 않음 (
!==
)
이 연산자들을 사용해 두 상수나 변수가 동일한 단일 인스턴스를 참조하는지 확인할 수 있습니다.
if tenEighty === alsoTenEighty { print("tenEighty와 alsoTenEighty는 같은 VideoMode 인스턴스를 참조합니다.") } // "tenEighty와 alsoTenEighty는 같은 VideoMode 인스턴스를 참조합니다." 출력
swift
같음
연산자(===
)는 같다
(==
)와 의미가 다릅니다. 같음
은 클래스 타입의 두 상수나 변수가 정확히 동일한 클래스 인스턴스를 참조함을 의미하죠. 반면 같다
는 두 인스턴스가 타입 설계자가 정의한 적절한 의미의 같다
에 대해 값이 동일하거나 동등함을 의미합니다.
사용자 정의 구조체와 클래스를 정의할 때는 두 인스턴스가 같다고 판단할 기준을 결정하는 것이 여러분의 책임입니다. ==
와 !=
연산자의 구현을 직접 정의하는 과정은 동등 연산자에 설명되어 있어요.
포인터
C, C++, Objective-C 경험이 있다면 이러한 언어에서 메모리 주소를 참조하기 위해 포인터
를 사용한다는 것을 알고 있을 거예요. Swift에서 참조 타입의 인스턴스를 참조하는 상수나 변수는 C언어의 포인터와 유사하지만, 메모리의 주소를 직접 가리키는 것은 아니며 참조를 생성한다는 것을 나타내기 위해 별표(*
)를 쓸 필요가 없습니다. 대신 이러한 참조는 Swift의 다른 상수나 변수와 같은 방식으로 정의됩니다.
Swift 표준 라이브러리는 포인터와 직접 상호 작용해야 하는 경우 사용할 수 있는 포인터와 버퍼 타입을 제공하는데요, 자세한 내용은 수동 메모리 관리를 참고해주세요.
이렇게 클래스가 참조 타입으로 동작하는 방식과 식별 연산자의 사용, 그리고 포인터에 대해 살펴봤습니다. 참조 타입은 코드의 이해와 추론을 어렵게 만들 수 있지만, 클래스 인스턴스를 효율적으로 공유하고 전달할 수 있게 해주는 강력한 기능이기도 하죠. 값 타입과 참조 타입의 차이를 잘 이해하고 적절히 사용하는 것이 Swift 프로그래밍의 중요한 부분입니다!