[Swift] Struct와 Class (feat. 값타입과 참조타입)

a.very·2022년 5월 27일
1

Swift

목록 보기
1/2

☃️ Previously

지난포스팅을 보고 오시면 훨씬 좋아요
[iOS] 메모리 구조 (Stack, Heap, Data, Code)

☃️ Struct

정의

Struct (구조체)는 struct 키워드로 다음과 같이 정의한다.

struct someStruct {
  // properties and methods
  var name: String
  var score: Int
}

구조체 인스턴스 생성 및 초기화

구조체를 정의하고 인스턴스를 초기화 해보자!

구조체는 기본적으로 생성되는 멤버 와이즈 이니셜라이저 (구조체는 사용자화 초기화 구문을 정의하지 않은 경우 자동적으로 멤버별 초기화 구문을 받음, 기본값이 지정되어 있지 않더라도)를 이용한다. (클래스는 멤버별 초기화를 받지 않는다)

var team1 = someStruct(name: "LiverpoolFC", score: 0)

하지만 프로퍼티에 기본값이 있는 경우 아래와 같이 생성해도 무방하다.

var team1 = someStruct()

프로퍼티 접근

인스턴스가 생성이 되고 프로퍼티 값에 접근 하고 싶을 때는 dot syntax (.) 를 사용

var team1 = someStruct(name: "LiverpoolFC", score: 0)
team1.name = "LFC"
team1.score = 97
print(team1.name) // LFC
print(team1.score) // 97

☃️ Class

정의

Class(클래스)는 class 키워드로 다음과 같이 정의한다.

class someClass {
  // properties and methods
  var name = ""
  var score = 0
}

클래스 인스턴스 생성 및 초기화

이번에는 클래스를 정의하고 인스턴스를 초기화 해보자!

구조체와 달리 클래스는 멤버와이즈이니셜라이저 사용기 불가하기에 기본적인 이니셜라이저로 초기화를 하게된다. (자세한 내용은 공식문서의 초기화 파트 에서 확인 가능하다)

위의 정의코드에서 이미 class는 기본값이 지정되어 있으므로 따로 초기값을 전달할 필요가 없다.

var team2 = someClass()

프로퍼티 접근

구조체와 마찬가지로 클래스도 인스턴스가 생성이 되고 초기화되고 프로퍼티 값에 접근 하고 싶을 때는 dot syntax (.) 를 사용하면 된다.

var team2 = someClass()
team2.name = "Manchester City"
team2.score = 92
print(team2.name) // Manchester City
print(team2.score) // 92

☃️ Struct vs Class

구조체와 클래스는 굉장히 비슷해보이고 실제로도 유사한 부분이 많다.
우선 공식문서에서 설명하는 공통점은 다음과 같다.

  • 값을 저장하는 프로퍼티 정의한다
  • 기능 제공을 위한 메서드를 정의한다
  • 서브 스크립트 구문을 사용하여 값에 접근을 제공하는 서브 스크립트 정의한다
  • 초기화 상태를 설정하기 위한 초기화 정의한다
  • 기본 구현을 넘어 기능적 확장을 위한 확장한다
  • 특정 종류의 표준 기능을 제공하는 프로토콜 준수한다

그리고 다음과 같은 차이점이 있다.

  • 클래스는 상속이 가능하다.
  • 클래스는 타입 캐스팅을 사용하여 런타임에 클래스 인스턴스의 타입을 확인하고 해석할 수 있다.
  • 클래스는 초기화 해제 구문 (Deinitalizers)을 사용할 수 있다.
  • 클래스는 참조 카운팅은 하나 이상의 클래스 인스턴스 참조를 허락한다.

그리고 구조체와 클래스의 가장 큰 차이점은 구조체는 값 타입이고 클래스는 참조 타입이라는 것이다! 아래서 자세히 살펴보겠다.


⭐️ 값 타입과 참조 타입

값타입 (value type) : Structure, Enum, Tuple
참조타입(reference type) : Class, Closure

