Swift - Colletion Types
1. Collection Types
스위프트는 값의 모음을 저장하기 위해 배열, 집합, 딕셔너리를 제공하기 위해 Collection Types를 제공한다.
배열(Array) : 정렬된 값들의 모음.
집합(Sets) : 정렬되지 않고, 유일한 값으로 이루어진 값들의 모음.
딕셔너리(Dictionary) : 정렬되지 않고, key - value 쌍으로 이루어진 값들의 모음.
스위프트의 컬렉션은 저장할 수 있는 값과 키 유형에 대해 항상 명확하기 때문에 잘못된 유형의 값을 컬렉션에 삽입할 수 없다.
1.1 Collection의 가변성
swfit의 컬렉션을 변수(var)에 할당하면, 생성된 컬렉션은 변경가능해진다. 이는 컬렉션에 항목을 추가, 제거, 젼경할 수 있다는 의미이다.
반대로 상수(let)에 할당하게 된다면 크기,내용을 변경할 수 없다.
1.1.1 상수 collection?
collection을 변경할 필요가 없다면 상수로 할당하는것이 좋다. 이렇게 함으로써 swift 컴파일러가 생성한 컬렉션의 성능을 최적화할 수 있다.
2. Array
배열은 동일한 타입의 값이 일련의 순서로 리스트에 저장한다.
NSArray vs Array
- NSArray : Foundation 라이브러리 내에 구현
- Array : Swift 라이브러리 내에 구현
Array 대신 NSArray를 사용하려면 as 연산자를 사용하여 브릿징을 수행할 수 있다.
이때 브리징을 하려는 요소 유형은 클래스, @objc 프로토콜 또는 Foundation 유형에 연결되어야하는 유형이여 한다.
예시로
let colors = ["periwinkle", "rose", "moss"] //[String] 타입의 Array
let moreColors: [String?] = ["ochre", "pine"] // [String?] 타입의 Array
let url = URL(fileURLWithPath: "names.plist")
(colors as NSArray).write(to: url, atomically: true)// Array -> NSArray 브리징 성공
// error: cannot convert value of type '[String?]' to type 'NSArray'
(moreColors as NSArray).write(to: url, atomically: true)// Array -> NSArray 브리징 실패
moreColors는 NSArray로 브리징을 할 수 없게 된다. 이유는 moreColors의 요소 타입은 Optional<String>이고, 이는 Foundation 타입과 연결되는 유형이 아니기 때문이다.
2.1 선언
2.1.1 빈 배열 선언
배열의 선언은 다음과 같은 방법으로 할 수 있다. 빈 대괄호 쌍 ( [] ) 으로 빈 배열을 만들 수 있다.
var array1: Array<String> = []
var array2: [String] = []
두 코드 전부 같은 String 타입의 배열을 가지며, 축약형이 더 선호된다.
2.1.2 기본값으로 선언
Array 이니셜라이저를 통해 기본값으로 설정된 특정 크기의 배열을 선언할 수 있다.
var threeDoubles = Array(repeating: 0.0, count: 3)
// threeDoubles is of type [Double], and equals [0.0, 0.0, 0.0]
2.1.3 배열 리터럴로 선언
배열 리터럴은 한쌍의 대괄호로 묶인 쉼표로 구분된 값 목록으로 작성된다. 이러한 배열 리터럴로 배열을 선언할 수 있다.
var shoppingList: [String] = ["Eggs", "Milk"]
// shoppingList has been initialized with two initial items
shoppingList 배열은 String 배열 리터럴로 작성되었으며, String배열의 타입을 가진다.
Swift가 타입을 추론하기 때문에 shoppingList 배열은 타입을 작성할 필요가 없다.
2.2 배열 수정 및 접근
메서드 및 프로퍼티를 통해 배열 수정 및 접근을 할 수 있다.
2.2.1 + 연산
더하기 연산 (+)을 통해 호환되는 타입의 기존 배열 두개를 더하여 새 배열을 만들 수 있다.
새 배열의 타입은 추가하는 두 배열의 타입에서 유추된다.
var threeDoubles = Array(repeating: 0.0, count: 3)
// threeDoubles is of type [Double], and equals [0.0, 0.0, 0.0]
var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
// anotherThreeDoubles is of type [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]
2.2.2 count
배열의 항목 수를 읽을 수 있다
print("The shopping list contains \(shoppingList.count) items.")
// Prints "The shopping list contains 2 items."
2.2.3 isEmpty
배열의 count 프로퍼티가 0 인지를 판단하는 bool 프로퍼티이다.
if shoppingList.isEmpty {
print("The shopping list is empty.")
} else {
print("The shopping list isn't empty.")
}
// Prints "The shopping list isn't empty."
2.2.4 append
배열의 끝에 새 항목을 추가할 수 있다.
shoppingList.append("Flour")
// shoppingList now contains 3 items, and someone is making pancakes
또는 += 연산자를 사용하여 하나 이상의 항목을 가진 같은 타입의 배열을 추가할 수 있다
shoppingList += ["Baking Powder"]
// shoppingList now contains 4 items
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
// shoppingList now contains 7 items
2.2.5 조회
배열은 subscript 문법을 이용하여 특정 인덱스에 있는 원소를 조회할 수 있게한다.
배열이름 바로 옆에 대괄호 쌍 안에 인덱스를 입력하며, 해당 인덱스에 있는 원소를 조회할 수 있다.
var firstItem = shoppingList[0]
// firstItem is equal to "Eggs"
subscript 문법을 사용할 때, 유효한 값을 넣어야 한다. swift의 array는 첫번째 원소가 1 이 아닌 0번째부터 시작하므로 1개의 원소를 가지는 배열에 1인덱스를 입력한다던지, 4 크기의 배열에 4 인덱스를 입력한다면 런타임 에러를 마주치게 될것이다.
2.2.6 insert
지정된 인덱스의 배열에 항목을 삽입할때 쓴다.
shoppingList.insert("Maple Syrup", at: 0)
// shoppingList now contains 7 items
// "Maple Syrup" is now the first item in the list
2.2.7 remove
지정된 인덱스에서 항목을 제거하고, 제거된 항목을 반환한다.
반환된 값을 무시할 수 있다.
let mapleSyrup = shoppingList.remove(at: 0)
// the item that was at index 0 has just been removed
// shoppingList now contains 6 items, and no Maple Syrup
// the mapleSyrup constant is now equal to the removed "Maple Syrup" string
insert, remove 메서드를 사용할때, 배열의 경계 밖에 있는 인덱스 값에 액세스하면 런타임 오류가 발생한다. 해당 인덱스에 있는 원소를 없애거나 수정할때, 유효한 인덱스인지 먼저 확인해야한다.
2.2.8 removeLast()
배열의 가장 마지막 항목을 삭제한다. removeat 메서드와 마찬가지로 제거된 항목을 반환한다.
2.3 배열에 대한 반복
for - in 루프를 사용하여 배열의 전체 값 세트를 반복할 수 있다.
for item in shoppingList {
print(item)
}
// Six eggs
// Milk
// Flour
// Baking Powder
// Bananas
각 항목의 인덱스와 값이 필요한 경우 enumerated() 메서드를 사용하여 배열을 반복한다.
enumerated() 메서드는 각 항목에 대하여 인덱스와 값으로 구성된 튜플을 반환한다.
for (index, value) in shoppingList.enumerated() {
print("Item \(index + 1): \(value)")
}
// Item 1: Six eggs
// Item 2: Milk
// Item 3: Flour
// Item 4: Baking Powder
// Item 5: Bananas
또한 배열 항목의 인덱스만 얻고싶다면 indices 프로퍼티를 사용하여 배열에 대한 인덱스 정보를 얻는다.
indices 프로퍼티는 오름차순으로 인덱스 범위를(Range) 반환한다.
3. Set
Swift의 Set은 순서 상관없이 같은 타입의 고유값을 저장한다.
순서가 중요하지 않고, 항목이 한번만 표시되도록 해야하는경우 배열 대신 집합을 사용할 수 있다.
3.1 Set 타입의 해쉬값들.
하나의 Set 안에 있는 저장되기 위한 A 타입은 반드시 hashable 이여야 한다. 즉, A 타입은 자체적으로 해시값을 계산하는 방법을 제공해야 한다는것이다.
여기서 해시값은 동등하게 비교되는 모든 객체에 대해 동일한 Int값이다. a == b 이면 a 해시값은 b 해시값과 동일하다는 뜻이다.
Swift의 기본적인 타입들 ( String, Int, Double, Bool ) 은 기본적으로 hashable이다 (Hashable 프로토콜을 준수한다). 이러한 타입들은 Set 타입 또는 딕셔너리 key 타입으로 사용될 수 있다.
연관된 값이 없는 열거형의 case 값들도 또한 기본적으로 hashable이다.
3.2 Set 선언
3.2.1 빈 Set 초기화 및 선언
Set<Element> 와 같은 방법으로 선언한다. 배열과 달리, set은 약식으로 선언할 수 없다.
var letters = Set<Character>()
print("letters is of type Set<Character> with \(letters.count) items.")
// Prints "letters is of type Set<Character> with 0 items."
빈 집합이 아닐때, 빈 집합을 만들기 위해 대괄호를 사용하여 빈 집합을 만들 수 있다.
letters.insert("a")
// letters now contains 1 value of type Character
letters = []
// letters is now an empty set, but is still of type Set<Character>
3.2.2 배열 리터럴로 Set 선언
Set 또한 배열 리터럴을 사용하여 초기화할 수 있다.
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// favoriteGenres has been initialized with three initial items
해당 코드에서 타입을 명시하지 않아도 favoritGenres는 String 타입의 Set이 된다. 해당 배열 리터럴에는 세개의 String 값밖에 없기 때문에 Swift는 favoritGenres 를 Set<String> 타입으로 추론한다.
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
Set임을 명시해주면 Element가 어떤 타입인지 몰라도 배열리터럴로 선언할 수 있다.
3.3 Set 접근 및 수정
3.3.1 count, isEmpty
Array와 동일하기 때문에 생략하겠다.
3.3.2 insert(_:)
Set에 새 항목을 추가할 수 있다.
favoriteGenres.insert("Jazz")
// favoriteGenres now contains 4 items
3.3.3 remove(_:)
집합의 remove(_:) 메서드 를 호출하여 집합에서 항목을 제거할 수 있다.
이 메서드는 Set의 구성원인 경우 해당 항목을 제거하고 제거된 값을 반환하고, Set에 포함되지 않은 경우 nil을 반환한다.
또는 Set의 모든 항목을 해당 removeAll() 메서드 로 제거할 수 있다.
if let removedGenre = favoriteGenres.remove("Rock") {
print("\(removedGenre)? I'm over it.")
} else {
print("I never much cared for that.")
}
// Prints "Rock? I'm over it."
3.3.4 contains(_:)
Set에 특정 항목이 포함되어 있는지 확인할떄 사용한다.
if favoriteGenres.contains("Funk") {
print("I get up on the good foot.")
} else {
print("It's too funky in here.")
}
// Prints "It's too funky in here."
3.3.5 sorted()
Set 타인에는 정의된 순서가 없으므로 Set의 Sorted() 메서드를 사용하여 < 연산자로 정렬된 배열을 반환한다.
여기서 리턴된 값은 배열이다.
let sorted = favoriteGenres.sorted() // [String] 타입을 가짐
for genre in sorted {
print("\(genre)")
}
// Classical
// Hip hop
// Jazz
3.4 Set 연산
두 집합을 결합하거나, 공통적으로 갖는 값을 결정, 교집합, 일부가 포함되어있는지, 포함되어있는지 등을 기본 Set 연산을 통해 효율적으로 수행할 수 있다.
3.4.1 intersection(_:)
두개의 Set에서 공통적으로 가지는 값만 사용하여 새 Set을 만든다.
let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]
oddDigits.intersection(evenDigits).sorted()
// []
3.4.2 symmetricDifference(_:)
해당 메서드를 사용하여 두개의 Set이 서로 공통적으로 가지고 있지 않은 값들로만 이루어진 새로운 Set을 만든다.
oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
// [1, 2, 9]
3.4.3 union(_:)
두 Set이 가지고 있는 전체 값을 사용해 새로운 Set을 만든다.
oddDigits.union(evenDigits).sorted()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
3.4.5 subtracting(_:)
해당 Set에서 파라미터로 넘어온 Set에는 없는 값으로 이루어진 새 Set을 만든다.
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
// [1, 9]
3.5 포함관계
Set의 메서드를 통해 각 Set a,b에 대해서 어떻게 포함되어있는지, 동일한 Set인지(동일한 값들로 이루어진 Set인지)를 판단할 수 있다.
let houseAnimals: Set = ["🐶", "🐱"]
let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
let cityAnimals: Set = ["🐦", "🐭"]
3.5.1 == 연산자
두 Set에 동일한 값이 모두 포함되어 있는지 확인한다.
3.5.2 isSubset(of:)
Set의 모든 값이 파라미터로 넘어온 집합에 포함되어 있는지 확인한다.
houseAnimals.isSubset(of: farmAnimals)
// true
3.5.3 isSuperSet(of:)
Set에 파라미터로 넘어온 Set의 모든 값이 포함되어 있는지 확인한다.
farmAnimals.isSuperset(of: houseAnimals)
// true
3.5.4 isDisjoint(with:)
두 Set에 공통된 값이 없는지 확인한다.
farmAnimals.isDisjoint(with: cityAnimals)
// true
4. Dictionary
Dictionary는 동일한 유형의 키-값 에 대해 연관된 값을 저장한다. Dictionary 또한 지정된 순서가 없다. 특정한 식별자를 기반으로 값을 조회해야할 때 Dictionary를 사용한다.
4.1 선언
Swift에서는 Dictionary<Key, Value> 를 사용하여 Dictionary를 선언할 수 있다. 여기 key는 식별자로 사용되고, value는 식별자로 찾을 값으로 사용된다.
4.1.1 빈 Dictionary 초기화 및 선언
빈 Dictionary는 [:] 를 사용해 선언할 수 있다.
Dictionary<key, value> 또한 [key:value] 로 축약할 수 있다.
var namesOfIntegers: [Int: String] = [:]
// namesOfIntegers is an empty [Int: String] dictionary
이미 Dictionary가 선언되어있는 경우, [:]를 통해 다시 빈 Dictionary로 만들 수 있다.
namesOfIntegers[16] = "sixteen"
// namesOfIntegers now contains 1 key-value pair
namesOfIntegers = [:]
// namesOfIntegers is once again an empty dictionary of type [Int: String]
4.1.2 Dictionary 리터럴로 선언
앞서 배열, Set과 같이 리터럴 구문을 통해 Dictionary를 선언할 수 있다.
Dictionary 리터럴은 하나 이상의 키-값 쌍을 Dictionary 컬렉션으로 작성하는 약식 방법입니다.

