클래스 vs 구조체

클래스와 구조체의 차이점은 무엇이 있을까요? Swift에서는 두 타입간에 매우 비슷합니다. 자 그럼 두 타입을 비교분석해보도록 합시다.

개요

먼저 클래스와 구조체의 공통점에 대해 살펴보겠습니다.

  • 값을 저장하는 속성을 정의 할 수 있으며, 함수 또한 정의할 수 있습니다.
  • 값에 대한 엑세스를 첨자구문( subscript syntax) 사용하여 정의할 수 있습니다.
  • 초기 상태를 설정하기 위해 초기화 프로그램 init() 을 정의할 수 있습니다.
  • 확장(extensions)을 사용할 수 있습니다.
  • 프로토콜을 준수할 수 있습니다.

클래스는 구조체에 없는 몇 가지 기능을 추가로 지원합니다.

  • 클래스는 다른 클래스에서 상속을 받을 수 있습니다. 예를 들어, UIViewController를 상속받고 프로젝트에서 공통으로 설정하는 BaseViewController라는 하위클래스를 만들수 있습니다.
  • 클래스는 초기화 취소(DeInitialized)를 할 수 있습니다. 즉, 클래스가 소멸되기 전에 deinit 메서드를 호출 하며, 제어가 가능 합니다.
  • 클래스는 참조유형(Reference Type)이며, 구조체는 값 (Value Type)입니다.

여기서 마지막으로 언급한 내용은 두 유형간에 가장 중요한 차이입니다. 따라서, 참조 유형과 값 유형에 대해 조금 더 알아보도록 합니다.

  • 값 유형: 값 유형의 복사방식은 깊은복사(Deep Copy)방식을 사용하여 각 인스턴스는 데이터의 고유 사본을 유지합니다. 한 인스턴스에서 데이터를 변경하면, 다른 인스턴스의 데이터는 변경되지 않습니다.
  • 참조 유형: 참조 유형을 복사방식은 얕은 복사(Shallow copy)방식을 사용하여, 각 인스턴스가 데이터를 공유합니다. 즉, 한곳에서 데이터가 변경되면, 다른 인스턴스의 데이터 또한 같이 변경이 됩니다.

예제를 살펴 봅시다. 예제에는 밥, 케빈, 스튜어트라는 3명의 친구들이 등장합니다. 전화번호라는 데이터를 공유할 때를 비유해서 들어보도록 하겠습니다.

  • 참조 유형: 스튜어트가 핸드폰을 구입합니다. 스튜어트는 자신의 전화번호를 밥에게 알려줍니다. 그리고 밥은 케빈에게 스튜어트의 전화번호를 알려줍니다. 스튜어트는 자신의 전화번호가 틀렸다는걸 알고 케빈에게 알려줍니다. 하지만 밥에게는 알려주지 않습니다. 따라서 스튜어트와 케빈은 둘 간의 전화번호는 동일하지만, 밥의 전화번호는 달라지게 됩니다.
  • 값 유형: 세명의 친구들은 페이스북을 가입하여, 서로 페이스북 친구를 맺습니다. 어느날 스튜어트가 핸드폰을 구입하여, 새로운 전화번호를 페이스북 정보에 변경하게됩니다. 이때 케빈, 밥, 스튜어트는 하나의 데이터로 공유하게되어 세명의 친구들의 스튜어트의 전화번호는 하나로 일치하게 됩니다.

정리하자면, Swift에서 구조체는 값 유형이지만 클래스는 참조 유형입니다. 즉, 구조체를 복사하면 두 개의 고유의 데이터로 복사본이 생성됩니다. 클래스를 복사하면 데이터의 한 인스턴스에 대한 두 개의 참조로 끝나게 됩니다.


구조체는 언제 사용해야 하죠?

구조체를 기본적으로 사용하는것이 좋습니다. 구조체는 다음 시나리오에서 유용합니다.

  • 간단한 데이터 유형에는 구조체를 사용하십시오.
  • 데이터베이스가 같은 코드에서 다른 객체들에게 전달 할때 사용하는 제공(Provider) 함수(NewItem, GetItem 등등…) 사용할때에 사용하십시오. 해당 함수들의 정의는 매우 뚜렸고, 종종 객체들간의 복잡한 관계를 사용 사용할 필요가 없기 때문에 구조체를 사용하는것이 더 간단합니다.
  • GCP를 사용하여 스레드로 데이터베이스 연결과 네트워크를 사용하는 구조에서 구조체가 더 안전합니다. 경쟁상태(Race Condition) 또는 교착상태(Deadlock)와 같은 위험을 감수하지 않고, 하나의 스레드에서 다른 스레드로 복사 할 수 있습니다. 이경우, 클래스는 쓰레드에 안전하지 않으며, 데이터의 값이 외부적 요인으로 인해 변경이 일어날 수 있기 때문에, 더 많은 리소스가 필요해집니다.
  • 구조체의 속성이 대부분 값 유형(String, Int…)을 사용한다면 클래스 대신 구조체로 랩핑하여 사용하는것이 좋습니다. 구조체 유형 내부에서 클래스를 사용하는 경우에는 주의해야 합니다. 클래스는 참조 유형이므로 구조체가 공유 클래스 인스턴스를 참조관련 유무가 모호한 문제가 발생하기 때문이죠.

구조체를 사용하면 추가적인 이점이 있습니다. 코드에서 데이터 변경 사항을 쉽게 유추할 수 있습니다. 구조체 유형을 사용한 경우 코드의 다른 부분이 객체에 대한 참조를 보유 할 수 없다는걸 확신할 수 있다는 것이죠. 명시적으로 코딩하지 않으면, 구조체의 코드 다른부분을 변경할 수 없습니다.

