정보/레벨 1

Swift의 기본 데이터 타입과 컬렉션(Collection) 타입에는 어떤 것들이 있나요?

밤새는 탐험가89 2024. 11. 10. 10:06

 Swift는 타입 안전성과 타입 추론을 강조하는 언어로, 코드의 가독성을 높이고 오류를 줄이는 데 초점을 두고 있습니다. Swift의 기본 데이터 타입컬렉션 타입은 이러한 목표를 지원하는 중요한 요소입니다.

 

1. 기본 데이터 타입 (Primitive Data Types)

  • Int: 정수를 나타내는 타입으로, Int는 64비트 시스템에서 64비트 정수를 나타냅니다. 특정 크기를 필요로 할 경우 Int8, Int16, Int32, Int64와 같은 명시적인 타입도 사용할 수 있습니다.
  • DoubleFloat: 실수를 나타내는 타입입니다. Double은 64비트 부동 소수점 숫자, Float은 32비트 부동 소수점 숫자를 사용합니다. Double은 보다 높은 정밀도를 제공하기 때문에 Swift에서 기본으로 사용됩니다.
  • Bool: 참과 거짓을 나타내는 논리 타입으로, true 또는 false 값을 가질 수 있습니다. 조건문과 논리 연산에 주로 사용됩니다.
  • String: 문자 시퀀스를 나타내는 타입으로, 텍스트 데이터를 처리합니다. String은 Unicode 지원을 통해 다양한 언어를 포함하며, + 연산자를 통해 쉽게 결합할 수 있습니다.
  • Character: 단일 문자를 나타내는 타입으로, 하나의 문자만 저장하는 데 사용됩니다. String과 다르게 Character는 텍스트의 개별 요소를 처리할 때 유용합니다.

 

2. 컬렉션 타입 (Collection Types)

Swift의 컬렉션 타입은 데이터를 정리하고 관리하는 데 최적화된 타입들로, 주로 세 가지가 있습니다.

  • Array: 순서가 있는 요소의 집합을 나타내며, 동일한 타입의 데이터만 포함할 수 있습니다. Array는 인덱스를 통해 요소에 접근할 수 있어 편리하고, 값의 추가나 삭제가 가능합니다. var numbers: [Int] = [1, 2, 3]와 같은 형식으로 사용합니다.
  • Set: 순서가 없는 고유한 요소의 집합을 나타냅니다. Set은 빠른 검색이 가능하고, 중복 값을 허용하지 않기 때문에 데이터의 고유성을 보장하는 데 유리합니다. 선언할 때는 var uniqueNumbers: Set<Int> = [1, 2, 3]와 같이 사용합니다.
  • Dictionary: 키와 값의 쌍으로 구성된 컬렉션으로, 키를 통해 값에 접근할 수 있습니다. Dictionary는 데이터를 키로 정렬하고 빠르게 접근할 수 있어 특정 항목을 식별하고 찾는 데 효과적입니다. 예를 들어, var studentScores: [String: Int] = ["Alice": 85, "Bob": 92]와 같이 사용합니다.

이러한 기본 데이터 타입과 컬렉션 타입은 Swift의 타입 안정성과 안전성을 강화하는 주요 요소로, 각각의 타입이 고유한 특성과 사용 목적을 가지고 있어 다양한 상황에서 효율적으로 데이터를 관리하고 조작할 수 있게 합니다.

 

 

 

값 타입(Value Type)과 참조 타입(Reference Type)의 차이점은 무엇인가요?

 

Swift에서 값 타입과 참조 타입은 메모리 관리와 데이터의 전달 방식을 결정하는 중요한 개념입니다. 이를 이해하는 것은 코드의 성능과 동작을 예측하는 데 매우 유용합니다.

 

값 타입 (Value Type)

 값 타입은 데이터를 복사하여 전달하는 방식으로 작동합니다. 즉, 값을 다른 변수나 상수에 할당하거나 함수에 전달할 때, 해당 값의 복사본이 생성되어 전달됩니다. 값 타입을 사용하는 데이터는 독립적인 복사본을 가지기 때문에, 한 인스턴스의 값을 수정해도 다른 인스턴스에 영향을 주지 않습니다. Swift에서 값 타입은 주로 구조체(struct), 열거형(enum), 그리고 기본 데이터 타입들(Int, Double, Bool 등)이 해당됩니다.

struct Point {
    var x: Int
    var y: Int
}

var point1 = Point(x: 10, y: 20)
var point2 = point1  // point1의 복사본이 생성됨

