iOS/Common

[Swift] struct, class, enum

TDCIAN 2023. 4. 10. 18:19

 

 

Q. Swift에서 struct, class, enum 각각의 특징과 차이점을 비교해서 설명해 줘

 

A. Swift에서 sturct, class, enum은 모두 데이터 모델링을 위한 구조체입니다.

각각의 구조체는 고유한 특징과 용도가 있습니다.

 

Struct(구조체)

- 값 타입(Value Type)

- 생성자(constructor) 필요 없음

- 상속 불가능

- 구조체 인스턴스를 상수로 선언하면 변경 불가능

- 메모리 할당과 해제가 빠름

- 프로퍼티는 기본적으로 모든 인스턴스에 대해 초기화되어야 함

 

Class(클래스)

- 참조 타입(Reference Type)

- 생성자 필요

- 상속 가능

- 클래스 인스턴스를 상수로 선언해도 내부 프로퍼티는 변경 가능

- 메모리 할당과 해제가 느림

- 프로퍼티는 초기화될 필요가 없음

 

Enum(열거형)

- 값 타입(Value Type)

- 생성자 필요 없음

- 상속 불가능

- 각 케이스는 자체 값 또는 연관 값(associated value)을 가질 수 있음

- 메모리 할당과 해제가 빠름

- 모든 케이스는 초기화되어야 함

 

 

Struct와 Class는 대체로 비슷하지만 차이점은 다음과 같습니다.

 

1. 값 타입 vs 참조 타입

- Struct는 값 타입(Value Type)이고, Class는 참조 타입(Reference Type)입니다.

값 타입은 복사(copy)될 때 값을 복사하고 참조 타입은 포인터(pointer)를 복사합니다.

 

2. 상속 가능 여부

- Struct는 상속이 불가능하지만, Class는 상속이 가능합니다.

 

3. 생성자

- Struct는 생성자를 선언하지 않아도 자동으로 생성되지만, Class는 생성자를 선언해야 합니다.

 

4. 메모리 할당과 해제

- Struct는 값 타입이므로 스택 메모리에서 할당 및 해제되어 처리 속도가 빠르고,

Class는 참조 타입이므로 힙 메모리에서 할당 및 해제되어 처리 속도가 느립니다.

 

Enum은 다음과 같은 특징을 가집니다.

1. 값 타입

- Enum은 값 타입(Value Type)입니다.

 

2. 연관 값

- 각 케이스(case)는 자체 값 또는 연관 값(associated value)을 가질 수 있습니다.

 

3. 상속 불가능

- Enum은 상속이 불가능합니다.

 

4. 생성자

- Enum은 생성자를 선언하지 않아도 자동으로 생성되지만,

연관 값(associated value)이 있는 경우 생성자를 선언해야 합니다.

 

5. 패턴 매칭

- Enum은 패턴 매칭(pattern matching)을 통해 쉽게 값에 접근할 수 있습니다.

 

6. switch 문

- Enum은 switch 문에서 패턴 매칭을 사용할 수 있습니다.

 

7. 연산 프로퍼티

- Enum은 연산 프로퍼티(computed property)를 가질 수 있습니다.

 

8. Raw Value

- Enum은 Raw Value를 가질 수 있습니다. Raw Value는 각 케이스(case)에 대한 기본 값을 나타내며, 명시적으로 지정할 수 있습니다.

 

각각의 구조체는 다른 용도로 사용됩니다. 일반적으로 값 타입(Value Type)인  Struct는 간단한 데이터 모델링에 적합하며,

참조 타입(Reference Type)인 Class는 복잡한 데이터 모델링에 적합합니다.

Enum은 제한된 개수의 값으로 데이터를 모델링할 때 유용합니다.

예를 들어, 주소록에 사용되는 연락처 유형을 열거형으로 모델링할 수 있습니다.

 

 

 

