기본 중의 기본 이므로, 글을 쓰면서 정리하고 또 복습 하였다.
Swift 타입 관련 개념
데이터 타입 안심
Safe.
스위프트의 특징 중 하나.
스위프트는 타입에 굉장히 엄격하기에, 서로 다른 타입끼리의 데이터 교환은 꼭 타입캐스팅(형변환)을 거쳐야 한다!
정확히 말하면, 스위프트에서 값 타입의 데이터 교환은 새로운 인스턴스를 생성하여 할당하는 것이다.
타입 캐스팅에 대한 설명(조금 더 공부할 예정) 더보기 V
타입 캐스팅과 타입 검사
- 컴파일러가 어떤 값의 특정 타입을 식별하지 못하는 경우
- 메서드, 함수가 반환하는 값이 불명확하거나 예상되지 않은 타입의 값일 때 발생
이럴 때는 as 키워드를 사용하여 내가 의도한 타입을 컴파일러가 알 수 있게 해야 한다. 이것을 타입 캐스팅(형 변환) 이라고 한다.
- 타입 검사 (Type Checking): 특정 인스턴스가 어떤 클래스 또는 클래스의 자식 클래스의 인스턴스인지 확인한다. 이는 is 키워드로 수행된다.
- 타입 캐스팅 (Type Casting): 인스턴스를 부모 또는 자식 클래스 타입으로 취급해야 할 때 사용된다. Swift에서는 as? 또는 as! 연산자를 사용하여 타입 캐스팅을 한다.
- 업캐스팅 (Upcasting)
- 서브 클래스의 인스턴스를 슈퍼 클래스 타입으로 취급하는 경우
- 업캐스팅은 항상 안전하기 때문에 as 연산자를 사용하거나 자동으로 수행된다.
- 업캐스팅은 상위 타입의 인터페이스만 접근할 수 있게 한다.
class Animal {}
class Dog: Animal {}
let pet = Dog()
let animal: Animal = pet // 업캐스팅
- 다운캐스팅 (Downcasting)
- 슈퍼 클래스의 인스턴스를 서브 클래스 타입으로 취급하는 경우
- 다운캐스팅은 불확실할 수 있기 때문에 as? 또는 as! 연산자로 수행해야 한다.
- as?는 실패할 수 있는 다운캐스팅(옵셔널 반환)을, as!는 실패할 수 없다고 확신할 때 사용하는 강제 다운캐스팅(크래시 발생 가능)을 의미한다.
// 옵셔널 다운캐스팅 (실패 가능성 있음)
let someAnimal: Animal = Dog()
if let dog = someAnimal as? Dog {
print("다운캐스팅 성공!")
}
// 강제 다운캐스팅 (실패하면 런타임 에러)
let certainDog = someAnimal as! Dog
object(forKey:) 메서드가 반환하는 값을 String 타입으로 처리해야 한다고 컴파일러에게 알려주는 코드
let myValue = record.object(forKey: "comment") as! String
위 메서드 처럼, 종종 API는 Any 타입 등의 일반적인 타입을 반환할 수 있기에, 이 경우, 타입 캐스팅을 사용하여 컴파일러에게 명확한 타입을 알려주면 된다.
여기서 as! String은 컴파일러에게 record.object(forKey:) 메서드의 반환값이 String이라는 것을 확실히 알려주는 강제 다운캐스팅.
이는 해당 값이 반드시 String이어야 하며, 그렇지 않은 경우 런타임 에러가 발생함을 의미한다.
가능하다면 as?를 사용하여 안전한 다운캐스팅을 시도하고, 해당 값이 올바른 타입인지 검사하는 것이 좋다.
타입을 안심하고 사용할 수 있기에, 그만큼 서로 다른 타입의 값을 할당하는 실수를 줄일 수 있다.
타입 추론
스위프트는 변수나 상수 선언 시, 특정 타입을 명시하지 않아도 컴파일러가 할당된 값을 기준으로 타입을 추론한다. (타입 추론)
var name = "iyungui"
name = 100 // name의 타입은 String이기에, 오류 발생
타입 별칭
스위프트의 기본 데이터 타입이든, 사용자가 만든 데이터 타입이든, 타입에 '별칭' 을 부여할 수 있다.
기존에 사용하던 데이터 타입 이름과, 별칭 모두 사용가능 하다.
typealias MyInt: Int
typealias YourInt: Int
typealias MyDouble: Double
let age: MyInt = 100
var year: YourInt = 100
year = age // 서로 같은 데이터 타입으로 취급
let month: Int = 7 // 물론, 기존의 Int 도 사용 가능.
let percentage: MyDouble = 99.9 // 물론, Int 이외에 다른 자료형도 별칭 사용 가능.
튜플
- 튜플은 타입의 이름이 따로 지정되어 있지 않은, 내 마음대로 만드는 타입.
- '지정된 데이터의 묶음'
- 파이썬의 튜플과 유사
아래 코드는 순서대로, 튜플을 사용할 때 '인덱스' 로, '이름'으로, 그리고 '타입 별칭'을 사용하였다.
// String, Int, Double 타입을 갖는 튜플
var person: (String, Int, Double) = ("iyungui", 100, 182.5)
// 인덱스 통해서 값 할당하기
person.1 = 24
person.2 = 172.5
// 인덱스 통해서 값 빼오기
print("이름: \(person.0), 나이: \(person.1), 신장: \(person.2)")
// 코드 이해도 높이기 위해서, 인덱스보다 튜플의 요소마다 이름을 붙여줄 수도 있다
var person2: (name: String, age: Int, height: Double) = ("yagom", 100, 182.5)
print("이름: \(person2.name), 나이: \(person2.age), 신장: \(person2.height)")
// 같은 모양의 타입을 여러 번 쓸 경우, 타입 별칭을 사용하면 깔끔하고 안전한 코드 작성 가능
typealias PersonTuple = (name: String, age: Int, height: Double)
let John: PersonTuple = ("John", 24, 172.5)
let Mike: PersonTuple = ("Mike", 100, 182.5)
컬렉션형 Collection Types
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/collectiontypes/
Documentation
docs.swift.org