point2.x = 30  // point2를 수정해도 point1에 영향 없음
print(point1.x)  // 출력: 10

 

위의 예시에서 point1을 point2에 할당하면, point1의 복사본이 point2에 저장됩니다. 따라서 point2의 값을 변경해도 point1에는 영향을 미치지 않습니다.

 

참조 타입 (Reference Type)

참조 타입은 데이터의 참조를 공유하여 전달하는 방식으로 작동합니다. 참조 타입의 인스턴스를 다른 변수나 상수에 할당하거나 함수에 전달할 때, 새로운 참조가 생성될 뿐 원본 데이터는 그대로 공유됩니다. 따라서 여러 변수나 상수가 동일한 인스턴스를 가리키며, 한쪽에서 데이터를 수정하면 모든 참조에 영향을 미칩니다. Swift에서 클래스(class)가 대표적인 참조 타입입니다.

class Point {
    var x: Int
    var y: Int
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
}

var point1 = Point(x: 10, y: 20)
var point2 = point1  // point1의 참조가 point2에 할당됨

point2.x = 30  // point2를 수정하면 point1에도 영향
print(point1.x)  // 출력: 30

 

위의 예시에서 point1을 point2에 할당하면 point1의 참조가 point2에 복사됩니다. 따라서 point2의 값을 수정하면 point1의 값도 함께 변경됩니다.

요약

  • 값 타입 (Value Type): 데이터를 복사하여 전달하며, 독립적인 복사본을 가지므로 한 인스턴스의 변경이 다른 인스턴스에 영향을 미치지 않습니다. 구조체(struct), 열거형(enum), 기본 데이터 타입이 이에 해당합니다.
  • 참조 타입 (Reference Type): 데이터의 참조를 공유하여 전달하므로, 여러 참조가 동일한 인스턴스를 가리키며 한쪽에서 데이터를 수정하면 모든 참조에 영향을 미칩니다. 클래스(class)가 이에 해당합니다.

이러한 차이를 이해하면, 데이터의 독립성과 효율성을 보장하기 위해 값 타입과 참조 타입을 상황에 맞게 선택할 수 있습니다. 예를 들어, 데이터 변경이 불필요하거나 독립성을 유지해야 하는 경우 값 타입, 공유된 리소스가 필요하거나 객체 간의 관계를 표현해야 하는 경우 참조 타입을 선택하는 것이 좋습니다.

 

 

구조체(Struct)와 클래스(Class)의 사용 시기는 어떻게 구분하나요?

 

 Swift에서 구조체와 클래스는 데이터를 정의하고 동작을 추가할 수 있는 중요한 사용자 정의 타입이지만, 각각의 사용 시기는 데이터 관리의 목적과 메모리 사용 방식에 따라 달라집니다. Swift에서는 구조체가 값 타입, 클래스가 참조 타입이기 때문에 이를 고려하여 두 가지 타입을 구분해 사용하는 것이 좋습니다.

 

구조체(Struct)를 사용하는 시기

구조체는 **값 타입(Value Type)**으로, 데이터를 복사하여 전달하는 특성을 가지고 있습니다. 아래와 같은 상황에서는 구조체를 사용하는 것이 더 적합합니다.

  1. 독립적인 데이터 복사가 필요한 경우:
    • 구조체는 변수를 다른 변수에 할당하거나 함수에 전달할 때, 데이터의 복사본을 생성합니다. 따라서 한 인스턴스가 수정되더라도 다른 인스턴스에 영향을 주지 않는 독립성이 필요할 때 유용합니다.
  2. 데이터 중심의 객체를 정의할 때:
    • 구조체는 주로 데이터를 저장하고 전달하는 데 초점을 맞춘 간단한 모델 객체에 적합합니다. 예를 들어, 좌표, 색상, 크기 등을 정의하는 Point, Size, Rectangle 등의 모델은 데이터를 캡슐화하는 데 주로 사용됩니다.
  3. 상속이 필요 없는 경우:
    • 구조체는 상속을 지원하지 않기 때문에, 상속 계층이 필요 없는 단순한 데이터 모델에 적합합니다. 상속이 불필요하고, 기능이 제한적인 객체를 정의할 때 구조체를 사용하는 것이 적절합니다.
  4. Immutable 데이터로 관리하려는 경우:
    • 구조체는 let으로 선언하면 불변성을 가집니다. 이로 인해 인스턴스를 변경할 수 없는 상태로 관리할 수 있어, 데이터가 변경되지 않아야 하는 상황에서 안전하게 사용할 수 있습니다.
