Swift에서 컬렉션은 가변형(mutable)과 불변형(immutable)이 있다.
불변형 컬렉션 인스턴스에 속한 것은 객체가 초기화된 이후에 변경될 수 없다.
불변형 컬렉션을 만들고 싶다면 컬렉션을 생성할 때 상수(constant)에 할당한다.
반면, 변수(variable)에 할당했다면 가변형이 된다.
배열은 하나의 순서 있는 컬렉션에 여러 값을 담기 위하여 특별하게 설계된 데이터 타입이다.
예를 들어, String 값들을 저장하기 위해 배열을 생성할 수 있다.
엄밀히 말하지면, 하나의 스위프트 배열은 동일한 타입의 값들만 저장할 수 있다.
따라서 String 값을 포함하도록 선언된 배열에 Int 값을 포함할 수 없다.
그러나 여러 타입이 혼합된 배열을 생성할 수도 있다.
배열의 타입은 타입 어노테이션(type annotation)을 이용하여 구체적으로 지정할 수도 있고, 타입 추론(type inference)을 이용하여 컴파일러가 식별하게 할 수도 있다.
다음의 구문을 이용하면 배열을 생성할 때 값들을 갖도록 초기화할 수 있다.
이것을 배열 리터럴(array literal)이라고 부른다.
var 변수명: [타입] = [값1, 값2, 값3, ...]
다음의 코드는 세 개의 문자열 값으로 초기화되어 생성된 새로운 배열을 변수에 할당한다.
이 인스턴스에서 Swift 컴파일러는 타입 추론을 이용하여 배열이 String 타입의 값을 담고 있다고 판단하며, 앱 코드 어디에서든 다른 타입의 값이 배열에 추가되지 않도록 막을 것이다.
var treeArray = ["Pine", "Oak", "Yew"]
다른 방법으로, 동일한 배열을 가지고 타입 어노테이션을 이용하여 선언할 수 있다.
var treeArray: [String] = ["Pine", "Oak", "Yew"]
배열을 생성할 때 반드시 값을 할당해야 할 필요는 없다. 다음 구문은 빈 배열을 생성할 때 사용되는 것이다.
var 변수명 = [타입]()
예를 들어, 다음의 코드는 부동소수점 값을 저장하도록 설계한 빈 배열을 생성하고 priceArray라는 이름의 변수에 할당한다.
var priceArray = [Float]()
또 다른 유용한 초기화 기술로는 배열의 각 항목마다 지정된 디폴트 값으로 미리 설정하여 배열이 특정 크기로 초기화되도록 할 수 있다.
var nameArray = [String](repeating: "My String", count: 10)
앞의 코드를 컴파일하고 실행하면 "My String"이라는 문자열로 배열의 각 항목이 초기화되어 10개의 항목을 가진 배열이 생성된다.
마지막으로, 기존의 배열 두 개를 합하여(배열 모두가 동일한 타입의 값을 포함하고 있다고 가정) 새로운 배열을 생성할 수도 있다.
let firstArray = ["Red", "Green", "Blue"]
let secondArray = ["Indigo", "Violet"]
let thirdArray = firstArray + secondArray
하나의 배열에 들어 있는 항목들의 개수는 배열의 count 프로퍼티에 접근하여 얻을 수 있다.
var treeArray = ["Pine", "Oak", "Yew"]
var itemCount = treeArray.count
print(itemCount) // 3
다음과 같이 불리언 타입인 isEmpty 프로퍼티를 이용하면 배열이 비었는지 알 수 있다.
var treeArray = ["Pine", "Oak", "Yew"]
if treeArray.isEmpty {
//배열이 비어있다.
}
// 다른 방법
// if treeArray.count < 1
인덱스 첨자(index subscripting)라 불리는 기술을 이용하여 배열 인덱스의 항목 위치를 참조하여 배열의 특정 항목에 접근하거나 수정할 수 있다.
배열의 첫 번째 항목의 인덱스 위치는 0이다.
다음의 코드는 배열의 인덱스 위치 2에 포함된 문자열을 print 호출로 출력한다.
var treeArray = ["Pine", "Oak", "Yew"]
print(treeArray[2]) // Yew
배열 객체의 shuffled() 메서드를 호출하면 항목의 순서가 무작위로 섞인 새로운 버전의 배열이 반환된다.
let shuffledTrees = treeArray.shuffled()
배열의 항목을 무작위로 선택하여 접근하려면 randomElement() 메서드를 호출하면 된다.
let shuffledTrees = treeArray.randomElement()
배열의 항목은 append 메서드 또는 +나 += 연산자를 이용하여 배열에 추가될 수 있다.
다음의 예제는 배열에 항목을 추가할 수 있는 방법으로 유효한 기술이다.
treeArray.append("Redwood")
treeArray += ["Redwood"]
treeArray += ["Redwood", "Maple", "Birch"]
배열에 삽입될 새로운 항목은 inser(at:) 메서드를 호출하여 특정 인덱스 위치에 삽입할 수 있다.
삽입은 배열 내에 있는 기존의 모든 항목을 보호하므로, 새롭게 삽입된 항목을 배열 내에 두기 위하여 새로운 항목이 삽입되는 인덱스 위치를 포함하여 그 뒤에 있던 기존 항목들을 오른쪽으로 한 칸씩 이동하게 된다.
treeArray.insert("Maple", at: 0)
treeArray = ["Maple"] + treeArray
마찬가지로, 배열의 특정 인덱스 위치에 있는 항목은 remove(at:) 메서드를 호출하여 제거할 수 있다.
treeArray.remove(at: 2)
배열의 마지막 항목을 삭제하려면 다음과 같이 removeLast() 메서드를 호출하면 된다.
treeArray.removeLast()
배열의 항목을 반복하는 가장 쉬운 방법은 for-in 반복문을 이용하는 것이다.
예를 들어, 다음의 코드는 String 배열에 있는 모든 항목을 반복하여 각각의 콘솔에 출력하는 것이다.
let treeArray = ["Pine", "Oak", "Yew", "Maple", "Birch", "Myrtle"]
for tree in treeArray {
print(tree)
}
// Pine
// Oak
// Yew
// Maple
// Birch
// Myrtle
타입이 혼합된 배열은 서로 다른 클래스 타입의 항목들을 담을 수 있는 배열이다.
물론, String 타입을 받도록 선언된 배열이라면 String 클래스 객체가 아닌 인스턴스를 담을 수는 없다.
하지만, Swift에서는 Any 타입이 있기 때문에 흥미로운 방법이 생긴다.
Swift의 Any 타입은 특별한 타입으로, 지정된 클래스 타입이 아닌 객체를 참조하는 데 사용된다.
따라서 Any 객체 타입을 포함하도록 선언된 배열은 여러 타입의 항목을 담을 수 있게 된다.
예를 들면 다음의 코드는 배열을 생성하고 String과 Int, 그리고 Double형의 항목들을 포함하도록 초기화하고 있다.
let mixedArray: [Any] = ["A String", 432, 34.989]
Any 타입은 주의해서 사용해야 한다.
왜냐하면 Any를 사용할 경우 Swift는 올바른 타입의 요소들이 배열에 포함되었다고 간주하게 되므로 코드상에서의 프로그래머 실수로 인한 오류가 발생할 수 있기 때문이다.
Any 배열을 사용하게 된다면 배열에 있는 요소를 가지고 코드에서 사용하기 전에 각 요소에 대한 올바른 타입으로의 형 변환을 직접 해야 하는 경우가 종종 생길 것이다.
배열에 있는 어떤 요소에 대해 올바르지 않은 타입으로 형 변환을 할 경우, 컴파일 오류는 발생하지 않겠지만 런타임에서 충돌이 발생할 것이다.
let mixedArray: [Any] = [1, 2, 45, "Hello"]
위와 같이 배열을 초기화한 다음, 배열의 정수형 요소에 10을 곱하는 반복문을 해야 한다면 다음과 같은 코드를 생각할 수 있다.
for object in mixedArray {
print(object * 10)
}
하지만 이 코드를 Xcode에 입력하면 Any 타입과 Int 타입의 곱셈 연산이 불가능하다는 구문 오류가 발생할 것이다.
이 오류를 없애려면 배열의 요소를 Int 타입으로 다운캐스팅해야한다.
아래 코드는 아무런 오류 없이 컴파일되며, 기대한 것처럼 동작하다가 배열의 마지막인 String 요소에 다다랐을 때 에러 메시지와 함께 충돌이 발생하게 된다.
따라서 이 코드는 배열에 있는 각 항목의 특정 타입을 식별하도록 수정되어야 한다.
분명한 것은 Swift에서 Any 배열을 이용할 때는 장점과 단점이 모두 존재한다는 점이다.
for object in mixedArray {
print(object as! Int * 10)
}
// Cloud not cast value of type 'swift.string' to 'swift.Int'
딕셔너리는 키-값(Key-value) 쌍의 형태로 데이터를 저장하고 관리할 수 있게 해준다.
딕셔너리는 배열과 비슷한 목적의 작업을 실시하지만, 딕셔너리에 저장된 각 항목은 연관된 값을 참조하고 접근하는 데 사용되는 유일한 키(정확하게 말하지면, 키는 특정 딕셔너리 객체에서 유일하다)와 연결되어 있다는 점이 다르다.
현재는 String, Int, Double, 그리고 Bool 데이터 타입만 Swift 딕셔너리에 키로 사용할 수 있다.
딕셔너리는 순서가 ㅇ벗는 단일 컬렉션에 여러 값을 담기 위해 설계된 특별한 타입이다.
딕셔너리에 있는 각 항목은 키(Key)와 그와 연관된 값(value)으로 구성된다.
키의 데이터 타입과 값 항목은 타입 어노테이션(type annotation)을 이용하여 구체적으로 지정되거나, 타입 추론(type inference)을 이용하여 컴파일러가 식별하게 한다.
새로운 딕셔너리는 다음의 구문을 이용하여 생성 시에 값들의 컬렉션으로 초기화 할 수 있다.
이것을 딕셔너리(dictionary literal)이라고 한다.
var 변수명: [키 타입: 값 타입] = [키1: 값1, 키2: 값2, ...]
다음 코드는 ISBN 번호와 그에 해당하는 책 제목의 형태로 네 개의 키-값 쌍으로 초기화된 변수에 할당된 새로운 딕셔너리를 생성한다.
var bootDict = ["100-432112": "Wind in the Willows",
"200-532874": "Tale of Two Cities",
"202-546549": "Sense and Sensibility",
"104-109834": "Shutter Island"]
위 인스턴스에서 Swift 컴파일러는 딕셔너리의 키와 값 항목이 String 타입임을 결정하고 다른 타입의 키 또는 값이 삽입되지 않도록 막기 위해 타입 추론을 이용할 것이다.
다른 방법으로, 타입 어노테이션을 이용하여 동일한 배열을 선언할 수도 있다.
var bootDict: [String: String] =
["100-432112": "Wind in the Willows",
"200-532874": "Tale of Two Cities",
"202-546549": "Sense and Sensibility",
"104-109834": "Shutter Island"]
배열처럼 빈 딕셔너리를 생성할 수도 있다.
var 변수명 = [키 타입: 값 타입]()
다음의 코드는 정수형 키와 문자열 값을 저장하기 위하여 설계된 빈 딕셔너리를 생성한다.
var myDictionary = [Int: String]()
딕셔너리는 키들과 값들을 나타내는 시퀀스를 이용하여 초기화될 수도 있다.
이것은 키들과 값들을 Swift의 zip() 함수에 전달하면 된다.
다음의 예제는 두 개의 배열을 이용하여 딕셔너리를 생성한다.
var keys = ["100-432112", "200-532874", "202-546549", "104-109834"]
var values = ["Wind in the Willows", "Tale of Two Cities",
"Sense and Sensibility", "Shutter Island"]
var bootDict = Dictionary(uniqueKeysWithValues: zip(keys, values))
이 방법은 키들과 값들을 프로그램적으로 생성되게 해준다.
다음의 예제는 미리 정의된 키들의 배열을 이용하는 것이 아니라 1부터 시작하는 숫자를 키로 지정한다.
var values = ["Wind in the Willows", "Tale of Two Cities",
"Sense and Sensibility", "Shutter Island"]
var bootDict = Dictionary(uniqueKeysWithValues: zip(1..., values))
다음의 코드는 앞의 코드와 동일한 작업을 수행하지만, 훨씬 깔끔하게 정리되었다.
var bootDict = [1: "wind in the Willows",
2: "Tale of Two Cities",
3: "Sense and Sensibility",
4: "Sutter Island"]
딕셔너리 항목 개수는 count 프로퍼티에 접근하여 얻을 수 있다.
print(bootDict.count)
특정 값은 해당 키를 참조하기 위하여 키 첨자 구문을 이용하면 접근되거나 수정될 수 있다.
다음의 코드는 bookDict 딕셔너리에 있는 키를 참조하며 해당 값을 출력한다.
print(bootDict["200-532874"])
이와 같이 딕셔너리 항목에 접근할 때도 지정된 키에 값이 없는 경우에 사용될 디폴트 값을 선언할 수 있다.
우리가 만든 딕셔너리에는 지정된 키에 대한 항목이 없기 때문에 아래 코드는 default 텍스트를 출력할 것이다.
print(bootDict["999-546547", default: "Book not found"]) //Book not found
마찬가지 방법으로, 특정 키와 연결된 값을 갱신할 수도 있다.
예를 들어, 다음과 같이 'Tale of Two Cities' 라는 제목을 'Sense and Sensibility'로 바꿀 수 있다.
bootDict["200-532874"] = "Sense and Sensibility"
변경될 값과 해당 키를 전달하여 updateValue(forKey:) 메서드를 호출해도 같은 동작을 한다.
bootDict.updateValue("The Ruins", forKey: "200-532874")
다음의 키 첨자 구문을 이용하면 딕셔너리에 항목을 추가할 수 있다.
딕셔너리 변수[키] = 값
예를 들어, 다음과 같이 책 딕셔너리에 새로운 키-값 쌍을 추가할 수 있다.
bookDict["300-898871"] = "The Overlook"
어떤 키-값 쌍을 딕셔너리에서 제거할 때는 해당 항목에 nil 값을 할당하거나 딕셔너리 인스턴스의 removeValue(forKey:) 메서드를 호출하면 된다.
다음의 두 줄은 모두 책 딕셔너리에서 특정 항목을 삭제하는 결과를 만든다.
bookDict["300-898871"] = nil
bookDict.removeValue(forKey: "300-898871")
배열과 마찬가지로 for-in 반복문을 이용하면 딕셔너리의 항목들을 반복할 수 있다.
예를 들어, 다음 코드는 책 딕셔너리에 있는 모든 항목을 가져다가 각각의 키와 값을 출력한다.
for (bookId, title) in bookDict {
print("Book ID: \(bookId) Title: \(title)")
}
// BookID: 100-432112 Title: Wind in the Willows
// BookID: 200-532874 Title: The Ruins
// BookID: 104-109734 Title: Shutter Island
// BookID: 202-546549 Title: Sens and Sensibility
Swift의 컬렉션은 딕셔너리(Dictionary), 세트(Set) 또는 배열(Array)의 형태를 취한다.
이들 모두 하나의 객체에 여러 항목을 담을 수 있는 방법을 제공한다.
배열은 항목들을 순서대로 담을 수 있는 방법을 제공하여 배열에 담긴 항목 위치에 해당하는 인덱스 값으로 항목에 접근할 수 있도록 한다.
딕셔너리는 키-값 쌍으로 저장하는 플랫폼을 제공하며, 키는 저장된 값에 접근하는 데 사용된다.
Swift 컬렉션의 항목들은 for-in 반복문을 이용하여 반복할 수 있다.