[Swift] 자료형 (2)

김민석·2022년 6월 10일
1

Swift

목록 보기
5/5
post-thumbnail

지난 포스팅에서 Swift의 자료형에 대해 알아보았는데, 이어서 조금 더 어려운 알아보도록 하겠습니다.

1. 데이터 타입 안심

Swift 언어는 데이터타입에 민감한 언어입니다.
Int 변수에 Doulbe 타입을 할당하게되면 컴파일 오류가 발생합니다.
이런 오류가 발생해서 좋을점이 있을까요?
개발자 입장에서는 컴파일러가 직접 알려주지 않으면 나중에 이와 관련된 오류가 발생했을 시에 오류를 찾아내는 것이 쉽지 않습니다.

1.1 타입 추론

하지만 Swift는 컴파일 오류로 알려주기 때문에 서로 다른 타입의 값을 할당하는 실수를 줄일 수 있습니다.

Swift에서는 변수나 상수를 선언할 때 타입을 작성하거나, 생략을 할 수 있는데 생략을 하게되면 컴파일러가 할당된 값을 기준으로 타입을 결정합니다.

var age = 28
print(type(of: age)) // Int

age = 28.5 // 컴파일 에러 

2. 타입 별칭

Swift는 기본 자료형, 만든 자료형 등 데이터 타입에 임의로 다른 이름을 부여할 수 있습니다. typealias 키워드를 사용하여 부여하고, 그 후 기존 타입과 추가된 별칭을 모두 사용할 수 있습니다.

struct Coord {
    let x: Double
    let y: Double
}

typealias MyCoord = Coord
typealias MyString = String

let coord: MyCoord = MyCoord(x: 123.0, y: 12.3)
let myName: MyString = "만도스"

print(type(of: myName)) // String

3. 튜플

튜플 자료형은 타입이 따로 지정되어 있지는 않습니다. 지정된 데이터들의 묶음이라고 표현할 수 있고, 프로그래머 입장에서 만드는 타입입니다.
예시를 보면 이해하실 수 있으실 겁니다.

// String, Int, Double 타입을 갖는 튜플
var person: (String, Int, Double) = ("만도스", 28, 173.5)

// 인덱스로 값을 얻을 수 있습니다.
print("이름: \(person.0), 나이: \(person.1), 키: \(person.2)") // 이름: 만도스, 나이: 28, 키: 173.5

// 인덱스로 값을 할당할 수 있습니다.
person.0 = "mandos"
person.2 = 187.8

print("이름: \(person.0), 나이: \(person.1), 키: \(person.2)") // 이름: mandos, 나이: 28, 키: 187.8

// 튜플의 요소에 이름을 붙혀서 사용할 수도 있습니다.
var newPerson: (name: String, age: Int, height: Double) = (name: "만도스", age: 28, height: 173.5)

// 값을 할당할 때는 이름 생략 가능합니다.
newPerson = ("만도스", 28, 173.5)

// 요소로 값을 얻을 수 있습니다.
print("이름: \(newPerson.name), 나이: \(newPerson.age), 키: \(newPerson.height)") // 이름: 만도스, 나이: 28, 키: 173.5

// 요소로 값을 할당할 수 있습니다.
newPerson.name = "mandos"

// 인덱스로 값 할당도 가능합니다.
newPerson.2 = 187.8

// 인덱스를 통해 값을 얻을 수도 있습니다.
print("이름: \(newPerson.0), 나이: \(newPerson.1), 키: \(newPerson.2)") // 이름: mandos, 나이: 28, 키: 187.8

튜플도 마찬가지로 타입 별칭이 가능합니다.

4. 컬렉션

Swift에는 컬렉션 타입이라는 것을 제공하는데, 컬렉션 타입은 배열, 딕셔너리, 세트 등이 있습니다.

4.1 배열(Array)

배열은 데이터의 순서가 지정된 컬렉션 타입입니다.
let, var 키워드를 사용해 선언할 수 있습니다.
한번 선언하게되면 자동으로 배열의 크기를 조절하기 때문에 요소의 삭제, 삽입이 자유롭습니다.

// 빈 배열의 선언 방법
var emptyArray1: Array<String> = Array<String>()
var emptyArray2: Array<String> = []
var emptyArray3: [String] = Array<String>()
var emptyArray4: [String] = [String]()

// 배열 선언 방법
var array1: Array<String> = ["a", "b", "c"]
var array2: [String] = ["a", "b", "c"]

배열은 인덱스를 통해 요소에 접근할 수 있습니다.
인덱스는 0 부터 시작하게 되고 잘못된 인덱스에 접근하려고 하면 오류가 발생합니다.