Arrays, sets, and dictionaries in Swift are always clear about the types of values and keys that they can store.
위에서 언급한 스위프트의 특성, Safe.
잘못된 데이터 타입의 값을 컬렉션에 할당할 수 없는 만큼, 서로 다른 타입의 값을 할당하는 등의 실수를 줄일 수 있다.
스위프트의 Array, Set, Dictionary 는, 제네릭 컬렉션으로 구현되어 있다.
제네릭에 대한 설명 -> https://yung-lee.tistory.com/31
Generics
제네릭을 사용하지 않는 전통적인 방식에서는, 특정 타입의 값만을 교환할 수 있는 함수를 여러 개 만들어야 한다. 여러 데이터 타입을 스왑할 수 있는 함수를 만드는 경우를 예시로 들어보자.
yung-lee.tistory.com
배열 Array
Arrays are ordered collections of values.
같은 타입의 데이터를 일렬로 나열한 후 순서대로 저장하는 형태의 컬렉션 타입.
각기 다른 위치에 같은 값이 들어갈 수도 있다.
스위프트의 Array는 C 언어의 배열처럼 버퍼 Buffer 이다. 단, 한 번 선언하면 크기가 고정되는 것이 아니라, 필요에 따라 자동으로 크기를 조절해주므로 요소의 삽입 및 삭제가 자유롭다.
var names: Array<String> = ["Jonathan", "Nancy", "Will", "Nancy"]
// [String] 은 Array<String> 의 축약 표현이다.
// [Element] 형식을 더 많이 쓰도록 하자.
// var names: [String] = ["Jonathan", "Nancy", "Will", "Nancy"]
var emptyArray: [Any] = [Any]() // Any 데이터를 요소로 갖는 빈 배열을 생성한다.
// 위와 같은 표현
var emptyArray2: [Any] = Array<Any>()
// 배열의 타입을 [] 으로 명시했다면, 빈 배열도 생성 가능
var emptyArray3: [Any] = []
print(emptyArray.isEmpty) // true
print(names.count) // 4
배열의 인덱스는 0 부터 시작한다. 만약 잘못된 인덱스 접근 시, Exception Error 익셉션 오류가 발생하므로 주의하자.
배열 공식문서에 있는 내용을 모두 따라 쳐보았다.
배열 선언 및 생성 _ 여러가지 방법
// 기본값을 가진 배열 생성
var threeDoubles = Array(repeating: 0.0, count: 3)
// type is [Double], and equals [0.0, 0.0, 0.0]
// 두 배열을 더하여 배열 생성하기
var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
// type is [Double], and equals [2.5, 2.5, 2.5]
var sixDoubles = threeDoubles + anotherThreeDoubles
// sixDoubles is inferred as [Double], and equals [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]
// array literal 로 배열 초기화
var shoppingList: [String] = ["Eggs", "Milk"]
// shoppingList has been initialized with two initial items
// shoppingList 는 [String] 으로 초기화 했기에, Element 로는 String 만 들어갈 수 있다.
// 모든 요소가 String 으로 '똑같은' 타입만 배열에 들어갔기에, [String] 으로 타입 추론이 가능
var shoppingList2 = ["Eggs", "Milk"]
배열에 접근 하거나 수정하는 방법
// count
print("The shopping list contains \(shoppingList.count) items.")
// "The shopping list contains 2 items."
// isEmpty는 count == 0 을 말함
if shoppingList.isEmpty {
print("The shopping list is empty.")
} else {
print("The shopping list isn't empty.")
}
// "The shopping list isn't empty."
// subscript syntax 를 사용한 예시 (배열의 인덱스로 접근)
var firstItem = shoppingList[0]
// 배열 끝에 새 요소 추가하기 append
shoppingList.append("Flour")
// 여러개를 추가하고 싶다면 append(contentsOf:)
shoppingList.append(contentsOf: ["Banana", "Melon"])
// 추가 할당 연산자 (+=) 로 호환 가능한 배열을 append
shoppingList += ["Baking Powder"]
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
// first와 last 프로퍼티를 통해 각각 배열의 첫번째, 마지막 요소를 가져올 수도 있음. (옵셔널 값)
print(shoppingList.first!) // Eggs
print(shoppingList.last!) // Butter
// firstIndex(of:) 를 사용하면, 해당 요소의 인덱스를 찾을 수 있음. (옵셔널)
// 만약 중복된 요소가 있다면, 제일 먼저 발견된 요소의 인덱스 반환
print(shoppingList.firstIndex(of: "Cheese")!) // 7
shoppingList.append("Cheese")
print(shoppingList.firstIndex(of: "Cheese")!) // 7
// 중간에 요소를 삽입하고 싶다면, insert(_:at:) 메서드를 사용하자
shoppingList.insert("Apple", at: 0)
print(shoppingList.first!)
배열의 요소를 삭제하는 방법
// 요소를 삭제하고 싶다면, remove(_:) 메서드 사용하자. remove 는 해당 요소를 삭제한 후, 반환된다.
shoppingList.remove(at: 0) // Apple
print(shoppingList) // ["Eggs", "Milk", "Flour", "Banana", "Melon", "Baking Powder", "Chocolate Spread", "Cheese", "Butter", "Cheese"]
// removeFirst, removeLast(): 삭제만 함.
shoppingList.removeLast()
shoppingList.removeFirst()
print(shoppingList) // ["Milk", "Flour", "Banana", "Melon", "Baking Powder", "Chocolate Spread", "Cheese", "Butter"]
// 범위 연산자를 사용하여 배열을 출력하기
print(shoppingList[1...3]) // ["Flour", "Banana", "Melon"]
// 새롭게 할당도 가능
shoppingList[1...3] = ["Eggs", "Cheese", "Ice Cream"]
print(shoppingList[1...3]) // ["Eggs", "Cheese", "Ice Cream"]
배열과 같은 컬렉션 타입을 활용할 때는 서브 스크립트, 반복문 등을 많이 사용한다.
타입 공부와 함께, 이를 같이 적용해보는 연습을 해보자.
for-in 사용
for item in shoppingList {
print(item)
}
/*
Milk
Eggs
Cheese
Ice Cream
Baking Powder
Chocolate Spread
Cheese
Butter
*/
// 각 항목의 정수 인덱스와 값 모두가 필요한 경우, enumerated() 사용.
/*
정수는 0에서 시작하여 각 항목에 대해 하나씩 계산합니다. 전체 배열을 열거하면, 이 정수는 항목의 인덱스와 일치합니다. 반복의 일부로 튜플을 임시 상수 또는 변수로 분해할 수 있습니다.
*/
for (index, value) in shoppingList.enumerated() {
print("Item \(index + 1): \(value)")
}
/*
Item 1: Milk
Item 2: Eggs
Item 3: Cheese
Item 4: Ice Cream
Item 5: Baking Powder
Item 6: Chocolate Spread
Item 7: Cheese
Item 8: Butter
*/
집합 Sets
A set stores distinct values of the same type in a collection with no defined ordering.
같은 타입의 데이터를, 순서 없이 하나의 묶음으로 저장하는 형태의 컬렉션 타입
세트 내의 값은 모두 유일한 값이므로 중복된 값은 존재하지 않는다.
세트의 요소는 해시 가능한 값이 들어와야 한다.
-> 순서가 중요하지 않거나 각 요소가 유일한 값이어야 하는 경우에 사용하자.

