[Swift] 구조체와 클래스

728x90

구조체와 클래스에 대해 알아 보겠습니다.

구조체와 클래스


구조체와 클래스는 프로그래머가 데이터를 용도에 맞게 묶어 표현하고자 할 때 용이하고, 프로퍼티와 메소드를 사용하여 구조화된 데이터와 기능을 가질 수 있어 하나의 새로운 사용자정의 데이터 타입을 만들어 주는 것입니다.

스위프트에선 구조체와 클래스의 모습과 문법이 거의 흡사 합니다. 하지만 구조체의 인스턴스는 값 타입이고, 클래스이 인스턴스는 참조 타입이라는 차이점이 있습니다.
또 중요한 점은 데이터 타입과 열거형은 모두 값 타입입니다.

1. 구조체

먼저 구조체에 대해 알아 보겠습니다.

1.1 구조체 정의

구조체는 strcut키워드로 정의 한다.

struct 구조체 이름 {
  프로퍼티와 메서드
}

1.2 구조체 인스터스 생성 및 초기화

구조체의 인스턴스를 생성하고 초기화하고자 할 때에는 기본적으로 생성되는 멤버와이즈 이니셜라이저를 사용합니다.
인스턴스가 생성되고 초기화된 후 프로퍼티 값에 접근하고 싶다면 마침표(.)를 사용하면 됩니다.
구조체를 상수 let으로 선언하면 인스턴스 내부의 프로퍼티 값을 변경할 수 없고, 변수 var로 선언하면 내부의 프로퍼티가 var로 선언된 경우에 값을 변경해줄 수 있습니다.

struct BasicInfo {
  var name: String
  var age: Int
}

var testInfo: BasicInfo = BasicInfo(name: "김승진", age: 28)
testInfo.name = "Kim seungjin" // 변경 가능
testInfo.age = 35           // 변경 가능

let sjInfo: BasicInfo = BasicInfo(name: "sj", age: 30)
sjInfo.name = "hahaha"  // 변경 불가!

2. 클래스

스위프트의 클래스는 부모의 상속 없이도 단독으로 정의가 가능합니다.

2.1 클래스 정의

class 클래스 이름 : 부모클래스 이름{
	프로퍼티와 메서드들
}

클래스를 정의 하는 방법은 구조체와 흡사합니다. 하지만, 클래스는 상속을 받을 수 있기 때문에 상속받을 때에는 클래스 이름 뒤에 콜론(:)을 써주고 부모클래스 이름을 명시합니다.

2.2 클래스 인스턴스의 생성과 초기화

인스턴스가 생성되고 초기화된 후 프로퍼티 값에 접근하고싶다면 마침표(.)를 사용하면 됩니다.
구조체와는 다르게 클래스의 인스턴스는 참조 타입이므로 클래스의 인스턴스를 상수 let으로 선언해도 내부 프로퍼티 값을 변경할 수 있습니다.

class Person {
  var name : String = ""
  var age: Int = 0
}

var ksj: Person = Person()
ksj.name = "김승진"
ksj.age = 28

let kkk: Person = Person()
kkk.name = "가가가"
kkk.age = 40

2.3 클래스 인스턴스의 소멸

클래스의 인스턴스는 참조 타입이므로 더는 참조할 필요가 없을 때 메모리에서 해제됩나다.
메모리에서 해제가 되기 직전에 deinit라는 메서드가 호출되고 클래스 내부에 deinit 메서드는 디이니셜라이즈(Deinitializer) 라고 부릅니다.

class Person {
  var name : String = ""
  var age: Int = 0

  deinit {
    print(“Person 클래스의 인스턴스가 소멸되었습니다.”)
  }
}

var ksj: Person? = Person()
ksj = nil

deinit 메서드는 인스턴스가 메모리에서 해제되기 직전에 처리할 코드를 넣어줍니다.
예를 들어 인스턴스 소멸전에 데이터를 저장한다거나 다른 객체에 인스턴스 소멸을 알려야 할때는 특히 deinit메서드를 구현해야 합니다.

3. 구조체와 클래스의 차이

구조체와 클래스의 공통점과 차이점을 알아보겠습니다.

 