var nums: [Int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(nums[1])  // 2 (인덱스는 0부터 시작)

// 요소에 접근해 값을 할당해줄 수 있습니다.
nums[1] = 1
print(nums[1])  // 1

let animals = ["사자", "코끼리", "얼룩말", "토끼", "기니피그"]
print(animals[1])   // 코끼리

// 상수로 선언되었다면 값 할당 시 오류가 발생합니다.
animals[1] = "타조" // 오류 발생!

배열은 다양한 프로퍼티가 존재합니다.
코드를 보며 이해하시면 편하실 것입니다.

var nums: [Int] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(nums.count) // 10
print(nums.first) // Optional(0)
print(nums.last)  // Optional(9)

nums.append(10)
print(nums)       // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(nums.removeLast()) // 10

print(nums) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
nums.insert(-1, at: 0)  //
print(nums) // [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(nums.removeFirst()) // -1
print(nums) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

nums.append(contentsOf: [10, 11])
print(nums) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

print(nums.remove(at: 10))  // 10
print(nums) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11]

print(nums[1...3])  // [1, 2, 3]
print(nums[1..<3])  // [1, 2
print(nums[1...]) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 11]

nums = [1, 1, 1, 2, 2, 3]
// 1이 가장 먼저 등장하는 인덱스
print(nums.firstIndex(of: 1))   // Optional(0)

// 1이 가장 마지막에 등장하는 인덱스
print(nums.lastIndex(of: 1))    // Optional(2)

이외에도 많은 프로퍼티가 존재합니다. 직접 공부하면서 차차 알아보도록 합시다.

4.2 딕셔너리(Dictionary)

딕셔너리는 요소들이 순서와 관계없이 키와 값의 쌍으로 구성된 컬렉션 타입입니다.
키가 여러개일 수는 있지만 중복될 수는 없습니다. 즉, 하나의 딕셔너리안에 키는 같은 이름을 중복해서 사용할 수 없습니다.
딕셔너리에서는 가 값을 도출할 수 있는 유일한 식별자 입니다.

딕셔너리는 배열과 마찬가지로 let, var 키워드를 사용해서 선언할 수 있습니다.

// 빈 딕셔너리 선언 방법
var emptyDictionary1: Dictionary<String, Int> = Dictionary<String, Int>()
var emptyDictionary2: [String: Int] = Dictionary<String, Int>()
var emptyDictionary3: [String: Int] = [String: Int]()
var emptyDictionary4: [String: Int] = [:]

// 값이 있는 딕셔너리 초기 선언
var dictionary: [String: Int] = ["만도스": 28]

print(dictionary["만도스"]) // Optional(28)

존재하는 키를 가지고 딕셔너리에 접근하려고할 때도 Optional 문법이 나오게 됩니다.
이는 존재하지 않은 키를 가지고 딕셔너리에 접근하게 되면 nil 이 반환되기 때문에 기본이 옵셔널이기 때문입니다.

if let mandosAge = dictionary["만도스"] {
    print(mandosAge)    // 28
}

if - let 문법을 통해서 값을 도출할 수 있습니다. 이는 옵셔널 파트에서 좀 더 자세히 다룰 예정입니다.

var dictionary: [String: Int] = ["만도스": 28, "루피": 18, "조로": 29, "쵸파": 2]
print(dictionary["나미"]) // nil

// 변수로 선언했다면 값을 할당해 줄 수 있습니다.
dictionary["루피"] = 48
print(dictionary["루피"]) // Optional(48)

dictionary.removeValue(forKey: "조로")

// 딕셔너리는 순서가 없기 때문에 뒤죽박죽 나올 수 있습니다.
print(dictionary)   // ["쵸파": 2, "만도스": 28, "루피": 48]
print(dictionary["조로"]) // nil

// 키가 존재하지 않으면 default 값을 설정해줄 수 있습니다.
print(dictionary["조로", default: 0]) // 0

4.3 세트(Set)

Set 자료형은 데이터의 순서와 상관없이 하나의 묶음으로 저장하며, 중복된 값이 존재하지 않습니다.
즉, 집합과 동일합니다.
세트는 순서가 중요하지 않거나, 각 요소가 유일한 값일 때 주로 사용합니다.
Set는 Hashable 프로토콜을 따르기 때문에 Hashable한 값인 경우에만 사용할 수 있습니다.
Set도 마찬가지로 let, var 키워드르 사용해 선언할 수 있습니다.

// 빈 세트 선언
let emptySet1: Set<String> = Set<String>()
let emptySet2: Set<String> = []

// 값이 있는 세트 선언
var set1: Set<String> = ["a", "b", "c"]
print(set1) // ["b", "a", "c"] 순서가 없습니다.

var set2: Set<String> = ["a", "a", "b", "b"]
print(set2) // ["b", "a"]   중복된 값이 없습니다.

Set도 여러 프로퍼티가 존재하는데 한번 알아봅시다.

var setNum1: Set<Int> = [1, 2, 3, 4, 5]
setNum1.insert(6)    // 6을 추가합니다.
print(setNum1)  // [1, 3, 5, 2, 4, 6]

print(setNum1.contains(5))  // true
print(setNum1.contains(8))  // false

var setNum2: Set<Int> = [4, 5, 6, 7, 8]
let removed = setNum2.remove(8)
print(removed)  // Optional(8)
print(setNum2)  // [6, 7, 5, 4]

// 교집합
let intersectSect: Set<Int> = setNum1.intersection(setNum2)
print(intersectSect)    // [5, 4, 6]

// 여집합들의 합
let symmertricDiffSe: Set<Int> = setNum1.symmetricDifference(setNum2)
print(symmertricDiffSe) //  [1, 2, 3, 7]