참고로, 스위프트의 기본 데이터 타입(Int, Double, String, Bool)은 모두 해시 가능한 타입이다. ->
해당 타입의 값들을 해시 함수를 통해 해시 값으로 변환할 수 있다는 의미인데, 이는 해당 값을 딕셔너리의 키로 사용하거나 세트(Set)의 멤버로 사용할 수 있음을 의미!
세트는 배열, 딕셔너리와 다르게 축약형이 없다..!
Set<Element> 로 선언한다.
집합 선언/생성 및 사용 예제
// 빈 세트 생성
var names: Set<String> = Set<String>()
var names2: Set<String> = []
// Array 와 마찬가지로 대괄호 사용!
var names3: Set<String> = ["Will", "Nancy", "Jonathan", "Will"]
// 그래서 Set는 타입 추론으로는 선언 못함. Array로 인식하기 때문.
print(names3.isEmpty) //false
print(names3.count) // 3 - Set 는 중복된 값 허용안하기에, Will 은 하나만 남는다.
// insert(_:)
names3.insert("jenny")
// remove(_:)
print(names3.remove("Will")) // Optional("Will")
// contains(_:)
if names3.contains("Will") {
print("Will")
} else {
print("nil")
}
for name in names3 {
print("\(name)")
}
/*
Nancy
Jonathan
jenny
*/
for name in names3.sorted() {
print("\(name)")
}
/*
Jonathan
Nancy
jenny
*/
Performing Set Operations
You can efficiently perform fundamental set operations, such as combining two sets together, determining which values two sets have in common, or determining whether two sets contain all, some, or none of the same values.
세트는 자신 내부의 값들이 모두 유일함을 보장하므로, 집합관계를 표현할 때 사용한다.

