본문 바로가기

iOS/Swift - UIKit

[Swift 객체지향] 2 - 구조체와 클래스의 차이점

✳️ 구조체와 클래스의 공통점과 차이점

📌 구조체와 클래스의 공통점

  • 프로퍼티 정의 : 값을 저장
  • 메서드 정의 : 기능을 실행
  • 서브스크립트 문법 : 프로퍼티에 접근 가능
  • 이니셜라이저 : 초기화 될 때의 상태를 지정 가능
  • 익스텐션 : 정의 이후 기능 확장 가능
  • 프로토콜 : 특정 기능 실행을 위해 채택 가능

📌 구조체와 클래스의 차이점

  • 클래스만 상속 가능
  • 클래스 인스턴스에만 타입캐스팅 가능
  • 클래스 인스턴스에만 디이니셜라이저 사용 가능
  • 클래스 인스턴스에만 참조 횟수 계산이 적용

이러한 차이점은 구조체와 클래스가 각각 값 타입과 참조 타입이라는 점에서 온다.

✨ 값 타입과 참조 타입

값 타입을 전달 인자로 사용하면 값이 직접 복사되어 전달되고, 참조 타입을 전달 인자로 사용하면 값이 저장되어 있는 주소가 전달된다. 이러한 참조의 개념은 C언어나 CPP에서 나오는 포인터와 비슷한 개념이다.

1️⃣ 클래스는 참조 타입이다

class Food {
    var price: Int = 0
    var weight: Float = 0.0

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

func checkReference() {
        var apple: Food = Food()
        var pineapple: Food = apple

        print("apple의 가격 : \(apple.price)")
        print("pineapple의 가격 : \(pineapple.price)")

        pineapple.price = 200
        print("바뀐 apple의 가격 : \(apple.price)")
        print("바뀐 pineapple의 가격 : \(pineapple.price)")
}

/* 출력결과
apple의 가격 : 0
pineapple의 가격 : 0
바뀐 apple의 가격 : 200
바뀐 pineapple의 가격 : 200
*/

클래스가 참조 타입이기 때문에, pineapple의 프로퍼티를 바꿔줬음에도 apple의 프로퍼티 값이 바뀌었다. 참조 타입은 그 주소에 직접 접근하여 그 값을 수정할 수 있다. 만약 Food가 struct였다면 apple의 값이 바뀌지 않았을 것이다.

2️⃣ 함수의 전달인자로 구조체와 클래스를 각각 넣었을 때

struct Member {
    var name: String
    var age: Int
}
class Food {
    var price: Int = 0
    var weight: Float = 0.0
}

var firstMember: Member = Member(name: "Duno", age: 25)
var tomato: Food? = Food()

func changeMemberAge(_ member: Member) {
    var targetMember: Member = member
    targetMember.age = 15
}

func changeFoodPrice(_ food: Food) {
    food.price = 300
}

changeMemberAge(firstMember)
print("firstMember의 나이 : \(firstMember.age)")

changeFoodPrice(tomato ?? Food())
print("tomato의 가격 : \(tomato!.price)")

/* 출력결과
firstMember의 나이 : 25
tomato의 가격 : 300
*/

📌 struct를 전달인자로 준 경우, firstMember가 값 타입이기 때문에 함수 안에 새로운 인스턴스를 생성해서 전달해야 하고, 결과적으로 firstMember의 나이는 변하지 않은 모습을 보인다.

📌 class를 전달인자로 준 경우, tomato가 참조 타입이기 때문에 새로운 인스턴스를 생성할 필요 없이 바로 프로퍼티에 접근하여 값을 할당할 수 있고, 결과적으로 tomato의 가격이 변한 모습을 보인다.

3️⃣ 식별연산자의 사용

클래스의 인스턴스가 같은 값을 참조하는지 판단하기 위해 === 연산자를 사용할 수 있다.

var apple: Food = Food()
var pineapple: Food = apple
var tomato: Food = Food()

print(apple === pineapple)
print(apple === tomato)
print(apple !== tomato)

/* 출력결과
true
false
true
*/

4️⃣ 기본 자료형은 모두 구조체이기에, 값 타입이다

Int, Float, Bool 등 데이터타입의 정의를 보면 구조체로 정의되어 있다. 따라서 이 모두 구조체이며 값 타입의 특성을 가진다.

그렇기에 함수에 이러한 데이터타입을 전달인자로 사용하면 값이 복사되어 전달되기 때문에 원본 데이터에는 영향을 주지 않는 것이다.

✨ 구조체와 클래스 선택해서 사용하기

지금까지 공부한 구조체와 클래스의 성질을 바탕으로, 실제 프로젝트에서 적절하게 취사선택해야한다.

📌 기준

전반적으로 클래스를 사용할 일이 많다고 한다.

⁉️ 그러나 애플 가이드라인에 따르면, 아래의 상황에서는 구조체의 사용이 적절하다고 밝히고 있다.

✅ 간단한 값들의 집합을 캡슐화하기 위해(x, y좌표계)

✅ 값의 참조보다 복사가 알맞을 때(서버에서 데이터를 받아오는 상황)

✅ 구조체 내에 있는 프로퍼티가 값 타입이고, 참조보다 복사가 합당할 때

✅ 상속받거나 상속할 필요가 없을 때