// 합집합
let unionSet: Set<Int> = setNum1.union(setNum2)
print(unionSet) // [7, 1, 5, 3, 4, 2, 6]

// 차집합
let substractSet: Set<Int> = setNum1.subtracting(setNum2)
print(substractSet) // [3, 1, 2]

5. 열거형(enum)

열거형은 관련있는 항목들을 묶어서 표현하는 타입입니다.
열거형은 제한된 선택지를 주고싶고 싶거나, 정해진 값들에서만 입력을 받을 때 사용하는 자료형입니다.

열거형은 RawValue 값의 형태로 실제 값을 가질 수도 있고, 연관 값을 사용하는 방식도 있습니다.

import UIKit

enum WeekDay {
    case mon
    case tue
    case wed
    case thu
    case fri
}

WeekDay라는 열거형에 여러 항목을 정의해줬습니다.

이제 열거형 변수를 생성해보겠습니다.

var today: WeekDay = .fri

today = .mon

변수로 선언하게되면 값을 변경해줄 수 있습니다.

5.1 원시 값 (raw Value)

열거형에는 RawValue 값도 가질 수 있다고 했으니 Rawvalue도 확인해봅시다

enum WeekDay: String {
    case mon
    case tue
    case wed
    case thu
    case fri = "금요일"
}

var today: WeekDay = .wed
print(today.rawValue)   // wed

var fri: WeekDay = .fri
print(fri.rawValue)     // 금요일

먼저 WeekDay 열거형의 rawValue를 String 형태로 지정해줬습니다.
문자열 형식의 rawValue를 지정해줬다면 각각 case에 그 값으로 띄게되고, 직접 입력하였다면 입력한 값이 rawValue 형태로 나타납니다.

만약 정수 타입으로 지정해줬다면 첫 번째 항목을 기준으로 0부터 1씩 늘어난 값을 갖게 됩니다.

enum WeekDay: Int {
    case mon
    case tue
    case wed
    case thu
    case fri = 999
}

var today: WeekDay = .wed
print(today.rawValue)   // 2

var fri: WeekDay = .fri
print(fri.rawValue)     // 999

rawValue를 갖고 있는 열거형이라면, rawValue를 통해 열거형 변수나 상수를 생성해줄 수 있습니다.
올바르지 않은 rawValue를 통해 생성하려고 하면 nil을 반환합니다.

enum WeekDay: String {
    case mon
    case tue
    case wed
    case thu
    case fri = "금요일"
}

var today = WeekDay(rawValue: "wed")
print(today)    // Optional(.wed)

var fri = WeekDay(rawValue: "fri")
print(fri)      // nil

5.3 연관 값(Associated Values)

Swift의 열거형의 각 항목이 연관 값을 가지게되면, 공용체 형태를 띌 수 있습니다.
열거형 내의 case가 자신과 관련된 연관 값을 가질 수 있고,, 이는 각 항목 옆에 소괄호로 묶어서 표현할 수 있습니다.

enum Phone {
    case iPhone(deviceType: String)
    case Android
}

var myPhone: Phone = .iPhone(deviceType: "XS")

5.4 항목 순회

열거형에 포함된 모든 항목들을 알아야 할 때 항목 순회를 사용할 수 있습니다
열거형의 CaseIterable 프로토콜을 채택해주게 되면 allCases라는 이름의 타입 프로퍼티를 통해 모든 케이스의 컬렉션을 생성해줍니다.

enum WeekDay: CaseIterable {
    case mon
    case tue
    case wed
    case thu
    case fri
}

let allCases: [WeekDay] = WeekDay.allCases

for allCase in allCases {
    print(allCase)
    // mon
    // tue
    // wed
    // thu
    // fri
}

5.5 순환 열거형

순환 열거형은 열거형 항목의 연관 값이 열거형 자신의 값이고자 할 때 사용합니다.
indirect 키워드를 사용하여 적용할 수 있는데 특정 항목에만 한정하고 싶다면 case 키워드 앞에 indirect를 붙이고, 열거형 전체에 적용하고 싶다면 enum 앞에 붙이면 됩니다.

enum NumbersExpression {
    case number(Int)
    indirect case add(Self, Self)
    indirect case mul(Self, Self)
}

let five: NumbersExpression = NumbersExpression.number(5)
let four: NumbersExpression = NumbersExpression.number(4)
let sum: NumbersExpression = NumbersExpression.add(five, four)
let mul: NumbersExpression = NumbersExpression.mul(sum, NumbersExpression.number(2))

func calculate(_ expression : NumbersExpression) -> Int {
    switch expression {
    case .number(let int):
        return int
    case .add(let left, let right):
        return calculate(left) + calculate(right)
    case .mul(let left, let right):
        return calculate(left) * calculate(right)
    }
}

let result: Int = calculate(mul)
print(result)   // 18

자료형 포스팅은 여기서 마치도록 하겠습니다
틀린점이나 지적 및 궁금하신점 댓글로 달아주세요 환영입니다.
감사합니다.

profile
안녕하세요 95년생 김민석입니다

0개의 댓글