Collection Types
Collection Types (컬렉션 타입)
Swift 앱을 짜다 보면 값 하나하나를 변수로 다루기보다, 여러 값을 한데 묶어서 다루고 싶은 경우가 많아요. 장바구니에 담긴 항목 목록, 서로 다른 데이터를 묶어 보관하는 표, 그리고 중복 없이 유일한 값만 모아 두고 싶을 때가 대표적이죠. 이럴 때 Swift는 배열, 집합, 딕셔너리라는 세 가지 핵심 컬렉션 타입을 제공해요.
먼저 큰 그림을 잡아볼게요. **배열(array)**은 순서가 있는 값들의 모음이고, **집합(set)**은 순서가 없는 유일한 값들의 모음이며, **딕셔너리(dictionary)**는 키와 값이 짝지어진, 순서가 없는 모음이에요.
Swift의 배열, 집합, 딕셔너리는 언제나 저장할 값의 타입과 키의 타입을 분명히 알고 있어요. 덕분에 잘못된 타입의 값을 컬렉션에 섞어 넣는 실수를 막을 수 있고, 반대로 컬렉션에서 꺼내 오는 값의 타입도 확신할 수 있죠.
Note: Swift의 배열, 집합, 딕셔너리 타입은 *제네릭 컬렉션(generic collections)*으로 구현돼 있어요. 제네릭 타입과 컬렉션에 대한 더 자세한 내용은 doc:Generics에서 다뤄요.
본문
컬렉션의 변경 가능성 (Mutability of Collections)
배열, 집합, 딕셔너리를 만들어 변수에 할당하면 그 컬렉션은 *변경 가능(mutable)*해져요. 즉 생성된 뒤에도 항목을 추가하거나, 제거하거나, 값을 바꾸는 방식으로 컬렉션을 바꿀 수 있어요. 반대로 배열, 집합, 딕셔너리를 상수에 할당하면 그 컬렉션은 *변경 불가능(immutable)*해지고, 크기와 내용을 바꿀 수 없어요.
Note: 컬렉션이 바뀔 필요가 없는 경우에는 항상 변경 불가능한 컬렉션으로 만드는 게 좋아요. 그러면 코드를 추론하기 쉬워지고, Swift 컴파일러가 여러분이 만든 컬렉션의 성능을 최적화할 수 있게 돼요.
배열 (Arrays)
*배열(array)*은 같은 타입의 값들을 순서가 있는 목록으로 저장해요. 같은 값이 배열 안에서 서로 다른 위치에 여러 번 나타날 수도 있어요.
Note: Swift의
Array타입은 Foundation의NSArray클래스와 브리징돼요. Foundation과 Cocoa에서Array를 사용하는 방법에 대한 자세한 내용은 Bridging Between Array and NSArray를 참고해요.
배열 타입 축약 문법
Swift 배열의 타입은 Array<Element>라고 온전히 쓰는데, 여기서 Element는 배열이 저장할 수 있는 값의 타입이에요. 배열의 타입은 [Element]처럼 축약 형태로도 쓸 수 있어요. 두 형태는 기능적으로 완전히 같지만, 축약 형태가 더 선호되며 이 가이드에서는 배열의 타입을 가리킬 때 축약 형태를 사용할게요.
빈 배열 만들기
Swift에서 빈 배열은 두 가지 방법으로 만들 수 있어요. 함수의 인자나 이미 타입이 정해진 변수·상수처럼 컨텍스트가 이미 타입 정보를 제공하는 경우에는 빈 배열 리터럴을 쓸 수 있어요. 빈 배열 리터럴은 [](빈 대괄호 한 쌍)로 작성해요:
var someInts: [Int] = []
print("someInts is of type [Int] with \(someInts.count) items.")
// Prints "someInts is of type [Int] with 0 items."
아니면 명시적 초기화 문법으로 특정 타입의 빈 배열을 만들 수도 있어요. 아래처럼 요소 타입을 대괄호 안에 쓰고 괄호를 붙이는 방식이에요 — 예를 들어 [Int]()처럼요:
var someInts = [Int]()
print("someInts is of type [Int] with \(someInts.count) items.")
// Prints "someInts is of type [Int] with 0 items."
두 방법 모두 같은 결과를 만들어요. 다만 빈 배열 리터럴이 더 짧고 보통 읽기 쉽죠.
두 경우 모두, 빈 배열 리터럴([])을 사용해 기존 변수에 빈 배열을 다시 할당할 수 있어요:
someInts.append(3)
// someInts now contains 1 value of type Int
someInts = []
// someInts is now an empty array, but is still of type [Int]
기본 값으로 채운 배열 만들기
Swift의 Array 타입은 또한, 특정 크기만큼 모든 값을 같은 기본 값으로 채운 배열을 만드는 초기화를 제공해요. 이 초기화에는 기본 값(이름은 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]
배열 리터럴로 배열 만들기
*배열 리터럴(array literal)*로도 배열을 초기화할 수 있어요. 배열 리터럴은 하나 이상의 값을 배열 컬렉션으로 쓰는 축약 방식이에요. 배열 리터럴은 쉼표로 구분된 값의 목록을 대괄호 한 쌍으로 감싼 형태로 작성해요:
[<#value 1#>, <#value 2#>, <#value 3#>]
아래 예시는 String 값을 저장하는 shoppingList라는 배열을 만드네요:
var shoppingList: [String] = ["Eggs", "Milk"]
// shoppingList has been initialized with two initial items
shoppingList 변수는 "문자열 값들의 배열", 즉 [String]으로 선언돼요. 이 배열이 값 타입을 String으로 지정했기 때문에 String 값만 저장할 수 있어요. 여기서 shoppingList 배열은 배열 리터럴 안에 적힌 두 개의 String 값("Eggs"와 "Milk")으로 초기화됐죠.
Note: 아래 예시들에서 쇼핑 목록에 항목을 더 추가할 거라서,
shoppingList배열은 상수(let)가 아니라 변수(var)로 선언됐어요.
이 경우 배열 리터럴은 두 개의 String 값만 담고 있어요. 이는 shoppingList 변수 선언의 타입(String 값만 담을 수 있는 배열)과 정확히 일치하므로, 배열 리터럴을 할당해 shoppingList를 두 개의 초기 항목으로 초기화하는 것이 허용돼요.
Swift의 타입 추론 덕분에, 같은 타입의 값을 담은 배열 리터럴로 초기화할 때는 배열의 타입을 명시하지 않아도 돼요. shoppingList의 초기화는 사실 더 짧은 형태로도 쓸 수 있었어요:
var shoppingList = ["Eggs", "Milk"]
배열 리터럴의 모든 값이 같은 타입이기 때문에, Swift는 [String]이 shoppingList 변수에 쓰기에 맞는 타입이라고 추론할 수 있어요.
배열 리터럴의 마지막 값 뒤에는 쉼표를 붙일 수도 있는데, 이를 *후행 쉼표(trailing comma)*라고 불러요:
var shoppingList = [
"Eggs",
"Milk",
]
후행 쉼표는 모든 줄이 같은 방식으로 끝나게 해 줘서, 위처럼 한 줄에 값 하나씩 쓰는 배열에서 유용해요. 배열을 바꿀 때도 값을 추가·제거·재배열만 하면 되지, 쉼표를 붙였다 뗐다 할 필요가 없죠.
배열 접근과 수정
배열은 메서드와 프로퍼티, 또는 서브스크립트 문법으로 접근하고 수정해요.
배열의 항목 수를 알려면 읽기 전용 count 프로퍼티를 확인해요:
print("The shopping list contains \(shoppingList.count) items.")
// Prints "The shopping list contains 2 items."
count 프로퍼티가 0인지 확인하는 단축키로 Boolean 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
배열에서 값을 가져오려면 서브스크립트 문법을 써요. 배열 이름 바로 뒤에 대괄호 안으로 가져오고 싶은 값의 인덱스를 넘기면 돼요:
var firstItem = shoppingList[0]
// firstItem is equal to "Eggs"
Note: 배열에서 첫 번째 항목의 인덱스는
1이 아니라0이에요. Swift의 배열은 항상 0부터 인덱싱돼요.
서브스크립트 문법으로 주어진 인덱스의 기존 값을 바꿀 수도 있어요:
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" is now the first item in the list
이 insert(_:at:) 메서드 호출은 "Maple Syrup"이라는 값을 가진 새 항목을 인덱스 0이 가리키는 쇼핑 목록의 맨 앞에 삽입해요.
비슷하게, 배열에서 항목을 제거할 때는 remove(at:) 메서드를 써요. 이 메서드는 지정된 인덱스의 항목을 제거하고 제거된 항목을 반환해요(반환값이 필요 없다면 무시해도 돼요):
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
Note: 배열의 기존 범위를 벗어난 인덱스의 값에 접근하거나 수정하려 하면 런타임 에러가 나요. 인덱스를 쓰기 전에 배열의
count프로퍼티와 비교해 유효한지 확인할 수 있어요. 배열은 0부터 인덱싱되므로 유효한 가장 큰 인덱스는count - 1이에요 — 다만count가0(배열이 비었다는 뜻)이면 유효한 인덱스가 아예 없죠.
항목이 제거되면 배열의 빈 자리들은 닫혀요. 그래서 인덱스 0의 값이 다시 "Six eggs"가 되죠:
firstItem = shoppingList[0]
// firstItem is now equal to "Six eggs"
배열에서 마지막 항목을 제거하고 싶다면 remove(at:) 대신 removeLast() 메서드를 써서 배열의 count 프로퍼티를 조회할 필요를 없애요. remove(at:) 메서드처럼 removeLast()도 제거된 항목을 반환해요:
let apples = shoppingList.removeLast()
// the last item in the array has just been removed
// shoppingList now contains 5 items, and no apples
// the apples constant is now equal to the removed "Apples" string
배열 순회하기
배열의 모든 값을 순회하려면 for-in 루프를 써요:
for item in shoppingList {
print(item)
}
// Six eggs
// Milk
// Flour
// Baking Powder
// Bananas
각 항목의 정수 인덱스까지 값과 함께 필요하다면 enumerated() 메서드를 써서 배열을 순회해요. enumerated() 메서드는 배열의 각 항목마다 정수와 그 항목으로 이루어진 튜플을 반환해요. 정수는 0에서 시작해 항목마다 1씩 증가하고, 배열 전체를 열거하면 이 정수들이 항목의 인덱스와 일치해요. 순회 과정에서 튜플을 임시 상수나 변수로 분해할 수 있어요:
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
for-in 루프에 대한 더 자세한 내용은 doc:ControlFlow#For-In-Loops에서 다뤄요.
집합 (Sets)
*집합(set)*은 정의된 순서가 없는 컬렉션에 같은 타입의 서로 다른 값들을 저장해요. 항목의 순서가 중요하지 않거나, 항목이 한 번만 나타나야 한다는 걸 보장하고 싶을 때 배열 대신 집합을 쓸 수 있어요.
Note: Swift의
Set타입은 Foundation의NSSet클래스와 브리징돼요. Foundation과 Cocoa에서Set을 사용하는 방법에 대한 자세한 내용은 Bridging Between Set and NSSet을 참고해요.
집합 타입을 위한 해시 값
집합에 저장되려면 타입이 *해시 가능(hashable)*해야 해요 — 즉 타입이 자기 자신을 위한 *해시 값(hash value)*을 계산하는 방법을 제공해야 하죠. 해시 값은 Int 값으로, 동등하게 비교되는 모든 객체에 대해 같아요. 그래서 a == b라면 a의 해시 값은 b의 해시 값과 같아요.
Swift의 기본 타입(String, Int, Double, Bool 등)은 전부 기본적으로 해시 가능하며, 집합의 값 타입이나 딕셔너리의 키 타입으로 쓸 수 있어요. 연관 값이 없는 열거형 케이스 값(doc:Enumerations에서 설명)도 기본적으로 해시 가능해요.
Note: Swift 표준 라이브러리의
Hashable프로토콜을 준수하게 만들면 자신만의 커스텀 타입을 집합 값 타입이나 딕셔너리 키 타입으로 쓸 수 있어요. 필요한hash(into:)메서드를 구현하는 방법은Hashable을, 프로토콜 준수 방법은 doc:Protocols를 참고해요.
집합 타입 문법
Swift 집합의 타입은 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."
Note:
letters변수의 타입은 초기화의 타입에서Set<Character>로 추론돼요.
아니면 함수의 인자나 이미 타입이 정해진 변수·상수처럼 컨텍스트가 이미 타입 정보를 제공하는 경우에는 빈 배열 리터럴로 빈 집합을 만들 수도 있어요:
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>
배열 리터럴로 집합 만들기
배열 리터럴로도 집합을 초기화할 수 있어요. 하나 이상의 값을 집합 컬렉션으로 쓰는 축약 방식이죠.
아래 예시는 String 값을 저장하는 favoriteGenres라는 집합을 만들어요:
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// favoriteGenres has been initialized with three initial items
favoriteGenres 변수는 "String 값들의 집합", 즉 Set<String>으로 선언돼요. 이 집합이 값 타입을 String으로 지정했기 때문에 String 값만 딱 저장할 수 있어요. 여기서 favoriteGenres 집합은 배열 리터럴 안에 적힌 세 개의 String 값("Rock", "Classical", "Hip hop")으로 초기화됐죠.
Note: 아래 예시들에서 항목을 추가하고 제거할 거라서,
favoriteGenres집합은 상수(let)가 아니라 변수(var)로 선언됐어요.
집합 타입은 배열 리터럴만으로는 추론되지 않아서 Set 타입을 명시적으로 선언해야 해요. 다만 Swift의 타입 추론 덕분에, 단 한 타입의 값만 담은 배열 리터럴로 초기화할 때는 집합의 요소 타입을 쓰지 않아도 돼요. favoriteGenres의 초기화는 사실 더 짧은 형태로도 쓸 수 있었어요:
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
배열 리터럴의 모든 값이 같은 타입이기 때문에, Swift는 Set<String>이 favoriteGenres 변수에 쓰기에 맞는 타입이라고 추론할 수 있어요.
집합 접근과 수정
집합은 메서드와 프로퍼티로 접근하고 수정해요.
집합의 항목 수를 알려면 읽기 전용 count 프로퍼티를 확인해요:
print("I have \(favoriteGenres.count) favorite music genres.")
// Prints "I have 3 favorite music genres."
count 프로퍼티가 0인지 확인하는 단축키로 Boolean 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
for-in 루프에 대한 더 자세한 내용은 doc:ControlFlow#For-In-Loops에서 다뤄요.
Swift의 Set 타입에는 정의된 순서가 없어요. 집합의 값들을 특정 순서로 순회하려면 sorted() 메서드를 써요. 이 메서드는 < 연산자로 정렬된 집합의 요소들을 배열로 반환해요:
for genre in favoriteGenres.sorted() {
print("\(genre)")
}
// Classical
// Hip hop
// Jazz
집합 연산 수행하기
두 집합을 합치거나, 두 집합이 공통으로 가진 값을 알아내거나, 두 집합이 같은 값의 일부·전부·아무것도 포함하는지 알아내는 것 같은 기본 집합 연산들을 효율적으로 수행할 수 있어요.
기본 집합 연산
아래 그림은 두 집합(a와 b)을, 다양한 집합 연산의 결과를 음영 영역으로 나타낸 것이에요.
intersection(_:)메서드는 두 집합 모두에 공통인 값만 담은 새 집합을 만들어요.symmetricDifference(_:)메서드는 두 집합 중 하나에는 있지만 둘 다에는 없는 값들로 새 집합을 만들어요.union(_:)메서드는 두 집합의 모든 값으로 새 집합을 만들어요.subtracting(_:)메서드는 지정된 집합에 없는 값들로 새 집합을 만들어요.
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, c)을, 집합 사이에서 공유되는 요소들을 겹친 영역으로 나타낸 것이에요. 집합 a는 b의 모든 요소를 포함하므로 b의 *상위 집합(superset)*이에요. 반대로 집합 b는 모든 요소가 a에도 포함되어 있으므로 a의 *부분 집합(subset)*이에요. 집합 b와 c는 공유하는 요소가 하나도 없으므로 서로 *서로소(disjoint)*예요.
- "같다" 연산자(
==)로 두 집합이 같은 값들을 모두 포함하는지 알아내요. 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
딕셔너리 (Dictionaries)
*딕셔너리(dictionary)*는 정의된 순서가 없는 컬렉션에 키와 값을 각각 같은 타입끼리 짝지어 저장해요. 각 값은 그 딕셔너리 안에서 값의 식별자 역할을 하는 유일한 *키(key)*와 연결돼요. 배열의 항목과 달리 딕셔너리의 항목에는 지정된 순서가 없어요. 실생활 사전이 특정 단어의 뜻을 찾는 데 쓰이듯, 값의 식별자에 기반해 값을 조회하고 싶을 때 딕셔너리를 써요.
Note: Swift의
Dictionary타입은 Foundation의NSDictionary클래스와 브리징돼요. Foundation과 Cocoa에서Dictionary를 사용하는 방법에 대한 자세한 내용은 Bridging Between Dictionary and NSDictionary를 참고해요.
딕셔너리 타입 축약 문법
Swift 딕셔너리의 타입은 Dictionary<Key, Value>로 온전히 쓰는데, 여기서 Key는 딕셔너리 키로 쓸 수 있는 값의 타입이고, Value는 딕셔너리가 그 키들에 대해 저장하는 값의 타입이에요.
Note: 집합의 값 타입과 마찬가지로, 딕셔너리의
Key타입은Hashable프로토콜을 준수해야 해요.
딕셔너리의 타입은 [Key: Value]처럼 축약 형태로도 쓸 수 있어요. 두 형태는 기능적으로 완전히 같지만, 축약 형태가 더 선호되며 이 가이드에서는 딕셔너리의 타입을 가리킬 때 축약 형태를 사용할게요.
빈 딕셔너리 만들기
배열과 마찬가지로 초기화 문법으로 특정 타입의 빈 Dictionary를 만들 수 있어요:
var namesOfIntegers: [Int: String] = [:]
// namesOfIntegers is an empty [Int: String] dictionary
이 예시는 정수 값들의 사람이 읽을 수 있는 이름을 저장하려고 [Int: String] 타입의 빈 딕셔너리를 만들어요. 키는 Int 타입이고, 값은 String 타입이죠.
컨텍스트가 이미 타입 정보를 제공한다면 빈 딕셔너리 리터럴로 빈 딕셔너리를 만들 수 있어요. 빈 딕셔너리 리터럴은 [:](대괄호 한 쌍 안에 콜론)로 작성해요:
namesOfIntegers[16] = "sixteen"
// namesOfIntegers now contains 1 key-value pair
namesOfIntegers = [:]
// namesOfIntegers is once again an empty dictionary of type [Int: String]
딕셔너리 리터럴로 딕셔너리 만들기
*딕셔너리 리터럴(dictionary literal)*로도 딕셔너리를 초기화할 수 있어요. 앞서 본 배열 리터럴과 비슷한 문법이죠. 딕셔너리 리터럴은 하나 이상의 키-값 쌍을 Dictionary 컬렉션으로 쓰는 축약 방식이에요.
*키-값 쌍(key-value pair)*은 키와 값의 조합이에요. 딕셔너리 리터럴에서 각 키-값 쌍의 키와 값은 콜론으로 구분돼요. 키-값 쌍들은 쉼표로 구분된 목록으로, 대괄호 한 쌍으로 감싸서 작성해요:
[<#key 1#>: <#value 1#>, <#key 2#>: <#value 2#>, <#key 3#>: <#value 3#>]
아래 예시는 국제 공항의 이름을 저장하는 딕셔너리를 만들어요. 이 딕셔너리에서 키는 세 글자 국제항공운송협회(IATA) 코드이고, 값은 공항 이름이에요:
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
airports 딕셔너리는 [String: String] 타입으로 선언됐는데, 이는 "키도 String 타입이고 값도 String 타입인 Dictionary"라는 뜻이에요.
Note: 아래 예시들에서 딕셔너리에 공항을 더 추가할 거라서,
airports딕셔너리는 상수(let)가 아니라 변수(var)로 선언됐어요.
airports 딕셔너리는 두 개의 키-값 쌍을 담은 딕셔너리 리터럴로 초기화돼요. 첫 번째 쌍은 키가 "YYZ"이고 값이 "Toronto Pearson"이며, 두 번째 쌍은 키가 "DUB"이고 값이 "Dublin"이에요.
이 딕셔너리 리터럴은 String: String 쌍 두 개를 담고 있어요. 이 키-값 타입은 airports 변수 선언의 타입(String 키와 String 값만 가진 딕셔너리)과 일치하므로, 딕셔너리 리터럴을 할당해 airports를 두 개의 초기 항목으로 초기화하는 것이 허용돼요.
배열과 마찬가지로, 키와 값의 타입이 일관된 딕셔너리 리터럴로 초기화할 때는 딕셔너리의 타입을 쓰지 않아도 돼요. airports의 초기화는 사실 더 짧은 형태로도 쓸 수 있었어요:
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
리터럴의 모든 키가 서로 같은 타입이고, 모든 값도 서로 같은 타입이기 때문에, Swift는 [String: String]이 airports 딕셔너리에 쓰기에 맞는 타입이라고 추론할 수 있어요.
배열 리터럴처럼, 딕셔너리 리터럴도 마지막 키-값 쌍 뒤에 후행 쉼표를 포함할 수 있어요:
var airports = [
"YYZ": "Toronto Pearson",
"DUB": "Dublin",
]
딕셔너리 접근과 수정
딕셔너리는 메서드와 프로퍼티, 또는 서브스크립트 문법으로 접근하고 수정해요.
배열과 마찬가지로, Dictionary의 항목 수를 알려면 읽기 전용 count 프로퍼티를 확인해요:
print("The airports dictionary contains \(airports.count) items.")
// Prints "The airports dictionary contains 2 items."
count 프로퍼티가 0인지 확인하는 단축키로 Boolean isEmpty 프로퍼티를 써요:
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:) 메서드는 갱신을 수행한 뒤 이전 값을 반환해요. 덕분에 갱신이 실제로 일어났는지 확인할 수 있죠.
updateValue(_:forKey:) 메서드는 딕셔너리 값 타입의 옵셔널 값을 반환해요. 예를 들어 String 값을 저장하는 딕셔너리라면 이 메서드는 String? 타입, 즉 "옵셔널 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 루프로 딕셔너리의 키-값 쌍들을 순회할 수 있어요. 딕셔너리의 각 항목은 (key, value) 튜플로 반환되고, 순회 과정에서 튜플의 구성원들을 임시 상수나 변수로 분해할 수 있어요:
for (airportCode, airportName) in airports {
print("\(airportCode): \(airportName)")
}
// LHR: London Heathrow
// YYZ: Toronto Pearson
for-in 루프에 대한 더 자세한 내용은 doc:ControlFlow#For-In-Loops에서 다뤄요.
딕셔너리의 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"]
Swift의 Dictionary 타입에는 정의된 순서가 없어요. 딕셔너리의 키나 값을 특정 순서로 순회하려면 keys나 values 프로퍼티에 sorted() 메서드를 써요.
더 알아보기
- 배열, 집합, 딕셔너리는 모두 제네릭 컬렉션으로 구현돼요. 제네릭 타입과 컬렉션에 대한 더 자세한 내용은 doc:Generics에서 다뤄요.
for-in루프의 사용법은 doc:ControlFlow#For-In-Loops에서 자세히 살펴볼 수 있어요.- 커스텀 타입을 집합 값 타입이나 딕셔너리 키 타입으로 쓰려면
Hashable프로토콜 준수가 필요해요. 구현 방법은Hashable문서와 doc:Protocols 챕터를 참고해요.