스위프트는 서로 관련이 있는 데이터끼리 모아 관리할 수 있도록 집단 자료형(Collection Types)를 제공한다. 그 중 먼저 배열(Array)에 대해서 알아보자.
배열(Array)는 일련의 순서를 가지는 리스트 형식의 값을 저장하는데 사용되는 자료형으로, 대부분의 언어에서 제공하는 자료형이다. 배열에 입력되는 아이템들은 각각의 순서가 있는데 이를 인덱스(Index)라고 한다.
배열에서 인덱스는 0부터 순서대로 할당되고 중간에 값을 생략하거나 건너뛰는 것은 불가능하다. 인덱스에 연결된 아이템이 삭제되더라도 뒤 아이템들이 한 칸씩 앞으로 이동하면서 빈 인덱스의 자리를 채우게된다. 물론 이런식으로 마지막 인덱스의 아이템은 앞으로 이동할 아이템들이 없기 때문에 바로 삭제된다.
스위프트에서 사용하는 배열은 다음과 같은 특징을 가지고있다.
- 배열에 저장할 아이템의 자료형에 제한은 없지만 하나의 배열에는 아이템들의 자료형이 모두 같아야한다.
- 선언 시 배열에 저장할 아이템을 명확히 정의해야 한다.
- 배열의 크기는 동적(늘리거나 줄이거나)할 수 있다.
스위프트에서의 배열을 정의하는 방법은 정적(static)인 방식과 동적(dynamic)인 방식으로 나눌 수 있는데 정적인 방식은 배열을 정의할 때부터 아이템들을 포함해 정의하는 방식이고 동적인 방식은 후에 아이템들을 유동적으로 추가해줄 수 있게하는 방식으로 동적인 방식으로 대부분 사용된다.
// 배열의 선언과 값 할당(정적인 방식)
var cities = ["Seoul", "Busan", "Incheon", "Daegu"]
위와 같이 정적인 방식으로 배열을 생성할 수있다. 이 때 cities는 문자열 아이템을 가지는 배열이 된다. 이렇게 생성한 아이템은 다음과 같이 인덱스를 사용해 참조할 수 있다.
cities[0] = "Seoul"
cities[1] = "Busan"
cities[2] = "Incheon"
cities[3] = "Daegu"
위에서 언급했듯 배열의 인덱스는 0부터 시작하니 이를 꼭 유의해야 한다.
배열 순회 탐색
순서가 있는 데이터를 처음부터 마지막까지 읽어들이는 것을 순회 탐색 이라고한다. 순회 탐색에는 주로 반복문이 사용된다.
배열을 순회탐색할 때에는 주로 for ~ in 구문을 사용하게 되는데 배열의 길이를 직접 다루는 방식, 배열의 순회 특성을 이용하는 방식 두 가지가 사용된다.
배열의 길이를 구할 때엔 .count 속성(properties)를 사용해주면 된다.
var cities = ["Seoul", "Busan", "Incheon", "Daegu"]
cities.count // 배열의 길이 : 4
첫번째 방법 .count로 배열의 길이를 구한 후 순회탐색에 사용한 예를 살펴보자.
var cities = ["Seoul", "Busan", "Incheon", "Daegu"]
let length = cities.count
for i in 0..<length { // 0부터 시작하기에 length 보다 1작아야 하므로 반닫힌 범위연산자 사용
print("\(i)번째 배열 원소는 \(cities[i])입니다")
}
/* 실행결과
0번째 배열 원소는 Seoul입니다
1번째 배열 원소는 Busan입니다
2번째 배열 원소는 Incheon입니다
3번째 배열 원소는 Daegu입니다
두 번째 방법은 이보다 더 간단한데 바로 배열의 순회 특성 이터레이터(Iterator)를 이용하는 방식이다.
그냥 배열 데이터를 직접 for ~ in 구문에 넣으면 배열의 순회특성 때문에 알아서 배열이 순회가 된다. 예제를 살펴보자.
var cities = ["Seoul", "Busan", "Incheon", "Daegu"]
var row in cities {
print("배열 원소는 \(row)입니다")
}
/* 실행결과
배열 원소는 Seoul입니다
배열 원소는 Busan입니다
배열 원소는 Incheon입니다
배열 원소는 Daegu입니다
이러한 방식으로 배열을 탐색하면 루프 상수에 담기는 값은 현재의 인덱스 값이 아니라 배열 아이템 자체이므로, 몇 번째 아이템인지 인덱스를 바로 알기에 어려움이 존재한다. 이 때는 index(of:)를 사용하면 아이템을 통해 인덱스 값을 역으로 찾을 수 있다.
var cities = ["Seoul", "Busan", "Incheon", "Daegu"]
for row in cities {
let index = cities.index(of: row) //
print("\(index!)번째 배열 원소는 \(row)입니다")
}
/* 실행결과
0번째 배열 원소는 Seoul입니다
1번째 배열 원소는 Busan입니다
2번째 배열 원소는 Incheon입니다
3번째 배열 원소는 Daegu입니다
예제에서 사용된 index(of:)는 메소드(Method)라는 것인데, 쉽게 말하자면 함수안에 있는 함수라고 생각하면 된다. 아무튼 여기에선 '배열의 아이템을 넣으면 그 아이템이 배열에서 몇 번째 인덱스에 저장되어있는지 알려주는 역할'로 생각하면 된다.
배열의 동적선언과 초기화
배열을 정의할 땐 한꺼번에 아이템을 집어넣고 정적으로 사용하는 것보단 선언과 초기화만 해 놓은 후에 필요에 따라 그때 그때 동적으로 아이템을 추가하는 경우가 훨씬 많다.
배열을 동적으로 선언하는 방법은 선언 + 초기화를 한 번에 하는 방법과 선언만하고 후에 초기화를 하는 두 가지 형식이 가능하다.
var cities = Array<String>() // 선언 + 초기화를 한 번에
var cities : Array<String> // 선언
cities = Array() // 초기화
스위프트에서 배열을 정의할 때에는 반드시 저장할 아이템의 타입을 명시 해주어야하며 동적으로 배열을 선언할 때에는 반드시 선언과 초기화를 한 번에 하던 차례대로하던 무조건 해주어야한다.
var cities : Array<String>과 같이 선언만된 배열은 초기화가 되지 않아서 아직 메모리 공간을 할당받지 않은 상태이다. 따라서 이 배열에는 아무것도 저장할 수가 없는 상태이다.
cities = Array()로 초기화를 해주어야 메모리 공간을 할당받고 아이템(문자열)을 저장할 수 있게된다.
이와 같은 형식은 다른 언어의 배열 형식과 달라서 시간이 지나 업그레이드를 통해 다음과 같은 방식으로도 배열을 선언할 수 있게 되었다.
var cities = [String]() // 선언 + 초기화
var cities : [String] // 선언
cities = [String]() // 초기화 방법 1
cities = [] // 초기화 방법 2
선언과 초기화를 분리하여 할 경우엔 방법이 2가지가 되는데 첫 번째 방법의 경우 선언된 배열 그대로를 초기화 하지만, 두 번째 방법은 빈 배열 하나를 새로 만들어 변수에 할당하는 것이다.
선언된 배열이 비어있는지를 체크해야 할 경우가 종종 생기는데 이 때는 배열 구조체에서 제공하는 속성인 isEmpty를 사용하면 좋다. 이 속성은 배열에 아이템이 없으면 true 있으면 false를 반환한다.
var list = [String]()
if list.isEmpty {
print("배열이 비어 있는 상태입니다")
} else {
print("배열에는 \(list.count)개의 아이템이 저장되어 있습니다")
}
/* 실행결과
배열이 비어 있는 상태입니다
배열 아이템 동적추가
배열에 아이템을 동적으로 추가할 때에는 메소드를 사용하는데, 대표적으로 다음과 같은 세 가지를 사용한다.
- append(_:)
- insert(_:at:)
- append(contentsOf:)
append(_:) 메소드는 입력된 값을 배열의 맨 뒤에 추가한다. 아이템 추가 전에 먼저 배열의 크기를 +1 확장시켜 공간을 만든 뒤에, 인자값을 마지막 인덱스 위치에 추가하는 형식이다.
insert(_:at:) 메소드는 아이템을 배열의 맨 뒤가 아닌 원하는 위치에 직접 추가하고 싶을 때 사용한다. at: 뒤에 입력되는 정수값은 배열에서 아이템이 추가될 때 인덱스 위치를 의미한다. 이 인덱스에 값이 추가되면 추가된 값을 기준으로 하여 나머지 인덱스들은 하나씩 뒤로 밀려나는 결과를 가져온다.
append(contentsOf:) 메소드는 append(_:) 메소드와 같이 맨 마지막에 아이템을 추가하지만, 개별이 아닌 여러 개의 아이템을 배열에 한꺼번에 추가할 때 사용하는 메소드이다. 이를 위해 메소드의 인자값은 항상 배열이어야 한다.
var cities = [String]()
cities.append("Seoul") // ["Seoul"]
cities.append("Busan") // ["Seoul", "Busan"]
cities.insert("Incheon", at: 1) // ["Seoul", "Inchoen", "Busan"]
cities.append(contentsOf: ["Daegu", "Gwangju"]) // ["Seoul", "Incheon", "Busan", "Daegu", "Gwangju"]
/* 배열의 구성
0 "Seoul"
1 "Incheon"
2 "Busan"
3 "Daegu"
4 "Gwangju"
입력된 값을 변경하고 싶을 경우엔 배열의 인덱스를 이용하여 변경할 값을 직접 대입하면 된다. 그러면 덮어씌워지는 방식으로 값의 변경이 이루어진다.
cities[2] = "Suncheon"
/* 배열의 구성
0 "Seoul"
1 "Incheon"
2 "Suncheon"
3 "Daegu"
4 "Gwangju"
범위 연산자를 이용한 인덱스 참조
범위 연산자를 이용하면 특정 범위의 인덱스에 해당하는 아이템을 모두 참조할 수 있다.
var alphabet = ["a", "b", "c", "d", "e"]
alphabet[0...2] // ["a", "b", "c"]
alphabet[2...3] // ["c", "d"]
alphabet[1..<3] // ["b", "c"]
범위연산자로 배열을 읽어들일 때 조금 특이한 결과가 나타난다. 범위 연산자로 읽은 배열에 새로운 값을 할당하면 할당할 배열 아이템과 범위 연산자로 읽어들인 배열의 크기가 일치하지 않을 때도 값을 변경할 수 있게된다.
.
.
.
alphabet[1...2] = ["1", "2", "3"]
// alphabet = ["a", "1", "2", "3", "d", "e"]
기존에 인덱스 1, 2에 있던 ["b", "c"] 가 사라지고 ["1", "2", "3"] 이 대신하게 되었다. 범위 연산자로 가져온 배열보다 할당될 배열의 크기가 작은 경우도 이와 다소 비슷하다.
.
.
.
alphabet[2...4] = ["A"]
// alphabet = ["a", "1", "A", "e"]
기존 인덱스 2, 3, 4에 있던 ["2", "3", "d"] 가 사라지고 ["A"] 하나가 추가된 모습을 확인할 수 있다.
출처 : 꼼꼼한 재은씨의 Swift 문법편
'Swift > 문법' 카테고리의 다른 글
스위프트(Swift) - 집단 자료형(Collection Types) Ⅲ. 튜플(Tuple) (0) | 2022.05.09 |
---|---|
스위프트(Swift) - 집단자료형(Collection Types) Ⅱ. 집합(Set) (0) | 2022.05.06 |
스위프트(Swift) - 제어 전달문(Control Transfer Statements) (0) | 2022.05.04 |
스위프트(Swift) - 조건문 Ⅳ. switch 구문 (0) | 2022.05.03 |
스위프트(Swift) - 조건문 Ⅲ. #available 구문 (0) | 2022.05.02 |