값타입과 참조타입은 메모리에 저장되는 방식에서 차이가 있다.
메모리에 대한 자세한 내용은 지난 포스팅 - [iOS] 메모리 구조 (Stack, Heap, Data, Code)에서 자세히 확인할 수 있다.

  • 값타입인 struct는 stack에 실제 데이터가 저장
  • 참조타입인 class 는 heap에 실제 데이터가 저장되고, stack에는 heap 영역의 메모리주소가 저장

따라서, 값타입은 값(실제 데이터)이 복사되어 전달되고, 참조타입은 값을 복사 하지 않고 참조(heap 영역의 주소)가 전달된다.


다음의 예시와 그림을 보면 조금 쉽게 이해 될 것이다.

/* Value Type (값타입) */
struct someStruct {
  // properties and methods
  var name = "Liverpool"
  var score = 100
}

/* Reference Type (참조타입) */
class someClass {
  // properties and methods
  var name = "Manchester City"
  var score = 92
}


var team1 = someStruct()
var team11 = team1 // ✅ 값타입이므로 별개의 새로운 인스턴스가 값을 복사하여 생성된다.

var team2 = someClass()
var team22 = team2 // ✅ 참조타입이므로 기존 인스턴스가 위치한 heap 주소(참조)만을 전달하고 실제 데이터는 heap 에 저장되어 있다

값 타입인 struct는 새로운 인스턴스가 값을 복사하여 stack 에 새로운 공간을 생성하므로 아래 코드에서 team1 과 team11의 프로퍼티는 각기 다른 값을 가진다.


/* Value Type (값타입) */
struct someStruct {
  // properties and methods
  var name = "Liverpool"
  var score = 100
}

var team1 = someStruct()
var team11 = team1
team11.name = "hello"
print(team1.name) // Liverpool
print(team11.name) // hello

반면, 참조타입인 class는 데이터를 전달할때, 인스턴스의 참조를 복사하여 전달하므로 아래와 같이 값이 변동된다.

/* Reference Type (참조타입) */
class someClass {
  // properties and methods
  var name = "Manchester City"
  var score = 92
}

var team2 = someClass()
var team22 = team2
team22.name = "hello"
print(team2.name) // hello
print(team22.name) // hello

let 키워드

let으로 선언된 인스턴스는, 인스턴스에 할당된 stack 영역의 메모리 공간을 변경하지 못하게 된다.
따라서, 값타입의 let으로 선언된 인스턴스는 stack 영역에 실제 데이터를 저장하므로 인스턴스 속성 수정에 있어 다음과 같은 제약이 발생한다.

하지만, 참조타입에서는 stack 영역에 실제 데이터가 위치한 heap 영역의 주소를 저장하기에 참조 타입 인스턴스들이 가르키는 대상을 수정하지 못할 뿐, 실제 데이터는 heap에 위치하기에 속성을 변경할 수 있다.

식별연산자 (== vs ===)

클래스는 인스턴스끼리 참조가 같은지 확인할때는 식별연산자 (Identity Operators)를 사용하면 된다. 클래스는 참조타입이므로 클래스를 가르키는 변수가 다수가 될 수 있으므로 === 가 필요하다.

  • == : 두 상수나 변수의 value가 같은지 비교
  • === : 두 상수나 변수가 같은 인스턴스를 참조하고 있는 경우 참
  • !== : 두 상수나 변수가 다른 인스턴스를 참조하고 있는 경우 참

메모리 관점에서는 다음과 같이 설명할 수 있다.

  • == : stack 영역의 값을 비교
  • === : heap 영역의 값을 비교
let c1 = someClass()
let c2 = someClass()
let c3 = c1

print(c1 === c2) // false
print(c1 === c3) // true

구조체 or 클래스

애플공식문서에서는 다음의 경우 구조체를 사용하는게 좋다고 이야기 한다.

  • 연관된 간단한 값의 집합을 캡슐화하는 것만이 목적일 때
  • 캡슐화한 값을 참조하는 것보다 복사하는 것이 합당할때
  • 구조체에 저장된 프로퍼티가 값 타입이며, 참조하는 것보다 복사하는것이 합당할때
  • 다른 타입으로부터 상속받거나, 자신을 상속할 필요가 없을때

☃️ References

profile
🚚chanhee-jeong.tistory.com 🚀 github.com/chaneeii/iOS-Study-Log

0개의 댓글