Q. Swift에서 struct, class, enum 각각의 특징과 차이점을 비교한 내용을 Swift 코드와 함께 설명해 줘

 

A. 각각의 타입에 대한 특징과 차이점을 코드와 함께 살펴보겠습니다.

 

1. Struct

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

var person = Person(name: "John", age: 30)
person.age = 31

- Struct는 값 타입(Value Type)입니다. 즉, 변수나 상수에 할당되면 해당 값이 복사됩니다.

- Struct는 기본적으로 멤버 와이즈 초기화를 지원합니다.

- Struct는 내부의 변수를 변경하려면 해당 Struct 인스턴스를 var로 선언해야 합니다.

 

+ Q. 멤버 와이즈 초기화의 정의와 용도를 Swift 코드와 함께 설명해 줘

 

A. 멤버 와이즈 초기화(Memberwise initialization)란 구조체나 클래스의 모든 멤버 변수를 파라미터로 받아서 객체를 초기화하는 초기화 메서드입니다. 이러한 초기화 방법은 코드의 가독성을 높이고 객체를 쉽게 생성할 수 있게 해 줍니다.

 

예를 들어, 다음과 같은 구조체가 있다고 가정해 봅시다.

struct Person {
    var name: String
    var age: Int
    var gender: String
}

위의 구조체를 생성할 때, 멤버 와이즈 초기화를 사용한다면 다음과 같이 코드를 작성할 수 있습니다.

let person = Person(name: "John", age: 25, gender: "Male")

이 코드는 name, age, gender 속성을 모두 인자로 받아서 객체를 초기화합니다.

이렇게 하면 코드의 가독성이 좋아지고, 객체를 쉽게 생성할 수 있습니다.

 

멤버 와이즈 초기화는 클래스에서도 사용할 수 있습니다.

다음은 클래스에서 멤버 와이즈 초기화를 사용하는 예제입니다.

class Person {
    var name: String
    var age: Int
    var gender: String
    
    init(name: String, age: Int, gender: String) {
        self.name = name
        self.age = age
        self.gender = gender
    }
}

let person = Person(name: "John", age: 25, gender: "Male")

위의 예제에서는 Person 클래스에 init 메서드를 정의하여 멤버 와이즈 초기화를 수행합니다.

init 메서드에서는 name, age, gender 속성을 파라미터로 받아서 객체를 초기화합니다.

이렇게 하면 클래스에서도 멤버 와이즈 초기화를 사용할 수 있습니다.

 

 

 

3. Class

class Person {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

var person = Person(name: "John", age: 30)
person.age = 31

- Class는 참조 타입(Reference Type)입니다. 즉, 변수나 상수에 할당되면 해당 값의 참조가 복사됩니다.

- Class는 초기화를 위해 생성자(constructor)를 구현해야 합니다.

- Class 내부의 변수를 변경할 때에도 var로 선언된 변수가 아니더라도 직접 접근이 가능합니다.

 

 

3. Enum

enum CompassDirection {
    case north
    case south
    case east
    case west
}

var direction = CompassDirection.north
direction = .east

- Enum은 값 타입(Value Type)입니다.

- Enum은 각 케이스(case)에 대한 값을 가질 수 있으며, 연관 값을 가질 수도 있습니다.

- Enum은 switch문과 함께 사용되어, 각 케이스에 대한 패턴 매칭을 지원합니다.

 

각각의 타입은 다양한 상황에서 적합한 용도로 사용됩니다. Struct는 간단한 데이터 모델링에 적합하며,

Class는 복잡한 데이터 모델링에 적합합니다. Enum은 제한된 개수의 값으로 데이터를 모델링할 때 유용합니다.

 

 

 

Q. struct, class, enum으로 데이터 모델링한 예시를 Swift 코드로 설명해 줘

 

A. 예시를 통해 각각의 타입이 어떻게 데이터 모델링에 사용될 수 있는지 살펴보겠습니다.

 

1. Struct를 사용한 예시

struct Book {
    var title: String
    var author: String
    var pageCount: Int
    var isAvailable: Bool
}

var book = Book(
    title: "The Great Gatsby", 
    author: "F. Scott Fitzgerald", 
    pageCount: 218, 
    isAvailable: true
)
book.isAvailable = false

위 예시에서는 책의 제목, 작가, 페이지 수, 대출 가능 여부 등의 정보를 담는 Book 구조체를 정의하고 있습니다.

구조체의 인스턴스를 생성하면 해당 정보를 담는 객체가 생성되며,

isAvailable 변수를 변경하여 대출 가능 여부를 변경할 수 있습니다.

 

 

2. Class를 사용한 예시

class Library {
    var name: String
    var books: [Book]
    
