구조체

jonghwan·2022년 9월 29일
2

멋쟁이사자처럼

목록 보기
16/28
post-thumbnail

구조체 개요

클래스처럼 구조체도 객체지향 프로그래밍의 기초를 형성하며 데이터와 기능을 재사용할 수 있는 객체로 캡슐화하는 방법을 제공한다.

구조체 선언은 클래스와 비슷하지만, class 키워드를사용하는 대신에 struct 키워드를 사용한다는 점이 다르다.

예를 들어 다음은 String 변수와 초기화(initializer), 메서드로 구성된 간단한 구조체를 선언하는 코드다.

struct SampleStruct {
  
  var name: String

  init(name: String) {
    self.name = name
  }
}

앞의 구조체 선언부와 동일한 클래스 선언부를 비교해보자.

class 키워드 대신에 struct 키워드를 사용했다는 것을 제외하면 두 개의 선언부는 동일하다.

struct SampleStruct {
  
  var name: String

  init(name: String) {
    self.name = name
  }
}

class SampleClass {
  
  var name: String

  init(name: String) {
    self.name = name
  }
}

각각의 인스턴스를 생성할 때도 동일한 문구를 사용한다.

struct SampleStruct {
  
  var name: String

  init(name: String) {
    self.name = name
  }
}

let myStruct = SampleStruct(name: "Mark")

class SampleClass {
  
  var name: String

  init(name: String) {
    self.name = name
  }
}

let myClass = SampleClass(name: "Mark")

클래스와 마찬가지로 구조체도 확장될 수 있으며, 프로토콜을 채택하거나 초기화를 가질 수 있다.

클래스와 구조체의 공통점이 많기 때문에 서로가 어떻게 다른지를 이해하는 것이 중요하다.

가장 큰 차이점에 대해 알아보기 전에 값 타입과 참조 타입에 대한 개념을 이해하는 게 먼저다.

값 타입 vs 참조 타입

겉으로 보기엔 구조체와 클래스는 비슷하지만, 구조체의 인스턴스와 클래스의 인스턴스가 복사되거나 메서드 또는 함수에 인자가 전달될 때 발생하는 동작의 큰 차이가 있다.

왜냐하면 구조체 인스턴스 타입은 값 타입(value type)이고, 클래스의 인스턴스의 타입은 참조 타입(reference type)이기 때문이다.

구조체 인스턴스가 복사되거나 메서드에 전달될 때 인스턴스의 실제 복사본이 생성되면서 원본 객체가 가지고 있던 모든 데이터를 그대로 복사해서 갖게 된다.

즉, 복사본은 원본 구조체 인스턴스와는 별개인 자신만의 데이터를 가진다는 의미다.

실제로 실행 중인 앱 내의 구조체 인스턴스에 대한 복사본이 여러 개 존재할 수 있으며, 각각의 복사본은 자신만의 데이터를 가질 수 있다는 말이다.

따라서 어떤 하나의 인스턴스를 변경해도 다른 복사본들에 영향을 미치지 않는다.

이와는 반대로, 클래스 인스턴스가 복사되거나 인자로 전달되면 해당 클래스 인스턴스가 있는 메모리의 위치에 대한 참조체가 만들어지거나 전달된다.

참조체를 변경하면 원본 인스턴스에도 동일한 작업이 수행된다.

다시 말해, 단 하나의 클래스 인스턴스가 있고 그 인스턴스를 가리키는 여러 개의 참조체가 존재하는 것이다.

참조체들 중 하나를 이용하여 인스턴스 데이터를 변경하면 모든 참조체의 데이터가 변경된다.

struct SampleStruct {
  
  var name: String

  init(name: String) {
    self.name = name
  }
}

let myStruct1 = SampleStruct(name: "Mark")
print(myStruct1.name)

이 코드를 실행하면 'Mark'라는 이름이 표시된다.

struct SampleStruct {
  
  var name: String

  init(name: String) {
    self.name = name
  }
}

let myStruct1 = SampleStruct(name: "Mark")
var myStruct2 = myStruct1
myStruct2.name = "David"

print(myStruct1.name)  // Mark
print(myStruct2.name)  // David

코드를 수정하여 myStruct1 인스턴스의 복사본을 만들고 name 프로퍼티를 변경한 다음에 각각의 인스턴스를 출력해보자.

myStruct2는 myStruct1의 복사본이기 때문에 아래와 같이 자신만의 데이터를 갖게 되므로 myStruct2의 name만 변경되었다.

class SampleClass {
  
  var name: String

  init(name: String) {
    self.name = name
  }
}

let myClass1 = SampleClass(name: "Mark")
var myClass2 = myClass1
myClass2.name = "David"

print(myClass1.name)  // David
print(myClass2.name)  // David

다음의 클래스 예제로 비교해보자.

이번에는 name 프로퍼티를 변경한 것이 myClass1과 myClass2 모두에 영향을 미쳤다.

왜냐하면 동일한 클래스 인스턴스에 대한 참조체들이기 때문이다.

지금까지 봤던 값 타입과 참조 타입에 대한 차이점 뿐만 아니라 구조체는 클래스에 있던 상속이나 하위클래스를 지원하지 않는다.

다시 말해, 하나의 구조체가 다른 구조체에 상속될 수 없다는 뜻이다.

클래스와는 다르게 구조체는 소멸자 메서드(deinit)을 포함할 수 없다.

마지막으로, 런타임에서 클래스 인스턴스의 유형을 식별할 수 있지만 구조체는 그렇지 않다.

구조체와 클래스는 언제 사용하는가

일반적으로 구조체가 클래스보다 효율적이고 멀티 스레드 코드를 사용하는 데 더 안정적이기 때문에 가능하다면 구조체를 권장한다.

하지만, 상속이 필요하거나 데이터가 캡슐화된 하나의 인스턴스가 필요할 때는 클래스를 사용해야 한다.

또는 인스턴스가 소멸될 때 리소스를 확보하기 위한 작업이 필요할 때도 클래스를 사용해야 한다.

요약

구조체와 클래스 모두는 프로퍼티를 정의하고, 값을 저장하며, 메서드를 정의할 수 있는 개체 생성 메커니즘을 제공한다.

두 개의 메커니즘이 서로 비슷해 보이지만, 구조체 인스턴스와 클래스 인스턴스가 복사되거나 메서드에 전달될 때는 중요한 차이점을 보인다.

구조체 인스턴스가 복사되거나 메서드로 전달되면 완전히 새로운 복사본이 생성되며, 복사본 자신의 데이터를 갖게 된다.

클래스만 갖는 고유한 기능은 상속과 소멸자를 지원한다는 것이며, 런타임에서 클래스 타입을 식별할 수 있다는 것이다.

클래스만의 기능이 필요하지 않다면 일반적으로는 클래스 대신에 구조체를 사용해야 한다.

0개의 댓글