struct Point {
    var x: Int
    var y: Int
}

var point1 = Point(x: 10, y: 20)
var point2 = point1  // point1의 복사본이 생성됨

point2.x = 30  // point2를 수정해도 point1에 영향 없음
print(point1.x)  // 출력: 10

 

 

클래스(Class)를 사용하는 시기

클래스는 **참조 타입(Reference Type)**으로, 데이터의 참조를 공유하는 특성을 가지고 있습니다. 다음과 같은 경우에는 클래스를 사용하는 것이 적절합니다.

  1. 데이터의 공유나 상호 참조가 필요한 경우:
    • 클래스는 참조를 통해 데이터를 공유하므로, 여러 인스턴스가 동일한 데이터에 접근하고 변경해야 하는 경우 유용합니다. 예를 들어, UI 요소나 데이터의 동기화가 필요한 모델에서 클래스 사용이 적합합니다.
  2. 상속 계층이 필요한 경우:
    • 클래스는 상속을 지원하므로, 부모 클래스의 기능을 확장하거나 재정의해야 할 때 유용합니다. 다형성을 활용해야 하거나, 공통된 특성과 기능을 계층적으로 정의해야 할 때 클래스를 사용하는 것이 좋습니다.
  3. ARC(Automatic Reference Counting)가 필요할 경우:
    • 클래스는 참조 타입으로, Swift의 자동 참조 카운팅(ARC)을 통해 메모리 관리를 수행합니다. 복잡한 객체 그래프나, 특정 객체의 수명 주기를 관리해야 하는 경우 클래스가 적합합니다.
  4. 객체의 정체성을 유지하고 상태를 공유해야 하는 경우:
    • 동일한 인스턴스를 여러 위치에서 참조해야 하는 경우, 클래스의 참조 타입 특성이 유용합니다. 예를 들어, 게임의 캐릭터, 네트워크 세션 등은 상태가 지속적으로 업데이트되고, 여러 위치에서 상태를 공유해야 하므로 클래스가 적합합니다.
class Point {
    var x: Int
    var y: Int
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
}

var point1 = Point(x: 10, y: 20)
var point2 = point1  // point1의 참조가 point2에 할당됨

point2.x = 30  // point2를 수정하면 point1에도 영향
print(point1.x)  // 출력: 30

 

요약

  • 구조체(Struct):
    • 독립적인 데이터 복사 및 불변성이 필요한 경우.
    • 데이터 중심의 단순한 모델에 적합.
    • 상속이 필요 없고, 기능이 단순할 때 사용.
  • 클래스(Class):
    • 데이터의 공유 및 상속이 필요한 경우.
    • 상태 공유가 필요한 객체나, 객체 간의 관계가 중요한 경우.
    • 다형성, 메모리 관리(ARC) 등이 필요한 경우.

이처럼 구조체는 주로 불변성, 독립성, 경량 데이터를 처리할 때 유용하며, 클래스는 복잡한 상태 관리, 공유, 상속 구조가 필요할 때 사용하는 것이 바람직합니다. 이를 통해 코드의 안정성과 가독성을 높일 수 있습니다.

 

 

 

열거형(Enum)의 원시값(Raw Value)과 연관값(Associated Value)은 무엇인가요?

 

열거형은 Swift에서 그룹화된 값을 처리할 때 매우 유용한 타입입니다. 일반적으로 상태나 특정 범주의 값을 정의하는 데 사용됩니다. Swift의 열거형은 원시값과 연관값을 지원하여, 열거형을 더 유연하고 다양한 방식으로 사용할 수 있도록 합니다.

 

원시값 (Raw Value)

원시값은 열거형의 각 케이스에 고정된 기본 값을 지정할 수 있는 기능입니다. 원시값은 열거형을 정의할 때 모든 케이스에 동일한 타입의 값을 설정해야 합니다. 원시값은 기본적으로 Int, String, Double 등의 리터럴 타입을 지원합니다.

  • 원시값은 열거형의 각 케이스에 미리 할당된 값이며, 각 케이스는 고유한 원시값을 가질 수 있습니다.
  • 원시값이 있는 열거형은 rawValue 프로퍼티를 통해 각 케이스의 값을 참조할 수 있습니다.
  • 원시값을 가진 열거형은 초기화할 때 rawValue를 통해 특정 케이스로 초기화할 수 있으며, 이 경우 초기화가 실패할 수 있으므로 옵셔널 타입을 반환합니다.