    init(name: String, books: [Book]) {
        self.name = name
        self.books = books
    }
    
    func addBook(book: Book) {
        self.books.append(book)
    }
    
    func removeBook(title: String) {
        self.books = self.books.filter { $0.title != title }
    }
}

var library = Library(name: "Main Library", books: [])
library.addBook(book: 
	Book(
        title: "The Great Gatsby", 
        author: "F. Scott Fitzgerald", 
        pageCount: 218, 
        isAvailable: true
    )
)
library.addBook(book: 
    Book(
        title: "To Kill a Mockingbird", 
        author: "Harper Lee", 
        pageCount: 281, 
        isAvailable: true
    )
)
library.removeBook(title: "To Kill a Mockingbird")

위 예시에서는 도서관을 모델링하는 Library 클래스를 정의하고 있습니다.

클래스의 인스턴스를 생성하면 도서관 객체가 생성되며,

addBook 메소드와 removeBook 메소드를 사용하여 도서관에 책을 추가하거나 삭제할 수 있습니다.

 

 

3. Enum을 사용한 예시

enum AccountType {
    case checking
    case savings
    case creditCard
}

struct BankAccount {
    var accountType: AccountType
    var balance: Double
}

var account = BankAccount(accountType: .checking, balance: 1000.0)

위 예시에서는 은행 계좌를 모델링하는 BankAccount 구조체를 정의하고 있습니다.

구조체 내부의 accountType 변수는 AccountType 열거형을 사용하며, 계좌 종류를 저장하고 있습니다.

BankAccount 인스턴스를 생성할 때, accountType 변수에는 .checking, .savings, .creditCard와 같은 케이스 중 하나를 할당하여 계좌의 종류를 정할 수 있습니다.

 

 

 

Q. struct를 쓰는 것이 유리한 상황, class를 쓰는 것이 유리한 상황, enum을 쓰는 것이 유리한 상황을 나눠서 Swift 코드로 이유와 함께 설명해 줘

 

A. Swift에서는 struct, class, enum을 각각 다른 상황에 적합하게 사용할 수 있습니다.

다음은 각각의 타입을 사용하는 상황을 설명하겠습니다.

 

1. Struct를 사용하는 경우

- 간단한 데이터 모델링: 구조체는 값 타입(Value Type)으로, 객체를 복사하면 객체의 모든 속성이 복사됩니다. 이러한 특성을 이용하여 단순한 데이터 모델링에 적합합니다.

- 동기화가 필요 없는 데이터 처리: 구조체는 값 타입으로 작동하기 때문에, 동시성 문제를 발생시키지 않습니다. 따라서, 스레드 안정성이 필요하지 않거나 동기화가 필요 없는 데이터 처리에 적합합니다.

- 함수형 프로그래밍: Swift에서는 함수형 프로그래밍을 지원하며, 이때 구조체를 이용하여 함수형 프로그래밍을 쉽게 구현할 수 있습니다.

struct Rectangle {
    var width: Double
    var height: Double
    
