[ETC_009] 구조체와 클래스
👨🏻‍💻iOS 공부/Swift 기본기 다지기

[ETC_009] 구조체와 클래스

728x90
반응형

구조체와 클래스

구조체(struct)와 클래스(class)는 프로퍼티와 메서드를 사용하여 구조화된 데이터와 기능을 가질 수 있다. 즉 하나의 새로운 사용자 정의 데이터 타입을 만들어 주는 것이다.

구조체의 인스턴스는 값 타입이며, 클래스의 인스턴스는 참조 타입이라는게 가장 큰 차이점이다.

구조체

구조체는 아래와 같이 정의할 수 있다.

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

위의 구조처럼 구성을 하나 이해를 위해서는 실제 사용 사례를 보자

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

이름과 나이라는 저장 프로퍼티를 가지고 있는 구조체를 만들어보았다. 이후 이를 기반으로 인스턴스를 생성하고 초기화할 수 있다.

// Student(name:,age:)로 자동 생성된 이니셜라이저를 사용하여 구조체를 생성합니다. 
var Info1: Student = Student(name: "Cha", age : 26)
Info1.name = "Min" // 변경 가능
Info1.age = 30 // 변경 가능

// Student(name:,age:)로 자동 생성된 이니셜라이저를 사용하여 구조체를 생성합니다. 
let Info2: Student = Student(name: "Cha", age : 26)
Info2.name = "Min" // 변경 가능
Info2.age = 30 // 변경 가능

변수(var)가 아닌 상수(let)로 생성시 인스턴스 내부 프로퍼티 값을 변경할 수 없게 된다.

클래스

스위프트의 경우, 부모 클래스가 없더라도 상속 없이 단독으로 정의가 가능하다.

클래스는 아래와 같이 정의할 수 있다.

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

구조는 구조체와 거의 똑같다. 다만 상속 받을 때는 “클래스이름” 뒤에 콜론을 사용하고 부모클래스 이름을 명시한다.

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

구조체처럼 동일하게 클래스로도 정의해보자.

class Player {
    var name : String = "cha"
    var age : Int = 26
}

위 처럼 프로퍼티 기본값이 설정이 되어있다면 전달인자를 사용하여 초깃값을 따로 지정해주지 않아도 된다.
구조체와 동일하게 프로퍼티에 접근하고 싶다면 “.”을 붙여 사용한다.
** 구조체와는 다르게 클래스의 인스턴스는 참조 타입이므로 클래스의 인스턴스를 상수 let으로 선언해도 내부 프로퍼티 값을 변경할 수 있다.

var P1: Player = Player()
P1.name = "messi"
P1.age = 37

let P2: Player = Player()
P2.name = "podolski"
P2.age = 35

기존에 구조체 사용시 let을 선언하였을 때 프로퍼티 값 변경이 안되었으나, 클래스는 가능하다.

클래스의 인스턴스는 참조 타입이므로 더 이상 참조할 필요가 없을 때, 소멸(메모리에서 해제)된다. 이를 소멸이라고 부르며, 소멸 직전에 deinit라는 메서드가 호출된다. 클래스 내부에 deinit 메서드를 구현하면 소멸직전에 deinit 메서드가 호출된다. 이 메서드는 클래스당 하나만 구현 가능하며, 매개변수와 반환값을 가질 수 없다. 또한 소괄호도 필요하지 않다.

class Food {
    var name : String
    var price : Float

    deinit {
        print("Food 클래스의 인스턴스가 소멸됩니다.")
    }
}

var favoriteFood : Food? = Food()
favoriteFood = nil // 클래스의 인스턴스가 소멸됩니다.

구조체와 클래스 이 두 가지는 앞으로 많이 사용하게 될텐데, 차이점이 헷갈릴 것이다. 공통점과 차이점을 알아보자.

공통점

  • 값을 저장하기 위해 프로퍼티를 정의할 수 있다.
  • 기능 실행을 위해 메서드를 정의할 수 있다.
  • 내부 프로퍼티에 접근할 수 있다.
  • 초기화 될 때의 상태를 지정하기 위해 이니셜라이저를 정의할 수 있다.
  • 초기구현과 더불어 기능 추가를 위한 익스텐션을 사용할 수 있다.
  • 특정 기능을 실행하기 위해 특정 프로토콜을 준수할 수 있다.