let setA: Set<Int> = [1, 2, 3, 4, 5]
let setB: Set<Int> = [4, 5, 6, 7, 8]
// 교집합
let intersection = setA.intersection(setB)
print(intersection) // 출력: [5, 4]
// 여집합_배타적 논리합
let symmetricDifference = setA.symmetricDifference(setB)
print(symmetricDifference) // 출력: [2, 3, 1, 6, 7, 8]
// 합집합
let union = setA.union(setB)
print(union) // 출력: [2, 3, 4, 5, 6, 7, 1, 8]
// 차집합
let subtraction = setA.subtracting(setB)
print(subtraction) // 출력: [2, 3, 1]
포함관계 연산

// setA가 setB의 모든 요소를 포함하는지. 즉, setA가 setB의 상위 집합인지 확인
if setA.isSuperset(of: setB) {
print("SetA is a superset of SetB.")
} else {
print("SetA is not a superset of SetB.")
}
// 출력: SetA is a superset of SetB.
// setB가 setA의 모든 요소를 포함되는지. 즉, setB가 setA의 부분 집합인지 확인
if setB.isSubset(of: setA) {
print("SetB is a subset of SetA.")
} else {
print("SetB is not a subset of SetA.")
}
// 출력: SetB is a subset of SetA.
// setA와 setC에 공통된 요소가 없는지. 즉, 두 세트가 완전히 서로 다른 요소만을 포함하고 있는지 확인
if setA.isDisjoint(with: setC) {
print("SetA and SetC have no elements in common.")
} else {
print("SetA and SetC have common elements.")
}
// 출력: SetA and SetC have no elements in common.
딕셔너리 Dictionary
A dictionary stores associations between keys of the same type and values of the same type in a collection with no defined ordering.
딕셔너리는 요소들이 순서 없이, 키와 값의 쌍으로 구성되는 컬렉션 타입.
딕셔너리의 값은 항상 키와 쌍을 이루는데, 키가 하나이거나 여러 개일 수 있다.
단 딕셔너리 안의 키는 같은 이름을 중복해서 사용할 수 없다. (아래 사진의 조건과 같이 where Key: Hashable 이므로...)
https://developer.apple.com/documentation/swift/dictionary#2846239