    func area() -> Double {
        return width * height
    }
}

let rect1 = Rectangle(width: 5, height: 10)
let rect2 = Rectangle(width: 7, height: 15)

print("rect1 area: \(rect1.area())") // 50.0
print("rect2 area: \(rect2.area())") // 105.0

 

+ Q. 값 타입으로 작동하는 것과 동시성 문제가 발생하는 것 사이에는 어떤 연관성이 있는 것인지 Swift 코드와 함께 설명해 줘

 

A. 값 타입(Value Type)은 복사되어 전달됩니다. 이는 값 타입이 별도의 메모리 공간에 저장되어 있기 때문입니다.

따라서 값 타입을 사용하면 다른 쓰레드에서 같은 값을 수정하는 경우 발생하는 동시성 문제가 없습니다.

 

반면 참조 타입(Reference Type)은 메모리에 한 번 생성되고 그 뒤로는 해당 메모리 위치를 가리키는 포인터만 전달됩니다.

따라서 참조 타입은 여러 객체에서 동시에 같은 메모리 위치를 참조할 수 있기 때문에 동시성 문제가 발생할 수 있습니다.

 

Swift에서는 struct와 enum이 값 타입(Value Type)이며 class가 참조 타입(Reference Type)입니다.

따라서 struct나 enum을 사용하면 동시성 문제를 예방할 수 있습니다.

 

다음은 값 타입과 참조 타입의 동시성 문제에 대한 예제입니다.

struct Counter {
    var value: Int = 0
    
    mutating func increment() {
        value += 1
    }
}

var c = Counter(value: 0)

let queue = DispatchQueue(label: "queue1", attributes: .concurrent)

for _ in 0..<1000 {
    queue.async {
        c.increment()
    }
}

sleep(5)
print(c.value)

위의 코드는 구조체인 Counter를 사용하여 값의 증가를 하는 코드입니다.

Counter 구조체는 값 타입(Value Type)이므로 여러 쓰레드에서 동시에 값을 변경하는 경우에도 값이 증가하게 됩니다.

따라서 위의 코드는 정상적으로 1000이 출력됩니다.

 

반면 클래스인 Counter를 사용하여 값을 증가시키는 경우 동시성 문제가 발생할 수 있습니다.

아래는 Counter 클래스를 사용한 예제입니다.

class Counter {
    var value: Int = 0
    
    func increment() {
        value += 1
    }
}

var c = Counter()

let queue = DispatchQueue(label: "queue1", attributes: .concurrent)

for _ in 0..<1000 {
    queue.async {
        c.increment()
    }
}

sleep(5)
print(c.value)

위의 코드는 클래스인 Counter를 사용하여 값을 증가시키는 코드입니다.

Counter 클래스는 참조 타입(Reference Type)이므로 여러 쓰레드에서 동시에 값을 변경하는 경우 동시성 문제가 발생할 수 있습니다.

따라서 위의 코드는 1000보다 적은 값이 출력될 수 있습니다.

 

 

+ Q. 구조체를 이용하면 함수형 프로그래밍을 쉽게 구현할 수 있는 이유가 무엇인지 설명해 줘

 

A. 구조체(struct)는 값 타입(Value Type)입니다. 이는 구조체의 인스턴스가 복사되어 전달된다는 것을 의미합니다.

이와 달리 클래스(class)는 참조 타입(Reference Type)으로 인스턴스가 메모리에 한 번 생성되고, 그 이후로는 참조만 전달됩니다.

 

값 타입은 복사되어 전달되기 때문에 상태 변경이나 부작용(side effect)을 일으키지 않습니다.

이는 함수형 프로그래밍에서 중요한 개념입니다.

함수형 프로그래밍에서는 부작용을 최소화하고, 대신 입력값과 출력값 사이의 관계에 집중합니다.

 

Swift에서는 함수형 프로그래밍을 쉽게 구현할 수 있도록 구조체를 이용할 수 있습니다.

구조체를 이용하여 함수형 프로그래밍을 구현하면, 상태 변경이나 부작용을 일으키지 않기 때문에 코드의 안정성이 높아지며 디버깅이 쉬워집니다.

 

다음은 구조체를 이용하여 함수형 프로그래밍을 구현하는 예제입니다.

struct Point {
    var x: Int
    var y: Int
    