공통점

  • 프로퍼티를 정의할 수 있다.
  • 메서드를 정의 할 수 있다.
  • 초기화될 때의 상태를 지정하기 위해 이니셜라이저를 정의할 수 있다.
  • 익스텐션을 통해 확장할 수 있습니 다.
  • 프로토콜을 준수할 수 있다.

차이점

  • 구조체는 상속할 수 없다.
  • 타입캐스팅은 클래스의 인스턴스에만 허용된다.
  • Reference Counting은 클래스의 인스턴스에만 적용된다.

3.1 값 타입과 참조 타입

구조체는 값 타입이고 클래스는 참조 타입이라고 했습니다. 그렇다면 값 타입과 참조 타입의 가장 큰 차이 무엇일까요? 무엇이 전달 되느냐입니다. 예를 들어 어떤 함수의 전달인자로 값 타입의 값을 넘긴다면 전달될 값이 복사되어 전달됩니다.
그러나 참조 타입이 전달될 인자로 전달될 때에는 값을 복사하지 않고 참조(주소)가 전달 됩니다. 여기서 주소라는것은 Point라는 개념과 유사합니다.

struct PersonInfo {
  var name: String
  var age: Int
}

var sjInfo: PersonInfo = PersonInfo(name: "김승진", age: 28)
sjInfo.age = 30

//PersonInfo의 값을 복사하여 할당
var friendInfo: PersonInfo = sjInfo

print("sjInfoAge : (sjInfo.age)")  // 30
print("friendInfoAge : (friendInfo.age)")  // 30

friendInfo.age = 90

//sjInfo의 값을 복사해왔기 때문에 별개의 값을 가진다.
print("sjInfoAge : (sjInfo.age)")  // 30
print("friendAge : (friendInfo.age)")  // 90

class Person {
  var name : String = ""
  var age: Int = 0
}

var kim: Person = Person()
var friend: Person = kim

print("kimAge : (kim.age)")  // 0
print("friendAge : (friend.age)")  // 0

friend.age = 28

print("kimAge : (kim.age)")  // 28
print("friendAge : (friend.age)")  // 28

값 타입의 데이터가 함수의 전달인자로 전달되면 메모리에 전달인자를 위한 인스턴스가 새로 생성되고, 생성된 새 인스턴스에는 전달하려는 값이 복사되어 들어갑니다.
반면 참조 타입의 데이터는 전달인자로 전달될 때, 기존 인스터스의 참조가 전달되므로 새로운 인스턴스가 아닌 기준의 인스턴스 주소가 전달 됩니다. 함수의 전달인자 뿐만 아니라 새로운 변수에 할당될 때 또한 마찬가지입니다.

3.2 스위프트의 기본 데이터 타입은 모두 구조체

String타입의 정의를 살펴보겠습니다.

public struct String {
  public init()
}

스위프트의 다른 기본타입(Int, Bool, Array, Dictionary, Set 등)도 String 타입과 마찬가지로 모두 구조체로 구현되어 있습니다.
즉, 기본 데이터 타입은 모두 값 타입이라는 뜻입니다.

 

구조체와 클래스는 모두 새로운 떼이터 타입을 정의하고 기능을 추가한다는 점에서는 같습니다. 하지만 용도의 의미에서는 다릅니다.
그러면 어떤것을 선택해서 사용하는 것이 좋을까요?

 

애플 가이드라인에서 다음 조건 중 하나 이상에 해당된다면 구조체를 사용하기를 권합니다.

  1. 연관된 간단한 값의 집합을 캡슐화하는 것만이 목적일 때
  2. 캡슐화된 값이 참조되는 것보다 복사되는것이 합당할 때
  3. 다른 타입으로부터 상속받거나 자신이 상속될 필요가 없을 때

그렇다면 스위프트의 기본 데이터 타입이 모두 구조체라서 다수의 배열 또는 딕셔너리 등의 데이터를 복사하고 이용할 때 메모리를 비효율적으로 사용한다고 오해할 수 있으실텐데 스위프트는 꼭 필요한 경우에만 진짜 복사를 합니다.
컴파일러가 판단하여 꼭 복사를 할 필요가 없을 경우, 요소를 많이 갖는 큰 배열을 함수의 전달인자로 넘겨준다고 해서 꼭 모든 값을 메모리의 다른 공간에 복사해 넣지 않을 수도 있다는 뜻입니다. 즉, 스위프트가 알아서 효율적으로 처리해줄 것입니다.