참조 타입과 값 타입 (Reference Types & Value Types)

구조체와 열거형은 값 타입
Structures and Enumerations Are Value Types

값 타입은 변수 또는 상수에 할당되거나 함수에 전달 될 때 값이 복사됩니다.

실제로 이전 장에서 값 타입을 광범위하게 사용했습니다. 실제로 Swift의 모든 기본 유형 (integers, floating-point numbers, Booleans, strings, arrays and dictionaries)은 값 타입이며 구조체로 구현되어있습니다.

모든 구조체와 열거형은 Swift에서 값 타입입니다. 즉, 사용자가 만든 모든 구조체 및 열거형은 값 타입이기 때문에 코드에서 인스턴스와 프로퍼티로 전달 될 때 항상 복사됩니다.

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

이 예제에서는 hd라는 상수를 선언하고 너비 1920, 높이 1080로 초기화 된 Resolution 인스턴스로 설정했습니다.

그런 다음 cinema라는 변수를 선언하고 hd의 현재 값으로 설정했습니다. Resolution은 구조체이기 때문에 기존 인스턴스의 복사본이 만들어지고 이 새 복사본이 cinema에 할당됩니다. 이제 hd와 cinema의 너비와 높이는 동일하지만 완전히 다른 두 인스턴스가 된 것 입니다.

다음으로, cinema의 너비 속성을 폭 2,048, 높이 1080으로 수정하였습니다.

cinema.width = 2048

cinema의 width 프로퍼티를 확인하면 실제로 2048로 변경되었음을 알 수 있습니다.

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

그러나 원래 hd 인스턴스의 width 프로퍼티에는 여전히 1920이라는 이전의 값인것을 확인 할 수 있습니다.

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

cinema에 hd의 현재 값을 대입하면 hd에 저장된 값이 cinema 인스턴스에 복사됩니다. 최종 결과는 동일한 숫자 값을 포함하는 완전히 별개의 두 인스턴스가 되는 것입니다. 그러나 별도의 인스턴스이기 때문에 아래 그림과 같이 cinema의 width를 2048로 설정해도 hd에 저장된 width에는 영향을주지 않습니다.

열거 형에도 동일한 동작이 적용됩니다.

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"

rememberedDirection에 currentDirection 값이 할당되면 실제로 해당 값의 복사본으로 설정됩니다. 이후 currentDirection 값을 변경해도 rememberedDirection에 저장된 원래 값에는 영향을주지 않습니다.

클래스는 참조 타입
Classes Are Reference Types

값 타입과 달리 참조 타입은 변수 또는 상수에 할당되거나 함수에 전달 될 때 복사되지 않습니다. 사본 대신 동일한 기존 인스턴스에 대한 참조가 사용됩니다.

클로져(Closures), 함수(Functions), 클래스(Classes)는 참조 타입 입니다.

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

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

이 예제에서는 tenEighty라는 상수를 선언하고 VideoMode 클래스 인스턴스를 참조하도록 설정했습니다.
VideoMode의 resolution은 hd의 복사본이 할당됬고, interlaced는 true, 이름은 “1080i”, frameRate은 25.0 으로 설정되었습니다.

다음으로 tenEighty가 alsoTenEighty라는 새로운 상수에 할당되고 alsoTenEighty의 frameRate가 수정되었습니다.

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

클래스는 참조 타입이므로 tenEighty와 alsoTenEighty는 실제로 둘 다 동일한 VideoMode 인스턴스를 참조합니다. 실제로는 아래 그림과 같이 동일한 단일 인스턴스에 대한 두 개의 다른 이름 일뿐입니다.

tenEighty의 frameRate 프로퍼티를 확인하면 기본 VideoMode 인스턴스에서 새로 설정한 frameRate 30.0을 올바르게 나타내고 있음을 알수 있습니다.

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

이 예제는 참조 타입이 왜 코드분석(추론)이 어려울 수 있는지를 보여줍니다. tenEighty와 alsoTenEighty가 프로그램 코드상으로 멀리 떨어져 있다면 VideoMode 인스턴스가 변경되는 모든 방법을 찾기가 어려울 수 것입니다.
같은 인스턴스를 참조하고 있기 때문에 tenEighty를 사용(변경)할 때마다 alsoTenEighty를 사용하는 코드에 대해서도 생각해야하며 그 반대의 경우도 마찬가지입니다. 반대로 값 타입은 동일한 값과 상호 작용하는 모든 코드가 소스 파일에서 서로 가깝기 때문에 추론하기가 더 쉽습니다.

tenEighty 및 alsoTenEighty는 변수가 아닌 상수로 선언됩니다. 그러나 tenEighty 및 alsoTenEighty 상수 자체의 값은 실제로 변경되지 않으므로 tenEighty.frameRate 및 alsoTenEighty.frameRate를 변경할 수 있습니다. tenEighty 및 alsoTenEighty 자체는 VideoMode 인스턴스를 “저장”하지 않습니다. 대신 둘 다 같은 VideoMode 인스턴스를 참조합니다. 변경되는 것은 해당 VideoMode에 대한 상수 참조의 값이 아니라 기본 VideoMode의 frameRate 프로퍼티입니다.

식별연사자 (Identity Operators)

클래스는 참조 타입이므로 여러 상수와 변수가 다른곳에서 클래스의 동일한 단일 인스턴스를 참조 할 수 있습니다. (구조체 및 열거형은 상수 또는 변수에 할당되거나 함수에 전달 될 때 항상 복사되므로 동일하지 않습니다.)

두 개의 상수 또는 변수가 정확히 동일한 클래스 인스턴스를 참조하는지 확인하는 것이 유용 할 수 있습니다. 이를 활성화하기 위해 Swift는 두 가지 식별연사자 를 제공합니다.

  • (===)와 동일
  • (! ==)와 동일하지 않음
if tenEighty === alsoTenEighty {
    print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// Prints "tenEighty and alsoTenEighty refer to the same VideoMode instance."

식별 연산자(===)는 비교 연산자(==)와 같지 않습니다. 식별 연산자는 참조를 비교하는 것이고, 비교 연산자는 값을 비교합니다.

포인터 (Pointers)
C, C++ 혹은 Objective-C를 사용해 보신 분이라면 이 참조라는 것이 포인터라고 생각하실 수 있습니다. Swift에서 상수나 변수가 특정 타입의 인스턴스를 참조하고 있다는 것은 위 포인터와 유사합니다. 하지만 표현에는 다른 점이 있는데요 C, C++, Objective-C에서 포인터는 실제 메모리를 직접 가르키고 있고 키워드로 표시하지만 Swift는 참조를 가르키기 위해 사용하지 않고 대신 다른 상수와 변수처럼 정의해 사용합니다.

에 발행했습니다
SWIFT(으)로 분류되었습니다

aaron님이 작성

아무것도 안해도 시간은 흐른다.

댓글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다