enum Direction: String {
    case north = "N"
    case south = "S"
    case east = "E"
    case west = "W"
}

let currentDirection = Direction.north
print(currentDirection.rawValue)  // 출력: "N"

 

 

위의 예시에서 Direction 열거형은 String 타입의 원시값을 사용하며, 각 케이스에는 "N", "S", "E", "W"의 원시값이 할당되어 있습니다. currentDirection.rawValue를 통해 north 케이스의 원시값인 "N"을 참조할 수 있습니다.

 

enum Weekday: Int {
    case monday = 1
    case tuesday
    case wednesday
    case thursday
    case friday
    case saturday
    case sunday
}

// 월요일의 원시값 확인
let today = Weekday.monday
print(today.rawValue)  // 출력: 1

 

여기서 Weekday 열거형은 각 요일에 고정된 숫자를 원시값으로 가집니다. monday의 원시값을 1로 지정하고, 나머지 요일들은 자동으로 순차적으로 값이 할당됩니다. today.rawValue를 통해 monday 케이스의 숫자 원시값 1을 확인할 수 있습니다.

이처럼 원시값은 열거형의 각 케이스가 고정된 값을 가지도록 할 때 유용합니다.

 

연관값 (Associated Value)

연관값은 열거형의 각 케이스가 개별적인 값을 가질 수 있는 기능입니다. 원시값과 달리, 연관값은 열거형의 각 케이스마다 서로 다른 데이터 타입과 값을 가질 수 있습니다. 연관값은 열거형 인스턴스가 생성될 때 저장되며, 특정 상황이나 값을 저장할 수 있어 열거형을 더욱 유연하게 만듭니다.

  • 연관값은 각 케이스마다 필요한 값을 저장할 수 있으며, 케이스마다 서로 다른 데이터 타입을 가질 수 있습니다.
  • 열거형의 인스턴스를 생성할 때 연관값을 함께 설정할 수 있으며, 이를 통해 추가적인 데이터를 저장하고 활용할 수 있습니다.
enum Media {
    case photo(filename: String)
    case video(filename: String, duration: Int)
    case audio(filename: String, bitrate: Int)
}

let imageFile = Media.photo(filename: "photo.jpg")
let videoFile = Media.video(filename: "video.mp4", duration: 120)

 

 위의 예에서 Media 열거형은 각 미디어 타입에 맞는 데이터를 연관값으로 저장합니다. photo 케이스는 이미지 파일 이름을 저장하고, video 케이스는 파일 이름과 비디오의 길이를 함께 저장합니다. 이처럼 연관값은 케이스에 따라 다른 타입과 개수의 데이터를 가질 수 있어 열거형을 매우 유연하게 만들어 줍니다.

 

enum Fruit {
    case apple(count: Int)          // 사과의 개수를 저장
    case orange(origin: String)      // 오렌지의 산지를 저장
    case banana(ripeness: String)    // 바나나의 숙성 정도를 저장
}

// 과일 정보 생성
let myApple = Fruit.apple(count: 5)
let myOrange = Fruit.orange(origin: "California")
let myBanana = Fruit.banana(ripeness: "Fully Ripe")

 

여기서 Fruit 열거형은 각 과일 종류마다 다른 정보를 저장할 수 있습니다:

  • apple은 count라는 사과의 개수를,
  • orange는 origin이라는 산지를,
  • banana는 ripeness라는 숙성 정도를 저장합니다.

이처럼 연관값을 사용하면 각 케이스마다 서로 다른 데이터를 담을 수 있어, 같은 열거형 안에서도 각기 다른 정보를 포함할 수 있습니다.

 

 

🔥 만약 count를 모든 과일에 공통적으로 사용하고 싶다면?

모든 과일의 count 값을 열거형 외부에 정의할 수도 있습니다. 예를 들어, 구조체를 사용해 count를 공통 속성으로 관리하는 방법입니다.

struct FruitInfo {
    var count: Int
    var type: FruitType
}

enum FruitType {
    case apple
    case orange(origin: String)
    case banana(ripeness: String)
}

// 과일 정보 생성
let myApple = FruitInfo(count: 5, type: .apple)
let myOrange = FruitInfo(count: 3, type: .orange(origin: "California"))
let myBanana = FruitInfo(count: 2, type: .banana(ripeness: "Fully Ripe"))

 

이렇게 하면 count는 FruitInfo 구조체에서 공통으로 관리하고, 각 과일의 개별 정보는 FruitType 열거형에서 다룰 수 있어 더 깔끔하게 데이터를 관리할 수 있습니다.