딕셔너리 선언 및 생성하기
// 딕셔너리 선언 및 생성
// 타입 별칭으로 간단하게 사용.
typealias StringIntDictionary = [String: Int]
// 키는 String, 값은 Int 타입인 빈 딕셔너리 생성!
var numberForName: Dictionary<String, Int> = Dictionary<String, Int>()
var numberForName2: [String: Int] = Dictionary<String, Int>()
var numberForName3: StringIntDictionary = Dictionary<String, Int>()
// 딕셔너리 키, 값 타입을 명시했다면 [:] 만으로도 빈 딕셔너리 생성 가능!
var numberForName4: [String: Int] = [:]
// 초기값 주어서 생성 가능
var numberForName5: [String: Int] = ["Jonathan": 18, "Will": 10, "Nancy": 18]
print(numberForName5.isEmpty) // false
print(numberForName5.count) // 3
딕셔너리 사용하기
// 딕셔너리 사용하기
// 딕셔너리는 각 값에 '키'로 접근한다! (옵셔널 반환함)
// 딕셔너리는 배열과 다르게 딕셔너리 내부에 없는 키로 접근해도 오류가 발생하지 않는다. 대신, nil 반환
print(numberForName5["Jonathan"]) // Optional(18)
print(numberForName5["iyungui"]) // nil
// 기존 키의 값에 새로운 값 할당
numberForName5["Jonathan"] = 20
// 새로운 키와 값으로 할당도 가능.
numberForName5["iyungui"] = 24
/*
위 부분을 풀어서 설명하면,
해당 배열에 "iyungui" 라는 키가 존재한다면, 그 키에 새로운 값으로 갱신해라!
해당 배열에 "iyungui" 라는 키가 존재하지 않는다면, 새로운 키-값 쌍을 딕셔너리에 추가해라!
라는 말이다. :)
*/
// 특정 키에 해당하는 값을 제거하려면, removeValue(forKey:)
print(numberForName5.removeValue(forKey: "Will")) // Optional(10)
print(numberForName5) // ["Nancy": 18, "Jonathan": 20, "iyungui": 24]
print(numberForName5.removeValue(forKey: "Will")) // nil
// nil을 할당함으로써, 키-값 쌍을 제거할 수도 있다.
numberForName5["Nancy"] = nil
// 키에 해당하는 값이 없으면 기본으로 0 반환
print(numberForName5["Will", default: 0]) // 0
추가로,
updateValue(_:forKey:) 라는 메서드도 있다.
if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
print("The old value for DUB was \(oldValue).")
}
위 코드는 위에서 실습한 numberForName5["iyungui"] = 24 인덱스 문법과 비슷하다.
키가 이미 존재한다면 해당 키의 값을 업데이트하고, 키가 존재하지 않는다면 새로운 키-값 쌍이 추가된다.
그러나 updateValue(_:forKey:) 메서드의 독특한 점은, 업데이트를 수행한 후 업데이트 되기 이전의 값 (oldValue)을 반환한다!
이를 통해 업데이트가 실제로 일어났는지를 확인할 수 있다.
만약 "DUB"라는 키가 존재하지 않았다면, oldValue는 nil이 될 것이고, 이 if문은 실행되지 않는다...
또한 아래와 같은 subscript 문법을 많이 사용한다.
if let airportName = airports["DUB"] {
print("The name of the airport is \(airportName).")
} else {
print("That airport isn't in the airports dictionary.")
}
// Prints "The name of the airport is Dublin Airport."
위 예시 처럼 바로 removeValue 를 하는 것보다, 서브 스크립트를 사용해보자
// subscript syntax
if let removedValue = numberForName5.removeValue(forKey: "iyungui") {
print("The removedValue is \(removedValue).") // The removedValue is 24.
} else {
print("The numberForName5 dict doesn't contain a value for iyungui.")
}
딕셔너리를 for-in 구문과 함께 사용해보자
for (name, age) in numberForName5 {
print("\(name)'s age is \(age)")
}
/*
Jonathan's age is 20
Nancy's age is 18
*/
// key 혹은 values 만 가져오기
for name in numberForName5.keys {
print("name is \(name)")
}
/*
name is Nancy
name is Jonathan
*/
for age in numberForName5.values {
print("age is \(age)")
}
/*
age is 20
age is 18
*/
딕셔너리의 키나 값을 배열의 형태로 사용하고 싶을 경우! (배열을 인자로 받는 API 등에서 사용)
let nameArray = [String](numberForName5.keys)
print(nameArray) // ["Nancy", "Jonathan"]
let ageArray = [Int](numberForName5.values)
print(ageArray) // [20, 18]
더 공부하고 연습해볼 내용
- Generics https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/
Documentation
docs.swift.org