위에서 언급한 예와 비교해보시길 바랍니다. 스튜어트의 전화번호가 Note라는 구조체에 적혀있다고 상상해봅시다. 밥이 케빈에게 Note라는 구조체를 전할 하면 객체가 복사가 되어 두개의 고유 한 인스턴스가 만들어집니다. 스튜어트가 전화번호를 변경하더라도, 밥과 캐빈의 전화번호는 변경이 되질 않습니다. 이것은 Note에 대한 구조체에 대해 더 쉽게 추론 할 수 있음을 의미합니다. 왜냐하면 밥 또는 케빈이 알지 못하면 케빈의 전화번호를 변경할 수가 없다는 것을 확신 할 수 있기 때문입니다.


클래스는 언제 사용해야 하죠?

클래스의 특정 기능이 필요한 경우 클래스를 사용하는것이 좋습니다. 위에 언급했듯이 구조체에는 없고 클래스의 만의 보유한 몇 가지 특성이 있습니다.

  • 클래스는 구조체로 할 수 없는 다른 클래스에서 상속받을 수 있습니다. 클래스를 사용하여 서브 클래스 또는 프로토콜 구현체를 작성할 수 있습니다. 반대로 구조체에서는 프로토콜 구현체만 작성할 수 있습니다.
  • 클래스는 초기화되지 않을 수 있습니다. 즉 deinit 함수를 구현할 수 있으며, 동일한 클래스에 대한 하나 이상의 참조유형을 만들 수 있습니다.
  • 클래스는 참조 유형이기 때문에 ID(Memory Address)라는 값을 제공받게 됩니다. ID 연산자 ===를 사용하면 두 개의 참조(변수, 상수, 프로퍼티 등)가 동일한 객체를 참조하는지 알수 있습니다.

Swift와 Objective-C간의 상호간의 참조해야되는 경우, 클래스를 사용해야합니다. 예를 들어 @objec 및 dynamic Object 영역 데이터 모델 (Ream Model 등) 을 사용할 경우, 클래스로 사용해야 합니다.

슈퍼 클래스와 서브 클래스간의 연결을 명확하게 해야 할 경우, 클래스를 사용해야합니다. 해당 특징은 객체지향 프로그래밍(OOP)적으로 설계 구현에 필요한 경우 사용성이 명확해집니다.

  • MyVIewController 는 UIViewController에 상속받았다
  • MyTableViewController -> InfinityTableViewController -> UIViewController 순서로 상속관계가 되어있다.
  • Car, Bike는 Vehicle를 상속받아 그들 모두의 동일한 기본 그룹를 사용하기 때문에 바퀴의 개수, 스피드등을 유추할 수 있습니다.

마지막 두개의 예제에서는 위험이 있습니다. 상속받은 클래스로 끝날 수 있습니다. 모두 하위 클래스의 다른 기능을 Override 또는 Overload으로 기능 변경으로 본래 의미를 변질 시킬 수 있습니다. 즉 일관성이 사라질 수 있다는 것이죠. 또한 슈퍼클래스는 하위 클래스에서 필요로 하지 않는 특정 기능을 명시할 필요가 없는 경우의 케이스등을 해결하는 방법의 자세한 내용은 프로토콜 기반 프로그래밍(POP)을 참고 하시길 바랍니다.

ID 및 참조에 대해 논의한 내용을 토대로 다음 시나리오에서 클래스를 사용하는 것이 가장 좋습니다.

  • CocoaTouch를 슈퍼 클래스로 사용하는 경우, 예를 들어 UIView, UIViewController등 해당 서브뷰로 만들어야하기 때문에 해당 시나리오는 강제로 사용할 수 밖에 없습니다.
  • 인스턴스 수명이 부수 효과가 필요한 경우, 예를 들어 데이터베이스에 연결 또는 디스크에 있는 파일에 대한 참조 사본을 두개 만드는 경우는 의미가 없기 떄문에 둘다 동일한 데이터를 참조하고 해당 데이터를 공유해야합니다.
  • 인스턴스가 외부 상태를 위한 헬퍼 함수로 작성할 경우, 예를 들어 CGContext 등 참조를 통한 부수효과를 발생하는 경우, 헬퍼 또는 래퍼 클래스가 필요한 경우가 있습니다. 이 경우 클래스는 정보 전달을 위한 콘딧 또는 파이프라인 일 뿐이며, 사본을 만드는 것이 의미가 없습니다.

요약하면 상속, ID, Objective-C와의 상호 결합성 및 값 복사가 의미가 없는 시나리오의 경우와 같이 클래스만 제공하는 기능이 필요한 경우 클래스를 사용하는것이 현명합니다.

0 Shares:
You May Also Like
Read More

Swift 메모리 관리 – 클로저 편

스위프트의 캡쳐 목록은 클로저 코드 내부에 폐쇄된 공간의 매개 변수 목록 앞에 표시되며, 메모리 참조환경에서 강한참조(strong), 약한참조(weak), 미소유(unowned) 참조 메모리값을 캡쳐합니다.
Read More

Swift 메모리 관리 – 클래스편

해당 포스트에서는 Apple의 메모리 관리에 대해 설명합니다. 대부분 ARC를 사용하여 자동으로 메모리를 처리한다고 하더라도 여전히 몇 가지 함정이 있습니다. 객체간의 설명하는 올바른 참조 유형을 선택하면 메모리 누수를 막을 수 있습니다.