var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
배열과 마찬가지로 Dictionary 또한 타입을 적지 않아도 Swift가 추론할 수 있다. Swift는 위 airports Dictionary를 Dictionary<String,String> 으로 추론하게 된다.
4.2 Dictionary 접근 및 수정
4.2.1 count, isEmpty
배열, Set과 동일하여 생략
4.2.2 Dictionary 추가
subscipt 문법을 사용하여 값을 추가할 수 있다. 추가하려는 키를 인덱스로 사용하고, 값을 저장하면 Dictionary에 항목을 추가할 수 있다.
특정 키와 관련된 값을 변경할 수도 있다.
airports["LHR"] = "London"
// the airports dictionary now contains 3 items
airports["LHR"] = "London Heathrow"
// the value for "LHR" has been changed to "London Heathrow"
4.2.3 updateValue(_:forKey:)
특정 키의 값을 설정하거나 업데이트한다.
위의 예시와 같이 특정 키에 대한 값이 없다면 새로 값을 설정하게되고, 이미 있다면 업데이트 수행 후 이전에 저장되어있던 값을 반환한다. 이를 통해 업데이트가 수행 되었는지를 확인할 수 있다.
updateValue(_:forKey:) 메서드는 옵셔널 타입을 반환한다. 이는 이전에 값이 있다면 이전값을 반환하고, 값이 없고 새로 할당하는 상황이라면 nil을 반환하게 된다.
4.2.4 조회, 삭제
subscript 문법으로 특정 키를 사용하여 Dictionary에서 저장되어있는 값을 조회할 수 있다.
Dictionary에서 특정키에 대한 값이 존재하지 않을 수 있기 때문에 Dictionary의 subscript는 옵셔널타입을 리턴하게 된다.
특정키에 대해 값이 존재한다면 해당 값을 리턴하게되고, 없다면 nil을 리턴하게 된다.
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."
또한 해당 키에 nil을 할당하여 Dictionary에서 해당 항목을 삭제할 수 있다.
airports["APL"] = "Apple International"
// "Apple International" isn't the real airport for APL, so delete it
airports["APL"] = nil
// APL has now been removed from the dictionary
또는 removeValue(forKey:) 메서드를 사용하여 삭제할 수 있다. Dictionary에서 해당 값이 있다면 삭제된 값을 리턴하고, 없다면 nil을 리턴하게 된다.
if let removedValue = airports.removeValue(forKey: "DUB") {
print("The removed airport's name is \(removedValue).")
} else {
print("The airports dictionary doesn't contain a value for DUB.")
}
// Prints "The removed airport's name is Dublin Airport."
4.3 Dictionary 반복
for-in 구문을 사용하여 Dictionary를 반복할 수 있다. 각 항목은 튜플로 반환되며, 반복의 일부로 튜플의 구성원을 상수,변수로 분해할 수 있다.
for (airportCode, airportName) in airports {
print("\(airportCode): \(airportName)")
}
// LHR: London Heathrow
// YYZ: Toronto Pearson
Dictionary의 keys 또는 values 프로퍼티에 액세스 하여 키 또는 값의 반복가능한 컬렉션을 검색할 수 있다.
for airportCode in airports.keys {
print("Airport code: \(airportCode)")
}
// Airport code: LHR
// Airport code: YYZ
for airportName in airports.values {
print("Airport name: \(airportName)")
}
// Airport name: London Heathrow
// Airport name: Toronto Pearson
Array 인스턴스를 사용하는 API와 함께 사전의 키 또는 값을 사용해야 하는 경우 keys또는 values 프로퍼티를 사용하여 새 배열을 초기화 한다.
let airportCodes = [String](airports.keys)
// airportCodes is ["LHR", "YYZ"]
let airportNames = [String](airports.values)
// airportNames is ["London Heathrow", "Toronto Pearson"]