    func moveX(_ deltaX: Int) -> Point {
        Point(x: x + deltaX, y: y)
    }
    
    func moveY(_ deltaY: Int) -> Point {
        Point(x: x, y: y + deltaY)
    }
}

let origin = Point(x: 0, y: 0)

let p1 = origin.moveX(10)
let p2 = p1.moveY(20)

print(p2) // Point(x: 10, y: 20)

위의 예제는 Point라는 구조체를 이용하여 함수형 프로그래밍을 구현한 것입니다.

moveX와 moveY 함수는 입력값을 받아서 새로운 Point 인스턴스를 반환합니다.

이때, 원래의 인스턴스는 그대로 유지되기 때문에 상태 변경이 일어나지 않습니다.

이러한 방식으로 함수형 프로그래밍을 구현할 수 있습니다.

 

 

 

 

2. Class를 사용하는 경우

- 상속을 필요로 하는 경우: 클래스는 참조 타입(Reference Type)으로 작동하기 때문에, 하나의 객체를 여러 인스턴스에서 공유할 수 있습니다. 이를 이용하여 상속 등의 기능을 구현할 수 있습니다.

- 객체의 라이프 사이클 관리: 클래스는 객체의 라이프 사이클을 직접 관리할 수 있기 때문에, 객체가 생성되고 제거될 때의 동작을 제어할 수 있습니다. 따라서, 객체의 라이프 사이클을 관리해야 하는 경우에 적합합니다.

- 동적 바인딩: 클래스는 런타임 시에 메소드와 프로퍼티에 접근할 수 있도록 동적 바인딩을 지원합니다. 이를 이용하여 런타임에 객체의 동작을 변경할 수 있습니다.

class Animal {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func makeSound() {
        print("...")
    }
}

class Dog: Animal {
    override func makeSound() {
        print("woof!")
    }
}

class Cat: Animal {
    override func makeSound() {
        print("meow!")
    }
}

let dog = Dog(name: "Buddy")
let cat = Cat(name: "Fluffy")

dog.makeSound() // woof!
cat.makeSound() // meow!

 

 

 

3. Enum을 사용하는 경우

- 제한된 값 집합: 열거형은 한정된 값 집합을 표현하는데 적합합니다. 예를 들어, 컬러, 요일, 월 등은 한정된 값 집합을 가지기 때문에, 이러한 경우에 적합합니다.

- 연관된 값 집합: 열거형은 값 집합뿐만 아니라, 각각의 값에 대한 연관된 데이터를 가질 수 있습니다. 예를 들어, 유저의 상태를 나타내는 경우에는 유저 상태값과 함께 연관된 데이터를 포함하는 것이 유용합니다.

- 코드 가독성: 열거형은 값을 명시적으로 정의하기 때문에, 코드의 가독성을 높이는 데 유용합니다.

enum Gender {
    case male
    case female
    case other
}

struct Person {
    var name: String
    var age: Int
    var gender: Gender
}

let person1 = Person(name: "John", age: 25, gender: .male)
let person2 = Person(name: "Jane", age: 30, gender: .female)
let person3 = Person(name: "Alex", age: 35, gender: .other)

위의 예제에서는 Gender라는 열거형을 사용하여 세 가지 값(male, female, other)을 나타내고,

Person이라는 구조체에서 Gender 타입의 gender 속성을 사용합니다.

이러한 구조체를 사용하면, Person 객체를 생성할 때 각각의 속성을 명시적으로 정의할 수 있으며, 코드의 가독성도 높아집니다.

 

 

 

출처: ChatGPT(https://chat.openai.com/chat)