Swift) ch4.데이터 타입 고급
ch4.데이터 타입 고급
4.2 타입 별칭
- 스위프트에서 기본으로 제공하는 데이터 타입이든, 사용자가 임의로 만든 데이터 타입이든 이미 존재하는 데이터 타입에 임의로 다른 이름을 부여할 수 있다.
- 기존에 사용하던 데이터 타입의 이름과 프로그래머가 만들어준 이름 모두 사용할 수 있다.
- typealias 키워드를 사용한다.
typealias MyInt = Int
let age: MyInt = 100
var year: Int = 2000
// MyInt는 Int로 같은 타입이기 때문에 밑 예시가 가능하다.
year = age
print(year) // 100
4.3 튜플
- 타입의 이름이 따로 지정되어 있지 않은, 프로그래머 마음대로 만드는 타입으로 '지정된 데이터의 묶음' 이라고도 표현함.
- 튜플에 포함될 데이터의 개수는 자유롭게 정할 수 있음.
- 튜플의 요소들에게 이름을 붙여줄 수 있다, 필수는 아니지만 차후에 요소들이 어떤 의미를 가지고 있는지 유추하기 어려운 상황을 피하기 위해 사용한다.
// String, Int, Double 타입을 갖는 튜플
var student: (name: String, totalScore: Int, average: Double) = ("Hoonsbrand", 300, 175.3)
// 인덱스나 요소이름을 통해 값을 추출할 수 있음.
print("이름: \(student.name), 과목 합산점수: \(student.1), 평균점수: \(student.2)")
// 이름: Hoonsbrand, 과목 합산점수: 300, 평균점수: 175.3
// 인덱스나 요소 이름을 통해 값을 할당할 수 있음.
student.totalScore = 200
student.2 = 150.8
print("이름: \(student.name), 과목 합산점수: \(student.1), 평균점수: \(student.average)")
// 이름: Hoonsbrand, 과목 합산점수: 200, 평균점수: 150.8
4.4 컬렉션형
Swift의 컬렉션 타입에는 배열, 딕셔너리, 세트 등이 있다.
4.4.1 배열
- 같은 타입의 데이터를 일렬로 나열한 후 순서대로 저장하는 형태의 컬렉션 타입
- Swift의 배열은 C언어의 배열처럼 버퍼(buffer)이다. 단, 필요에 따라 자동으로 버퍼의 크기를 조절해주어 요소의 삽입 및 삭제가 자유롭다는 점이 다르다.
버퍼(Buffer)라는 용어는 대체로 연속적인 메모리 공간을 의미한다. 메모리를 할당해서 구한 포인터는 이 버퍼의 시작 주소를 담고 있다고 볼 수 있다. 버퍼는 메모리 덩어리 그 자체다.
하지만 스위프트(Swift)는 포인터를 쓸 수 있는 언어가 아니기 때문에 연속적인 메모리를 액세스 하는 것이 불가능하다. 그래서
포인터를 사용하는 기존 언어의 버퍼 개념이 맞지 않는다.
// 대괄호를 사용하여 배열임을 표현
var names: Array<String> = ["Hoonsbrand", "Hoon", "pelixx"]
// Array<String>의 축약 표현인 [String], 위 선언과 같은 동작을 함.
var names2: [String] = ["Hoonsbrand", "Hoon", "pelixx"]
// 빈 배열 생성
var emptyArray: [Any] = [Any]()
var emptyArray2: [Any] = Array<Any>() // 위 선언과 같은 동작을 함.
// 배열의 타입을 정확히 명시해줬다면 []만으로도 빈 배열을 생성할 수 있음.
var emptyArray3: [Any] = []
// 배열의 프로퍼티 사용
print(emptyArray.isEmpty) // true
print(names.count) // 3
print(names2.first) // "Hoonsbrand"
print(names.last) // "pelixx"
print(names2.firstIndex(of: "Hoon")) // 1
4.4.2 딕셔너리
- 요소들이 순서 없이 키와 값의 쌍으로 구성되는 컬렉션 타입
- 키는 같은 이름을 중복해서 사용할 수 없음, 즉 키는 값을 대변하는 유일한 식별자
- 각 값에 키로 접근, 배열과 다르기 딕셔너리 내부에 없는 키로 접근해도 오류가 발생하지 않고 nil을 반환함.
// 타입 명시로 생성
var dict1: [String: Int] = [:] // 빈 딕셔너리 생성
var dict2: [String: Int] = ["Hoonsbrand": 25]
// 타입 추론으로 생성, 단 타입 추론으론 빈 딕셔너리 생성 불가
var dict3 = ["Hoonsbrand": 25]
// 3. 생성자로 생성
var dict4 = Dictionary<String, Int>()
var dict5 = [String: Int]()
// 4. 여러 타입을 저장하는 딕셔너리 생성, Key 값의 Type을 Any로 선언하거나, Objective-C의 클래스인 NSDictionary를 사용
var dict6: [String: Any] = ["name": "Hoonsbrand", "age": 25]
var dict7: NSDictionary = ["name": "Hoonsbrand", "age": 25]
// 값 수정
print(dict2["Hoonsbrand"]) // 25
dict2["Hoonsbrand"] = 24
print(dict2["Hoonsbrand"]) // 24
// 새로운 값 추가
dict2["Hoon"] = 27
print(dict2["Hoon"]) // 27
// 값 삭제
print(dict2.removeValue(forKey: "Hoonsbrand")) // 24
// Hoonsbrand 키에 해당하는 값이 없으면 nil을 반환, 값이 없을때의 기본값도 지정할 수 있음.
print(dict2["Hoonsbrand", default: 0]) // 0
4.4.3 세트
- 같은 타입의 데이터를 순서 없이 하나의 묶음으로 저장하는 형태의 컬렉션 타입
- 세트 내의 값은 모두 유일한 값, 즉 중복된 값이 존재하지 않음.
- 보통 순서가 중요하지 않거나 각 요소가 유일한 값이어야 하는 경우에 사용
- 세트의 요소로는 해쉬 가능한 값(Hashable)이 들어와야 함.
- 줄여서 표현할 수 있는 축양형이 없음.
- Set 키워드를 사용
해쉬 값(Hash Value)?
원본 데이터를 특정 규칙에 따라 처리하여 간단한 숫자로 만든 것을 해쉬값이라고 한다.
정확히는 원본 데이터 (객체)를 해쉬 함수 (hash function)을 사용하여 64bit의 Int값으로 변환한 것이다.
Swift에서 Set 타입의 값, Dictionary 타입의 Key은 Hashable 해야 한다.
또한 Swift의 기본 타입인 String, Int 등은 자동으로 Hashable 하다.
열거형 (Enum)이 연관 값 (associated value)을 가지지 않으면, 자동으로 Hashable 하다.
예를 들면 Set의 값에 String 타입을 담을 수 있다. 그리고 사용자 정의 타입을 Hashable 하도록 설정하면, Set의 값에 해당 타입을 담을 수 있다.
해쉬값은 왜 사용할까? 값의 검색 속도가 빠르기 때문이다. (해쉬 테이블에 대한 이해가 필요하다.)
쉽게 말하자면, 원래는 처음부터 순서대로 값을 찾아야 한다.
반면, 해쉬 테이블에서는 해쉬값을 index로 사용하여 원하는 값의 위치를 한 번에 알 수 있다.
세트의 사용
var names: Set<String> = Set<String>() // 빈 세트 생성
var names2: Set<String> = [] // 빈 세트 생성
// Array와 마찬가지로 대괄호 사용
var names3: Set<String> = ["Hoonsbrand", "Hoon", "Hoonsbrand"]
// 그렇기 때문에 타입 추론을 사용하게 되면 컴파일러는 Set가 아닌 Array로 타입을 지정한다.
var numbers = [100, 200, 300]
print(type(of: numbers)) // Array<Int>
print(names3.isEmpty) // false
print(names3.count) // 2: 중복된 값은 허용되지 않아 Hoonsbrand는 1개만 남는다.
// -------------------------------------------------------------------------------------
let iOSClassStudents: Set<String> = ["Hoonsbrand", "chulsoo", "john"]
let swiftClassStudents: Set<String> = ["jenny", "Hoonsbrand", "chulsoo", "hana", "minsoo"]
// 교집합 {"Hoonsbrand", "chulsoo"}
let intersecSet: Set<String> = iOSClassStudents.intersection(swiftClassStudents)
// 여집합의 합 {"john", "jenny", "hana", "minsoo"}
let symmetricDiffSet: Set<String> = iOSClassStudents.symmetricDifference(swiftClassStudents)
// 합집합 {"minsoo", "jenny", "john", "Hoonsbrand", "chulsoo", "hana"}
let unionSet: Set<String> = iOSClassStudents.union(swiftClassStudents)
// 차집합 {"john"}
let subtractSet: Set<String> = iOSClassStudents.subtracting(swiftClassStudents)
4.5 열거형
연관된 항목들을 묶어서 표현할 수 있는 타입이다. 열거형은 배열이나 딕셔너리 같은 타입과는 다르게 프로그래머가 정의해준 항목 값 외에는 추가/수정이 불가능하다. 그렇기 때문에 딱 정해진 값만 열거형 값에 속할 수 있다.
아래와 같은 경우에 요긴하게 사용할 수 있다.
- 제한된 선택지를 주고 싶을 때
- 정해진 값 외에는 입력받고 싶지 않을 때
- 예상된 입력 값이 한정되어 있을 때
4.5.1 기본 열거형
- 각 항목은 그 자체가 고유의 값이다.
- 항목이 여러 가지라서 나열하기 귀찮거나 어렵다면 한 줄에 모두 표현 가능
- enum 키워드를 사용
enum School {
case primary, elementary, middle, high, college, university, graduate
}
// School.university 대신에 .university도 사용이 가능하다.
var highestEducationLevel: School = School.university
// 같은 타입인 School 내부의 항목으로만 highestEducationLevel의 값을 변경해줄 수 있다.
highestEducationLevel = .graduate
4.5.2 원시 값
- 열거형의 각 항목은 자체로도 하나의 값이지만 항목의 원시 값(Raw Value)도 가질 수 있다.
- 원시 값을 사용하고 싶다면 열거형 이름 오른쪽에 타입을 명시해주면 되고, rawValue라는 프로퍼티를 통해 가져올 수 있다.
enum School: String {
case primary = "유치원"
case elementary = "초등학교"
case middle = "중학교"
case high
case college = "대학"
case university = "대학교"
case graduate = "대학원"
}
let highestEducationLevel: School = .university
print("저의 최종학력은 \(highestEducationLevel.rawValue) 졸업입니다.")
// 저의 최종학력은 대학교 졸업입니다.
// 값을 따로 할당해주지 않으면 항목 자체의 값을 가져온다.
let highSchoolEducationLevel: School = .high
print("저의 최종학력은 \(highSchoolEducationLevel.rawValue) 졸업입니다.")
// 저의 최종학력은 high 졸업입니다.
// -------------------------------------------------------------------------------------
// 정수 타입이라면 첫 항목을 기준으로 0부터 1씩 늘어난 값을 갖게 된다.
enum Numbers: Int {
case zero
case one
case two
case ten = 10
}
print("\(Numbers.zero.rawValue), \(Numbers.one.rawValue), \(Numbers.two.rawValue), \(Numbers.ten.rawValue)")
// 0, 1, 2, 10
4.5.3 연관 값
- 열거형 각 항목이 연관 값을 가지게 되면, 기존 프로그래밍 언어의 공용체 형태를 띨 수 있다.
- 열거형 내의 항목(case)이 자신과 연관된 값을 가질 수 있으며 각 항목 옆에 소괄호로 묶어 표현한다.
- 다른 항목이 연관 값을 갖는다고 모든 항목이 연관 값을 가질 필요는 없다.
enum PastaTaste {
case cream, vongole
}
enum PizzaDough {
case cheeseCrust, thin, original
}
enum PizzaTopping {
case pepperoni, cheese, bacon
}
enum MainDish {
case pasta(taste: PastaTaste)
case pizza(dough: PizzaDough, topping: PizzaTopping)
case chicken(withSauce: Bool)
case rice
}
var dinner: MainDish = MainDish.pasta(taste: PastaTaste.vongole) // 봉골레 파스타
dinner = .pizza(dough: PizzaDough.cheeseCrust, topping: .pepperoni) // 페퍼로니 치즈크러스트 피자
dinner = .chicken(withSauce: true) // 양념 치킨
dinner = .rice // 밥
4.5.4 항목 순회
- 열거형에 포함된 모든 케이스를 알아야 할 때 사용
- 열거형의 이름 뒤에 콜론(:)을 작성하고 한 칸 띄운 뒤 CaseIterable 프로토콜을 채택(원시 값을 갖는 열거형이면 쉼표 뒤에 작성)
- 그러면 열거형에 allCases라는 이름의 타입 프로퍼티를 통해 모든 케이스의 컬렉션을 생성해줌
enum School: String, CaseIterable {
case primary = "유치원"
case elementary = "초등학교"
case middle = "중학교"
case high
case college = "대학"
case university = "대학교"
case graduate = "대학원"
}
let allCases: [School] = School.allCases
print(allCases)
// [School.primary, School.elementary, School.middle, School.high, School.college, School.university, School.graduate]
allCases 프로퍼티를 사용할 수 없는 경우
- 플랫폼별로 사용 조건을 추가하는 경우(available속성을 이용 ex. iOS14 이하는 특정 케이스를 사용할 수 없다)
- 열거형의 케이스가 연관 값을 갖는 경우
4.5.5 순환 열거형
- 열거형 항목의 연관 값이 열거형 자신의 값이고자 할 때 사용
- indirect 키워드를 사용
- 특정 항목에만 한정하고 싶다면 case 앞에, 열거형 전체에 적용하고 싶다면 enum 앞에 키워드를 붙임
//indirect enum ArithmeticExpression {
// case number(Int)
// case addition(ArithmeticExpression, ArithmeticExpression)
// case multiplication(ArithmeticExpression, ArithmeticExpression)
//}
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let final = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case .number(let value):
return value
case .addition(let left, let right):
return evaluate(left) + evaluate(right)
case .multiplication(let left, let right):
return evaluate(left) * evaluate(right)
}
}
let result: Int = evaluate(final)
print("(5 + 4) * 2 = \(result)") // (5 + 4) * 2 = 18
참고자료
https://babbab2.tistory.com/113
Swift) Dictionary - 자주 사용하는 메서드
안녕하세요 :) 소들입니다 이번 포스팅에선 딕셔너리를 사용할 때 알아두면 편한 메서드들을 좀 정리해보려고 해요!!! 원래 설명충이긴 한데, 이번 포스팅만큼은 단순 명료하게 가겠슴니다!! (물
babbab2.tistory.com
https://applecider2020.tistory.com/14
[Swift] Hashable 해야 한다? 해쉬값이란? (간단 요약)
안녕하세요. 애플사이다 입니다. Swift Language Guide의 네 번째 챕터 Collection Types에 "Hashable"과 "해쉬값 (Hash Value)"이 등장합니다. 해쉬 개념을 제대로 이해하려면 해쉬 테이블 (Hash Table)이라는..
applecider2020.tistory.com
해당 게시글은 야곰님의 "스위프트 프로그래밍 3판" 을 참고하여 작성하였습니다.
스위프트 프로그래밍(3판): 객체지향, 함수형, 프로토콜 지향 패러다임까지 한 번 ... - 야곰 - Google 도서