차이점

  • 구조체는 상속할 수 없다.
  • 타입캐스팅(형변환:데이터 타입이 필요에 따라 다른 자료형으로 변환되는 것)은 클래스의 인스턴스에만 허용된다.
  • deinit은 클래스 인스턴스에서만 사용가능하다.
  • 참조 횟수 계산은 클래스의 인스턴스에만 적용된다.


가장 큰 차이점은 구조체는 “값” 타입이고 클래스는 “참조” 타입이라는 점이다. 그렇다면 값 타입과 참조 타입이란 무엇일까?


예시로, 어떤 함수의 전달인자로 값타입의 값을 넘긴다면 전달될 값이 복사되어 전달되게 된다. 하지만 참조 타입으로 전달인자를 넘기게 된다면 복사하지 않고 참조가 전달된다. 즉 값 타입은 값을 복사하여 사용하는 것이기 때문에 추후 값을 변경하여도 해당 값에만 변동을 주는 것이다.


name과 age를 프로퍼티로 갖는 struct A가 있다고 하자. 이를 복사하여 struct B를 생성했다고 생각해보자. 당연히 struct B에도 name과 age가 있을 것이다. 근데 여기서 struct B 프로퍼티 중 age 값을 변경하게 되면, struct B의 age에만 변화가 생기고 struct A의 age는 기존값을 유지하게 된다. 즉 별개로 판단한다는 것이다.


반면에 class의 경우 동일하게 A,B가 있다고 하자. 복사한 B의 프로퍼티 값을 변경하게 되면 A의 프로퍼티도 따라서 변경되게 된다. 왜냐하면 값이 아니라 “참조”이기 때문이다. 메모리에 전달인자를 위한 새로운 인스턴스를 생성되는 것이 아니라, 기존의 인스턴스 참조를 전달하는 것이다. (반대로 보면 struct는 전달인자를 위한 새로운 인스턴스를 생성해내기에 기존 인스턴스는 값의 변동에 무반응을 보이는 것이다.)


그림과 코드를 통해 다시 한 번 이해해보자.

// 앞의 struct Student 사용// 앞 줄글에서 struct A를 dean, struct B를 alsoDean으로 봐보자.
let dean = Student()
dean.name = "dean"
dean.age = 26

var alsoDean = dean
alsoDean.age = 30

이는 아래의 그림처럼 나타낼 수 있다.


아예 새로운 인스턴스를 생성하여 가리키는 것을 볼 수 있다. 그렇기에 값을 변경하여도 기존 인스턴스에는 영향을 미치지 않는 것이다.


반면 클래스의 경우로 다시 봐보자.

// class Student 사용
let dean = Student()
dean.name = "dean"
dean.age = 26

let alsoDean = dean
alsoDean.age = 30

이는 아래의 그림처럼 나타낼 수 있다.


새로운 인스턴스를 생성하지 않고 참조하는 모습을 볼 수 있다. 이 때문에 값의 변화가 생기게 되면 값이 전부 바뀌게 되는 것이다.


데이터를 일괄적으로 관리하기에는 좋으나 독립적으로 사용이 어렵다는 점을 알 수 있다.



이에 클래스의 인스턴스끼리 참조가 같은지 확인할 때는 “식별 연산자”를 사용한다.

var messi: Player = Player()
let P1: Player = messi // messi의 참조를 할당
let P2: Player = Player() // 새로운 인스턴스 생성

messi === P1 // true
messi === P2 // false
P1 !== P2 // true

이에 따라 용도에 맞게, 성격에 맞게, 데이터 활용도에 따라 구조체 혹은 클래스를 선택하여 사용하여야 한다. 대다수 사용자 정의 데이터 타입이 클래스로 구현된다고는 하나, 용도에 따라 다르기에 case-by-case일 것 같다!

728x90
반응형