스위프트는 값의 모음을 저장하기 위해 배열, 집합, 딕셔너리 로 알려진 세 가지 기본 컬렉션 타입을 제공한다. 배열은 값의 모음을 순서대로 정렬되어있다. 집합은 중복이 되지 않고, 정렬도 되어 있지 않다. 딕셔너리는 키-값 쌍의 정렬되지 않은 값의 모음이다.
스위프트의 배열, 집합, 딕셔너리는 저장이 가능한 값과 키의 타입이 항상 명백하다. 이는 실수로 잘못된 타입을 삽입 할 수 없음을 의미한다. 또한 이는 값의 모음으로 부터 검색할 값의 타입에 대해 확신을 할 수 있음을 의미한다.
NOTE
스위프트의 배열 집합 딕셔너리 타입은 제네릭 모음으로 구현이된다.
제네릭 : 값의 타입을 일반화 한다는 의미. 타입에 의존하지 않는다.
만약 배열 집합 딕셔너리에 변수에 할당 하여 만든다면, 이 컬렉션은 변경이 가능 할 수 있다. 이는 컬렉션안의 요소에 추가 제거 변경을 생성 후에도 가능하다라는 것을 의미한다. 만약 이 컬렉션들을 상수에 할당하여 만든다면, 불변하고, 사이즈와 내용도 변경할 수 없다.
NOTE
컬렉션이 변경할 필요가 없는 모든 경우에 컬렉션을 상수에 할당하여 만드는 것은 좋은 습관이다. 이는 코드의 목적을 더 알기 쉽게 해주고, 스위프트의 컴파일러가 컬렉션의 성능을 최적화 하게끔 해준다.
배열은 같은 타입의 값을 정렬된 형태로 저장한다. 같은 값도 배열에서 다른 위치에 표현이 가능하다.
NOTE
스위프트의 배열 타입은Foundation의 NSArray
클래스와 연결이 된다.
스위프트의 배열 타입은 Array<Element>
로 작성이 가능하고, Element
는 배열에 저장이 가능한 값의 타입을 의미한다. 또한 짧게 [Element]
로 작성이 가능하다. 두 형태가 기능적으로 동일하지만, 짧은 형태의 작성법이 선호된다.
초기화 구문을 사용하여 특정 타입의 빈 배열을 만들 수 있다.
var someInts: [Int] = []
print("someInts is of type [Int] with \(someInts.count) items.")
// Prints "someInts is of type [Int] with 0 items."
someInts
변수는 초기화 타입으로 부터 [Int]
로 추론이 된다.
또는, 문맥에서 타입의 정보를 이미 제공한다면(함수의 인자 또는 이미 타입이 있는 변수나 상수), 빈 배열 리터럴로 빈 배열을 만들 수 있다. []
로 작성하면 된다.
someInts.append(3)
// 하나의 Int 값이 포함이 되어 있다.
someInts = []
// 현재 비어있지만, 타입은 여전히 [Int] 이다.
스위프트의 배열 타입은 모든 값이 같은 기본값으로 설정된 특정 사이즈의 배열을 생성할 수도 있다. repeating
에 적절한 타입의 기본값을 넣고, count
에는 그 값의 반복 횟수를 정한다.
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]
하나 이상의 배열의 값과 함께 배열 리터럴로 새로운 배열을 초기화 할 수 있다. 배열 리터럴은 값을 콤마로 구분하고 대괄호로 감싸서 작성한다.
var shoppingList: [String] = ["Eggs", "Milk"]
// shoppingList는 두 개의 초기 항목으로 초기화 된다.
shoppingList
는 [String]
으로 선언이 되었다. (문자열 값의 배열이라는 뜻) 문자열타입의 값으로 배열이 지정되었으므로, 문자열 값만 저장이 가능하다.
위 경우에 배열 리터럴은 두개의 문자열 값을 포함하고 그 외에는 포함하지 않는다. 이는 shoppingList
변수의 선언 유형과 일치하므로 2개의 초기 항목으로 초기화 하는 방법으로 배열 리터럴 할당이 허용된다.
스위프트의 타입 유추 덕분에, 같은 타입의 값을 포함하는 배열 리터럴로 작성을 할 때, 배열의 타입을 작성할 필요는 없다.
// 더 짧게 작성이 가능하다
var shoppingList = ["Eggs", "Milk"]
배열 리터럴의 값이 모두 같은 타입이므로, 스위프트는 [String]
이 shoppingList
변수에 사용할 올바른 타입임을 유추할 수 있다.
배열의 메소드와 프로퍼티 또는 서브스크립트 구문으로 접근과 변경이 가능하다.
// count 프로퍼티를 사용하는 예
print("The shopping list contains \(shoppingList.count) items.")
// Prints "The shopping list contains 2 items."
// isEmpty 프로퍼티를 사용하는 예 불린 타입을 반환
if shoppingList.isEmpty {
print("The shopping list is empty.")
} else {
print("The shopping list isn't empty.")
}
// Prints "The shopping list isn't empty."
// 배열의 삽입을 하는 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
// 서브스크립트 구문을 사용해서 배열의 인덱스로 접근하는 경우
// 스위프트의 배열은 항상 0으로 시작한다
var firstItem = shoppingList[0]
// firstItem is equal to "Eggs"
// 서브스크립트 구문으로 값을 변경하는 경우
shoppingList[0] = "Six eggs"
// the first item in the list is now equal to "Six eggs" rather than "Eggs"
서브스크립트 구문을 사용할 때, 지정한 인덱스가 유효해야 한다. shoppingList[shoppingList.count] = "Salt"
인 경우, 인덱스 범위가 아니므로 런타임 에러가 발생한다.
변경할 값들이 변경할 범위랑 길이가 달라도 범위 안에 값들을 한 번에 변경이 가능하다. 밑의 예를 사용해서 "Chocolate Spread", "Cheese", "Butter"
는 "Bananas", "Apples"
로 변경된다.
shoppingList[4...6] = ["Bananas", "Apples"]
// shoppingList now contains 6 items
// insert(_:at:) 메소드를 사용하여 특정 인덱스에 값을 삽입 할 수 있다.
shoppingList.insert("Maple Syrup", at: 0)
// shoppingList now contains 7 items
// "Maple Syrup" 은 배열의 처음 요소가 된다.
비슷하게 remove(at:)
메소드로 항목을 제거 할 수 있다. 항목을 제거하고, 제거된 항목을 반환한다.(필요하지 않은경우 무시할 수 있다)
let mapleSyrup = shoppingList.remove(at: 0)
// 처음 요소가 삭제되었다.
// 위 상수는 "Maple Syrup" 이라는 문자열과 동일하다.
NOTE
만약 범위 밖의 요소를 변경하거나 접근하면 런타임 에러가 발생할것이다.count
프로퍼티로 유효한 인덱스인지 확인을 해라. 배열의 마지막 인덱스는count-1
이다. 0 부터 시작하기 때문이다.
배열의 요소가 삭제 될 때, 배열의 틈은 닫혀진다. 따라서 현재 0번 인덱스의 값은 "Six eggs"
이다.
firstItem = shoppingList[0]
// firstItem is now equal to "Six eggs"
// 만약 마지막 요소를 제거하려면 밑의 메소드를 사용해라.
// 이는 count 프로퍼티를 쿼리하지 않기 때문에 성능면에서 유리하다.
let apples = shoppingList.removeLast()
// 마지막 요소가 제거 되었다.
// apples 상수는 "Apples" 문자열과 같다.
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
집합은 정렬되지 않은 같은 타입의 별개의 값을 저장한다. 요소의 순서가 중요하지 않고 값의 중복이 없어야 한다면 배열 대신 사용할 수 있다.
NOTE
스위프트의 집합은Foundation의 NSSet
클래스와 연결이 된다.
집합에 저장되기위해 타입은 hashable
해야 한다. 즉 타입은 스스로 해쉬 값을 계산할 수 있게 제공되야만 한다. 해쉬 값은 동일하게 비교되는 모든 객체들의 동일 Int
값이다. a == b
라면 a
의 해쉬값은 b
의 해쉬값과 같다.
hash : 데이터를 특정 규칙에 따라 간단한 숫자로 변환한 것이다.
스위프트의 모든 기본 타입은 기본적으로 해쉬 가능하다. 그리고 집합 값의 타입이나 딕셔너리 키 타입으로 사용이 가능하다. 연결된 값이 없는 열거 케이스 또한 기본적으로 해쉬 가능하다.
NOTE
스위프트의 표준 라이브러리를 통해 해쉬 가능한 프로토콜을 준수하게 만든다면 집합 값의 타입이나 딕셔너리의 키 타입으로 스스로 커스텀 타입을 만들어서 사용할 수 있다.
스위프트의 집합은 Set<Element>
로 작성하며, Element
는 집합에서 저장 가능한 타입들만 허가된다. 배열과 달리 짧은 작성 형태는 존재하지 않는다.
초기화 구문을 사용해서 빈 집합을 만들 수 있다.
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는 비어 있지만, 타입은 여전히 Set<Character>이다.
배열 리터럴로 집합을 초기화 할 수도 있으며, 하나 이상의 집합 모음을 작성하는 축약 방식이다.
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// favoriteGenres has been initialized with three initial items
집합 타입은 배열 리터럴 하나로는 유추 될 수 없다. 따라서 Set
키워드가 반드시 선언되야한다. 하지만, 스위프트의 타입추론 때문에 집합 원소들의 타입을 하나의 타입만으로 구성된 배열 리터럴로 초기화를 한다면 집합 요소들의 유형은 작성할 필요는 없다.
// 더 짧게 선언한 방식
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
메소드와 프로퍼티를 통해 집합에 접근과 변경이 가능하다.
// count 하는 프로퍼티 사용 예
print("I have \(favoriteGenres.count) favorite music genres.")
// Prints "I have 3 favorite music genres."
// isEmpty 프로퍼티. 불린 값을 반환한다.
if favoriteGenres.isEmpty {
print("As far as music goes, I'm not picky.")
} else {
print("I have particular music preferences.")
}
// Prints "I have particular music preferences."
// 삽입하는 insert(_:) 메소드
favoriteGenres.insert("Jazz")
// favoriteGenres now contains 4 items
// 제거하는 remove(_:) 메소드, 깂이 있다면, 제거된 값을 반환하고
// 값이 없다면 nil을 반환한다.
// 모든 값을 제거하는 경우는 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."
// 원소가 포함되어 있는지 확인하는 contains(_:) 메소드
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."
// for-in 문을 사용하여 반복 가능
for genre in favoriteGenres {
print("\(genre)")
}
// Classical
// Jazz
// Hip hop
스위프트의 집합 타입은 순서를 정하지 않는다. 특정 순서로 집합의 값을 반복하려면, sorted()
메소드를 사용해라. 이는 <
연산자를 사용하여 집합의 원소들을 배열해서 반환한다.
for genre in favoriteGenres.sorted() {
print("\(genre)")
}
// Classical
// Hip hop
// Jazz
intersection(_:)
메소드는 교집합을 뜻한다.symmetricDifference(_:)
메소드는 합집합에서 교집합을 뺀 부분만 해당이된다.union(_:)
메소드는 합집합을 의미한다.subtracting(_:)
메소드는 a
에서 b
집합을 뺀 부분으로 새 집합을 만든다.let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]
oddDigits.union(evenDigits).sorted()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
oddDigits.intersection(evenDigits).sorted()
// []
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
// [1, 9]
oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
// [1, 2, 9]
아래 그림은 세 개의 집합을 보여준다. a
집합은 b
집합의 상위 집합이다. b
의 요소 전부가 a
안에 포함이 되어 있기 때문이다. 반대로 b
집합은 a
집합의 하위 집합이다. b
집합과 c
집합은 공통 요소를 공유하지 않기 때문에 서로 연결되어 있지 않다.
==
연산자는 두 집합의 요소가 전부 같은 값인지 결정한다.isSubset(of:)
메소드는 집합의 모든 값이 인자로 받는 집합에 포함되어 있는지 확인한다.isSuperset(of:)
메소드는 인자로 받는 집합의 값들이 전부 포함이 되어 있는지 확인한다.isStrictSubset(of:), isStrictSuperset(of:)
메소드는 상위 집합인지 하위 집합인지 확인하고, 두 집합이 같으면 안된다.isDisjoint(with:)
메소드는 두 집합의 공통 요소의 여부를 확인한다.let houseAnimals: Set = ["🐶", "🐱"]
let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
let cityAnimals: Set = ["🐦", "🐭"]
houseAnimals.isSubset(of: farmAnimals)
// true
farmAnimals.isSuperset(of: houseAnimals)
// true
farmAnimals.isDisjoint(with: cityAnimals)
// true
딕셔너리는 키와 같은 타입들의 값 쌍을 정렬없이 저장한다. 각각의 값은 고유한 키랑 연관되어 있고, 고유한 키는딕셔너리 내에서 값의 식별자로 동작한다. 배열과 달리 특정 순서가 존재하지 않는다. 특정 단어에 대한 정의를 찾는데 사전을 사용하는 것과 거의 동일한 방식으로 해당 식별자를 기반하여 값을 검색할 때 딕셔너리를 사용한다.
NOTE
스위프트의 딕셔너리 타입은Foundation의 NSDictionary
클래스와 연결되어 있다.
딕셔너리의 작성은 Dictionary<Key, Value
로 작성하고, Key
는 딕셔너리 키로 사용되는 값의 타입으로 사용한다. Value
는 저장되는 딕셔너리 값의 타입을 의미한다.
NOTE
딕셔너리의Key
는 해쉬가능한 프로토콜을 준수해야한다.
또한 딕셔너리의 축약형으로 [Key: Value]
로 작성이 가능하다. 두 형태는 기능적으로 동일하나, 축약형이 좀 더 선호된다.
초기화 구문을 사용하여 특정 유형의 빈 딕셔너리를 생성할 수 있다.
var namesOfIntegers: [Int: String] = [:]
// namesOfIntegers is an empty [Int: String] dictionary
// 미리 타입을 선언 했다면, 타입을 선언해서 초기화 할 필요는 없다.
namesOfIntegers[16] = "sixteen"
// namesOfIntegers now contains 1 key-value pair
namesOfIntegers = [:]
// namesOfIntegers is once again an empty dictionary of type [Int: String]
또한 딕셔너리 리터럴을 사용해서 딕셔너리를 초기화 가능하다. 배열과 거의 동일한 방식이다. 딕셔너리 리터럴은 하나 이상의 키-값 쌍을 딕셔너리 컬렉션으로 작성하는 축약방식이다.
키-값 페어는 키와 값의 조합이다. 딕셔너리 리터럴 안에서 키와 값은 콜론(:)으로 구분된다. 키-값 쌍끼리의 구분은 콤마(,)로 구분하고 대괄호 안에서 작성한다.
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
위 딕셔너리는 키-값 둘다 문자열로 선언했기 때문에 그 외의 타입은 올 수 없다.
배열과 마찬가지로, 딕셔너리 리터럴의 키-값이 동일 타입과 함께 초기화 되었다면 딕셔너리 타입을 작성할 필요는 없다. 좀 더 줄여서 작성한 방법 밑의 예시
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
리터럴내의 모든 키와 값은 각각 같은 타입이므로, 스위프트는 [String: String]
이 딕셔너리에서 사용할 올바른 타입이라고 유추가 가능하다.
메소드와 프로퍼티, 서브스크립트 구문을 사용해서 접근과 변경이 가능하다.
// count 프로퍼티를 사용하는 모습
print("The airports dictionary contains \(airports.count) items.")
// Prints "The airports dictionary contains 2 items."
// isEmpty 사용 예시 count가 0인지 체크함
if airports.isEmpty {
print("The airports dictionary is empty.")
} else {
print("The airports dictionary isn't empty.")
}
// Prints "The airports dictionary isn't empty."
// 서브스크립트 구문으로 키-값을 추가로 넣을수 있고 변경이 가능하다.
airports["LHR"] = "London"
// the airports dictionary now contains 3 items
airports["LHR"] = "London Heathrow"
// the value for "LHR" has been changed to "London Heathrow"
서브스크립트 구문 대신 딕셔너리의 메소드인 updateValue(_:forKey:)
로 특정 키의 값을 업데이트 할 수 있다. 위의 서브스크립트 구문 예와 비슷하게, 특정 키가 존재한다면 업데이트를 하고 없다면 새로운 키-값 쌍을 삽입한다. 하지만 서브스크립트 구문과 다르게 updateValue(_:forKey:)
메소드는 업데이트를 수행 한 후 이전의 값을 반환한다. 이를 통해 업데이트가 이루어 졌는지 확인이 가능하다.
updateValue(_:forKey:)
메소드는 딕셔너리 값 타입의 옵셔널 값을 반환한다. 문자열 값을 저장하는 딕셔너리에서는 String? or "optional String"
으로 값의 타입을 반환한다. 이 옵셔널 타입은 업데이트 전의 값을 포함하거나, 값이 존재하지 않았다면 nil
을 포함한다.
if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
print("The old value for DUB was \(oldValue).")
}
// Prints "The old value for DUB was Dublin."
또 특정 키로 딕셔너리에서 값을 검색하는 서브스크립트 구문을 사용 가능하다. 값이 존재하지 않아도 키의 요청은 가능하므로, 딕셔너리의 서브스크립트 구문은 딕셔너리 값의 타입의 옵셔널 값을 반환한다. 만약 딕셔너리가 요청된 키의 값을 포함하고 있다면 서브스크립트 구문은 키에 대해 존재하는 값을 포함시켜 옵셔널 값으로 반환한다. 그렇지 않다면 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을 할당시켜서 키-값 쌍을 삭제하는 경우
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:) 메소드를 사용해서 삭제하는 경우
// 존재한다면 그 값을 반환한다. 없다면 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."
// for-in 문으로 반복가능하다.
// 각각의 요소는 (키-값) 튜플로 반환한다.
// 반복문 내에서 일시적인 상수나 변수로 분해 할당이 가능하다.
for (airportCode, airportName) in airports {
print("\(airportCode): \(airportName)")
}
// LHR: London Heathrow
// YYZ: Toronto Pearson
// 키와 값에 접근하는 예시
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
배열 인스턴스를 사용하는 API와 함께 딕셔너리의 키 또는 값을 사용해야 하는 경우, 키 또는 값의 프로퍼티와 함께 새로운 배열로 초기화해라.
let airportCodes = [String](airports.keys)
// airportCodes is ["LHR", "YYZ"]
let airportNames = [String](airports.values)
// airportNames is ["London Heathrow", "Toronto Pearson"]
스위프트의 딕셔너리 타입은 특정 순서가 없다. 딕셔너리의 키 또는 값에 특정 순서를 반복하려면, 키 또는 값 프로퍼티에서 sorted()
메소드를 이용하면 된다.