본문 바로가기
iOS/Swift

[iOS/Swift] Swift에서의 구조체(Struct), 값 타입과 참조 타입의 비교

by iosdevlime 2023. 2. 22.

이번 포스팅에서는

 

앞서 다뤄온 '클래스'와 비슷해 보이지만, 큰 차이점이 존재하는

구조체(Struct)에 대한 내용을 살펴볼까 합니다.

 

어떻게 선언하고, 사용하는지와 더불어

클래스와의 차이점에 대해 알아보도록 하겠습니다. 

 

 

 


 

 

 

Swift의 구조체

클래스와 유사하나, 값(Value) 타입의 캡슐화를 제공하는 틀

 

클래스와 동일하게,

구조체 또한 [객체지향 프로그래밍]의 기초를 형성합니다.

 

선언된 데이터와 기능(프로퍼티, 메서드)을 재 사용할 수 있는 객체이며,

 

추상화(인스턴스로의 실체화를 위해 공통된 속성과 행위를 정의)를 목표로 

관련된 데이터를 캡슐화(결합, 집합화)하는 방법을 제공합니다.

 

 

 

선언 및 활용방법

  • 클래스와는 달리, class 키워드 대신 struct 키워드를 사용하여 선언합니다.
    • 클래스와 동일하게 프로퍼티와 메서드를 캡슐화 할 수 있습니다.
    • 또한, 인스턴스(객체)를 생성하여 활용할 수 있습니다.
  • 다음 예시코드는 간단한 구조체를 선언하는 방식입니다.
    • Human이란 이름의 구조체입니다.
    • name이란 String 타입의 프로퍼티와 초기화(initializer) 메서드로 구성됩니다.
struct Human {
    var name: String // 프로퍼티
    
    // initializer 메서드
    init(name: String) {
        self.name = name
    }
}

 

  • 다음 예시코드는 위 구조체를 활용, 인스턴스를 생성하는 방식입니다.
    • 역시나, 클래스에서 인스턴스를 생성하는 구문과 완벽하게 동일한 형식입니다.
var lime: Human1 = Human1(name: "Lime")
lime.name // "Lime"

 

 

 

 

구조체의 확장 및 상속

  • 클래스와는 달리, 구조체는 상속을 할 수 없습니다!
    • 하지만, Swift에서는 프로토콜(Protocol)을 활용한 구조체의 확장을 권장합니다.
    • 이와 같은 [프로토콜 지향 패러다임]을 토대로 확장/상속/조합 기능을 활용합니다.
  • 다음 예시코드는 위 Human 구조체가 'Animals 프로토콜'을 상속받는 방식입니다. 
    • 구조체 뿐만 아닌, 클래스와 Enum 또한 프로토콜을 채택할 수 있습니다.
    • thread-safe 하며, OOP(객체지향 프로그래밍)보다 확장 측면에서 유연합니다.
// 프로토콜(protocol) 선언
protocol Animals {
    
    // 1. 각 프로퍼티에 gettable/settable(읽기/쓰기)인지 반드시 명시해야 함
    // 2. 항상 variable로 선언되어야 함
    var name: String { get }
}

// (마치 클래스가 상속받는것과 동일하게) :(콜론) 뒤에 프로토콜 이름을 작성
struct Human: Animals {
    var name: String
}

 

(프로토콜의 경우, 다음 포스팅에서 더욱 자세히 다루도록 하겠습니다)

 

 


 

 

클래스 vs 구조체

인스턴스가 복사되거나 / 특정 함수의 매개변수로 전달될 시 동작의 차이가 존재

 

앞서 다룬 구조체가 클래스와 다른점을 정리하자면,

  • class 키워드가 아닌, struct 키워드를 사용한다!
  • 상속을 할 수 없으며, 프로토콜을 채택함으로서 기능을 확장한다!

 

하지만,

중요한 차이점이 존재합니다.

 

 

 

(Value)타입과 참조(Reference) 타입

  • 값과 참조타입은 클래스와 구조체의 '인스턴스' 생성 과정에서 차이가 발생합니다.
    • 구조체는 ➟ 값 타입이며, 생성되는 인스턴스는 일종의 '복사본'이 됩니다.
    • 클래스는 ➟ 참조 타입이며, 인스턴스가 저장된 메모리의 위치에 대한 '참조체' 됩니다.
  • 아래 예시 코드는 Animals란 이름의 구조체 입니다.
    • 인스턴스 myDog(①) 를 생성하고, name 초기값으로 "Van"을 설정합니다.
      • myDog(①)를 복사하는 yourDog(②) 인스턴스의 name 초기값을 "Coco"로 설정합니다.
      • yourDog(②) myDog(①)는 별개의 객체이므로, 각자만의 '값'을 가지게 됩니다.
    • 따라서, 프로퍼티 name의 값은 각각 다른 "Van"과 "Coco"가 출력됩니다.
// 구조체 (Animals) 
struct Animals {
    var name: String

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

// 첫 번째 인스턴스 'myDog(①)'
let myDog: Animals = Animals(name: "Van")
print(myDog.name) // "Van"

// myDog(①)의 복사본, yourDog(②)
var yourDog = myDog
yourDog.name = "Coco" // 'Coco'로 이름을 변경!

// 아래와 같이 각각 다르게 출력되며, 자신만의 인스턴스 데이터를 갖게 됨
print(myDog.name) // "Van"
print(yourDog.name) // "Coco"

 

기존 myDog에서 복사된 yourDog 구조체는 각각 자신만의 데이터를 갖게된다.
기존 myDog에서 복사된 yourDog 구조체는 각각 자신만의 데이터를 갖게된다.

 

 


 

 

  • 아래 예시 코드는 Animals란 이름의 클래스 입니다.
    • 인스턴스 myDog(①) 를 생성하고, name 초기값으로 "Van"을 설정합니다.
      • myDog(①)를 복사하는 yourDog(②) 인스턴스의 name 초기값을 "Coco"로 설정합니다.
      • 참조체의 데이터(name)를 변경하게 되면, 원본인 클래스 인스턴스도 영향을 미칩니다.
    • 따라서, 프로퍼티 name의 값은 동일한 "Coco"가 출력됩니다.
      • 이유는 즉, 동일한  '클래스 인스턴스'에서 파생되어 나온 참조체 이기 때문입니다.
class Animals {
    var name: String

    init(name: String) {
        self.name = name
    }
}
// 첫 번째 인스턴스 'myDog(①)'
let myDog: Animals = Animals(name: "Van")
print(myDog.name) // "Van"

// myDog(①)의 복사본, yourDog(②)
var yourDog = myDog
yourDog.name = "Coco"

print(myDog.name) // "Coco" -> name 프로퍼티의 값을 변경했더니, 원본 클래스 인스턴스의 프로퍼티 값도 변경!
print(yourDog.name) // "Coco"

 

기존 myDog 인스턴스와 참조된 yourDog 모두 데이터가 변경될 경우 동일하게 영향을 미치게 된다!
기존 myDog 인스턴스와 참조된 yourDog 모두 데이터가 변경될 경우 동일하게 영향을 미치게 된다!

 


 

구조체의 전반적인 내용을

기존에 다뤘던 유사한 형태인 클래스와 함께 살펴보았습니다.

 

미쳐 다루지 못한 프로토콜(Protocol)에 대한 포스팅으로 돌아오겠습니다.

댓글