class vs struct 차이점
- Swift에서
class(클래스)와 struct(구조체) 모두 데이터를 정의하는 기본적인 방법이다
- 하지만 메모리 관리 방식, 상속 여부, 참조 방식, 속성 변경 가능 여부에서 큰 차이가 있다
| 구분 | struct(구조체) | class(클래스) |
|---|
| 메모리 저장 방식 | 값 형식(Value Type) | 참조 형식(Reference Type) |
| 인스턴스 복사 방식 | 값 복사(새로운 복사본 생성) | 참조 복사(같은 인스턴스를 참조) |
| 상속(Inheritance) | ❌ 지원하지 않음 | ✅ 지원됨 |
| 초기화 방식 | 멤버와이즈 이니셜라이저 제공 | 생성자를 직접 정의해야 한다 |
| 속성 변경 여부 | mutating 키워드를 사용해야 변경 가능 | 메서드 내에서 기본적으로 변경 가능 |
let 선언 시 동작 | 모든 속성이 let처럼 동작(수정 불가) | 인스턴스는 고정되지만, 내부 속성은 변경 가능 |
소멸자(deinit) | ❌ 없음 | ✅ 있음(메모리에서 해제 시 호출) |
| ARC(자동 메모리 관리) | ❌ 적용되지 않음(값 타입이라 필요 없음) | ✅ 적용됨(클래스의 참조 카운트 관리) |
값 형식(Value Type)과 참조 형식(Reference Type)의 차이점
- Swift의 구조체와 클래스의 가장 큰 차이점은 값 형식(구조체)과 참조 형식(클래스) 라는 점이다
값 형식(Value Type)
- 구조체(
struct), (enum)은 값 형식이다
- 인스턴스를 변수나 상수에 할당할 때 값 자체가 복사된다(완전히 독립적인 복사본이 만들어짐)
- 하나의 인스턴스를 여러 변수나 상수에 저장하더라도 각각 별개의 인스턴스로 취급된다
struct Car {
var brand: String
}
var car1 = Car(brand: "Tesla")
var car2 = car1
car2.brand = "BMW"
print(car1.brand)
print(car2.brand)
참조 형식(Reference Type)
- 클래스(
class)는 참조 형식이다
- 인스턴스를 변수나 상수에 할당할 때 참조(메모리 주소)만 복사된다
- 여러 변수가 동일한 인스턴스를가리키게 된다
- 하나의 변수를 수정하면, 같은 인스턴스를 참조하는 다른 변수에도 영향을 미친다
class Car {
var brand: String
init(brand: String) {
self.brand = brand
}
}
var car1 = Car(brand: "Tesla")
var car2 = car1
car2.brand = "BMW"
print(car1.brand)
print(car2.brand)
구조체와 클래스의 특징 및 주요 차이점
구조체(struct)의 특징
- 값 타입(Value Type)
- 인스턴스를 복사하면, 값이 복사됨.
- 서로 독립적인 인스턴스로 취급됨.
- 상속 불가능(Inheritance)
- mutating 키워드 필요
- 메서드 내부에서 속성을 변경하려면 mutating 키워드를 사용해야 함.
- 멤버와이즈 이니셜라이저 자동 제공
- 모든 속성을 초기화하는 이니셜라이저가 자동으로 제공됨.
- let 선언 시 제한
- let으로 선언된 구조체는 내부 속성도 변경 불가
클래스(class)의 특징
- 참조 타입(Reference Type)
- 인스턴스를 복사하면, 같은 인스턴스를 가리키게 됨.
- 하나의 변수를 수정하면 다른 변수도 영향을 받음.
- 상속 가능(Inheritance)
- 다른 클래스로부터 상속받거나 상속할 수 있음.
- mutating 키워드 필요 없음
- 메서드 내에서 속성 변경이 기본적으로 허용됨.
- 생성자(이니셜라이저)를 직접 정의해야 함
- 소멸자 (deinit) 사용 가능
- 인스턴스가 메모리에서 해제될 때 호출되어 정리 작업을 수행할 수 있음.
구조체의 mutating 키워드 사용 방법
- 구조체의 인스턴스 메서드는 기본적으로 내부 속성을 변경할 수 없다
- 하지만
mutating 키워드를 사용하면 속성을 변경할 수 있다
struct Car {
var brand: String
mutating func changeBrand(to newBrand: String) {
self.brand = newBrand
}
}
var myCar = Car(brand: "Tesla")
myCar.changeBrand(to: "BMW")
print(myCar.brand)
클래스의 소멸자
- 클래스는 참조 타입이기 때문에, 인스턴스가 메모리에서 해제될 때
deinit을 통해 정리 작업을 수행할 수 있다
class Person {
var name: String
init(name: String) {
self.name = name
}
deinit {
print("\(name) 객체가 메모리에서 해제됩니다.")
}
}
var person: Person? = Person(name: "철수")
person = nil
객체지향 프로그래밍(Object-Oriented Programming, OOP)
- 객체지향 프로그래밍(Object-Oriented Programming, OOP)은 현실 세계의 사물과 개념을 프로그래밍으로 모델링하여 코드의 복잡성을 줄이고, 유지보수 및 재사용성을 높이기 위한 방법론이다
- Swift에서 객체지향 프로그래밍은 클래스와 구조체를 이용해 데이터를 묶고, 해당 데이터와 관련된 기능들을 하나로 캡슐화하는 방식으로 구현된다
- 결국, 클래스와 구조체는 데이터를 관련 있는 기능과 함께 하나의 묶음으로 만들려는 것이다
class Player {
var name: String
var health: Int
var level: Int
init(name: String, health: Int, level: Int) {
self.name = name
self.health = health
self.level = level
}
func attack() {
print("\(name) is attacking!")
}
}
let player1 = Player(name: "Warrior", health: 100, level: 1)
player1.attack()
클래스와 구조체의 사용 기준
- Swift에서는 클래스와 구조체 모두 객체를 정의하는 데 사용된다
- 하지만 두 가지는 중요한 차이가 있기 때문에 상황에 따라 다르게 사용해야 한다
클래스를 사용해야 하는 경우
- 클래스는 참조 타입(Reference Type) 이기 때문에 인스턴스를 복사하면 같은 메모리 주소를 참조하게 된다
- 즉, 하나의 인스턴스를 여러 곳에서 수정할 수 있다
클래스를 사용해야 하는 경우
- 상속이 필요할 때
- 클래스는 상속을 지원하여 코드의 재사용성을 높일 수 있다
- 참조를 통한 공유가 필요할 때
- 동일한 객체를 여러 곳에서 공유하여 수정할 필요가 있을 때 사용한다
- 의도적으로 인스턴스가 변경 가능한 상태를 유지해야 할 때
class Dog {
var name: String
init(name: String) {
self.name = name
}
}
let dog1 = Dog(name: "Buddy")
let dog2 = dog1
dog2.name = "Max"
print(dog1.name)
구조체를 사용해야 하는 경우
- 구조체는 값 타입(Value Type) 이기 때문에 인스턴스를 복사하면 독립된 복사본이 만들어진다
- 각 인스턴스는 별도로 존재하며, 수정해도 원본 데이터에 영향을 주지 않는다
구조체를 사용해야 하는 경우
- 상속이 필요 없을 때
- 구조체는 상속을 지원하지 않기 때문에, 클래스 계층 구조가 필요하지 않은 경우 사용한다
- 값 불변성을 유지하려고 할 때
- 구조체는 데이터를 복사하여 전달하므로 원본 데이터의 안전성을 보장한다
- 데이터의 복사와 독립성을 보장할 때
- 예를 들어 좌표, 크기, 거리 등 값으로 표현되는 데이터 모델에 적합하다
- 메모리 관리 효율성을 높이고자 할 때
- 값 타입으로서 메모리 할당 및 해제가 더 효율적이다
struct Point {
var x: Int
var y: Int
}
var pointA = Point(x: 0, y: 0)
var pointB = pointA
pointB.x = 10
print(pointA.x)
객체지향의 4대 특징
1. 캡슐화(Encapsulation)
- 데이터를 보호하고, 외부에서 접근할 수 없도록 숨기는 것
- 접근 제어자(
private, internal, public)를 사용하여 구현한다
class BankAccount {
private var balance: Double = 0.0
func deposit(amount: Double) {
balance += amount
}
func getBalance() -> Double {
return balance
}
}
2. 상속(Inheritance)
- 기존 클래스를 확장하여 새로운 클래스를 정의하는 방법
- 코드의 재사용성을 증가시킨다
class Vehicle {
var speed: Int = 0
func drive() {
print("Driving at \(speed) km/h")
}
}
class Car: Vehicle {
var brand: String
init(brand: String) {
self.brand = brand
}
}
3. 다형성(Polymorphism)
- 동일한 이름의 메서드나 프로퍼티가 클래스별로 다르게 동작할 수 있도록 하는 것이다
- Swift에서는 프로토콜과 클래스의 메서드 오버라이딩으로 구현한다
protocol Shape {
func area() -> Double
}
class Circle: Shape {
var radius: Double
init(radius: Double) {
self.radius = radius
}
func area() -> Double {
return 3.14 * radius * radius
}
}
4. 추상화(Abstraction)
- 불필요한 구현을 숨기고, 필요한 정보만 노출하는 방법이다
- Swift에서 프로토콜을 이용하여 인터페이스를 정의한다
protocol Drawable {
func draw()
}
class Triangle: Drawable {
func draw() {
print("Drawing a triangle.")
}
}
정리
- 객체지향 프로그래밍은 현실 세계의 개념을 코드로 모델링하여 유지보수와 재사용성을 높이는 방법이다
- 클래스는 참조 타입으로 상속과 공유가 필요할 때 사용하며, 구조체는 값 타입 으로 데이터의 독립성을 보장할 때 사용한다
- Swift의 객체지향 프로그래밍은 클래스와 구조체의 적절한 사용과 4대 특징(캡슐화, 상속, 다형성, 추상화) 을 활용하는 것이 중요하다