Array와 List는 모두 데이터를 저장하는 자료구조이지만, 몇 가지 중요한 차이점이 있습니다. iOS 개발에서는 특히 두 자료구조를 활용할 때 성능과 사용성의 차이를 이해하는 것이 중요합니다.
1. 기본 개념
- Array: 고정된 크기의 메모리 블록에 데이터를 순차적으로 저장하는 자료구조입니다. 한 번 크기를 설정하면 변경할 수 없어서, 처음 설정한 크기에 맞춰 사용해야 합니다. Array는 메모리에서 연속적으로 데이터를 저장하기 때문에 데이터에 빠르게 접근할 수 있습니다.
- List: 동적으로 크기가 변경 가능한 자료구조입니다. 보통 연결 리스트(Linked List)로 구현되며, 데이터가 메모리에 연속적으로 저장되지 않아도 됩니다. 데이터가 추가될 때마다 메모리 공간을 새로 할당하거나 링크를 생성하여 크기를 유연하게 조정할 수 있습니다.
2. 메모리 사용
- Array는 미리 할당된 연속적인 메모리 블록을 사용하기 때문에 메모리의 효율이 좋습니다. 각 요소가 연속적으로 배치되어 있어서 특정 위치의 데이터에 즉시 접근할 수 있습니다.
- List는 동적으로 크기를 조정할 수 있기 때문에 메모리 할당과 해제가 빈번하게 발생할 수 있습니다. 각 요소가 서로 연결되어 있으므로 특정 요소를 찾거나 수정할 때 이전 요소를 따라가야 하므로 메모리 접근 속도가 상대적으로 느립니다.
3. 데이터 접근 속도
- Array는 인덱스를 통해 직접 접근할 수 있기 때문에 특정 인덱스에 있는 데이터에 빠르게 접근할 수 있습니다. 예를 들어, array[3]으로 4번째 요소에 바로 접근할 수 있습니다.
- List는 특정 인덱스에 접근하려면 첫 요소부터 순차적으로 탐색해야 하기 때문에 접근 속도가 느립니다. list[3]에 접근하려면 앞의 요소들을 모두 거쳐야 합니다.
4. 데이터 추가/삭제
- Array는 크기가 고정되어 있어 데이터 추가가 제한적입니다. 크기를 늘리려면 새로운 Array를 만들어 기존 데이터를 복사해야 하기 때문에 데이터 추가 및 삭제가 비효율적일 수 있습니다.
- List는 크기가 동적으로 조정되기 때문에 새로운 데이터를 삽입하거나 삭제하기 쉽습니다. 메모리 블록의 이동이 없기 때문에 삽입, 삭제가 효율적입니다.
5. iOS 개발에서의 활용 예
- Array는 주로 크기가 일정하고, 빠른 데이터 접근이 필요한 경우에 사용됩니다. 예를 들어, 앱의 설정 옵션이나 메뉴 항목처럼 미리 정해진 데이터가 고정된 순서로 필요한 경우 Array를 사용하면 좋습니다.
- List는 데이터의 크기가 유동적이고 삽입과 삭제가 자주 발생하는 경우에 사용됩니다. 예를 들어, 채팅 메시지, 뉴스 피드처럼 데이터가 동적으로 추가되거나 삭제될 가능성이 높은 경우 List를 사용하는 것이 적합합니다.
6. Swift에서의 사용
- Swift에서는 Array라는 타입이 List의 기능도 어느 정도 포함하고 있습니다. Swift의 Array는 크기를 동적으로 변경할 수 있기 때문에, 고정 크기 Array와 유사하지만 동시에 List처럼 동적으로 데이터를 관리할 수도 있습니다.
- 하지만 내부 구현에서는 여전히 Array가 연속된 메모리 공간을 사용하므로, 대량의 삽입 및 삭제가 빈번한 경우 LinkedList를 사용하는 것이 좋을 수 있습니다.
요약
Array는 빠른 데이터 접근이 필요한 고정된 크기의 데이터에 적합하고, List는 크기가 변하고 데이터의 추가/삭제가 빈번한 경우에 적합합니다. iOS 개발에서는 상황에 따라 두 자료구조를 잘 선택하여 사용하는 것이 중요하며, 특히 성능과 메모리 효율을 고려해 적합한 자료구조를 선택하는 것이 좋은 개발 습관입니다.
예제 1: 친구 목록
Array 사용 예제
- 만약 우리가 고정된 친구 목록을 관리하고 있다고 가정해봅시다. 예를 들어, 그룹에 친구 5명만 있고 이 목록은 변하지 않는다고 가정하면, Array를 사용하는 것이 적합합니다.
- Swift에서 Array를 사용해 이 목록을 만들면 다음과 같이 작성할 수 있습니다.
let friendsArray = ["Alice", "Bob", "Charlie", "Dave", "Eve"]
- Array를 사용하면 특정 친구에게 빠르게 접근할 수 있습니다. 예를 들어, 세 번째 친구를 찾으려면 friendsArray[2]로 바로 접근할 수 있습니다.
- 하지만 만약 새로운 친구를 추가하려면 Array의 크기가 고정되어 있기 때문에 새 배열을 만들어야 하는 상황이 발생할 수 있습니다. Swift의 Array는 다행히 동적이어서 append 메서드를 사용할 수 있지만, 내부적으로 메모리를 재할당하고 복사해야 하므로 큰 데이터를 다룰 때는 성능에 영향을 미칠 수 있습니다.
var friendsArray = ["Alice", "Bob", "Charlie", "Dave", "Eve"]
friendsArray.append("Frank") // 새로운 친구 추가
List 사용 예제
- 반대로 친구 목록이 자주 바뀌는 경우, 예를 들어 클래스의 모든 학생을 친구 목록에 추가하는 경우라고 가정해봅시다. 이 경우 매일 새로운 친구가 추가될 수도 있고, 일부 친구가 목록에서 사라질 수도 있습니다.
- 이때는 List를 사용하는 것이 더 효율적입니다. Swift에서는 기본적으로 LinkedList 자료형을 제공하지 않지만, List와 같은 동작을 원한다면 LinkedList 구조를 직접 구현할 수도 있습니다. 예를 들어, 연결 리스트의 동작을 흉내 내기 위해 Node와 LinkedList 클래스를 만들어볼 수 있습니다.
class Node {
var value: String
var next: Node?
init(value: String) {
self.value = value
}
}
class LinkedList {
var head: Node?
func append(_ value: String) {
let newNode = Node(value: value)
if let tailNode = head {
var currentNode = tailNode
while let nextNode = currentNode.next {
currentNode = nextNode
}
currentNode.next = newNode
} else {
head = newNode
}
}
func remove(_ value: String) {
var previousNode: Node?
var currentNode = head
while currentNode != nil {
if currentNode?.value == value {
if previousNode == nil {
head = currentNode?.next
} else {
previousNode?.next = currentNode?.next
}
break
}
previousNode = currentNode
currentNode = currentNode?.next
}
}
}
- 이 코드를 사용하면 학생들을 친구 목록에 쉽게 추가하고 삭제할 수 있습니다.
let friendsList = LinkedList()
friendsList.append("Alice")
friendsList.append("Bob")
friendsList.append("Charlie")
friendsList.remove("Bob") // 중간에 있는 친구 삭제
정리
- Array는 고정된 크기의 데이터에 적합합니다. 데이터 접근 속도가 빠르며, friendsArray[2]와 같이 인덱스를 통해 빠르게 접근할 수 있습니다.
- List는 데이터가 자주 추가되고 삭제되는 경우에 적합합니다. 새로운 친구가 생길 때마다 append 메서드를 통해 빠르게 추가할 수 있으며, 삭제도 리스트의 연결을 끊는 방식으로 쉽게 가능합니다.
🔥 Class Node, Class LinkedList 코드
이 코드는 단일 연결 리스트(Linked List) 자료구조를 구현한 Swift 예제입니다. 연결 리스트는 각각의 노드가 다음 노드의 위치를 참조하여 연결되는 방식으로 데이터를 저장하는 자료구조입니다. 이 코드에서는 Node 클래스와 LinkedList 클래스를 통해 연결 리스트를 구현합니다.
1. Node 클래스
Node 클래스는 연결 리스트의 기본 단위인 노드를 나타냅니다. 각 노드는 두 가지 주요 속성을 가지고 있습니다.
- value: 노드의 값, 여기서는 String 타입으로 데이터를 저장합니다.
- next: 다음 노드를 가리키는 참조입니다. next는 Node? 타입으로 선언되어 있으며, 마지막 노드는 다음 노드가 없으므로 nil을 가리킵니다.
class Node {
var value: String
var next: Node?
init(value: String) {
self.value = value
}
}
예시
Node 인스턴스를 만들 때, 각 노드가 저장할 데이터를 value로 전달해 저장하고, 다음 노드(next)는 nil로 초기화됩니다. 예를 들어, Node(value: "Alice")는 "Alice"라는 값을 가지는 노드를 생성합니다.
🔥 궁금한게 프로퍼티 next의 타입이 Node 잖아? 근데 Node 클래스가 안에 Node 타입을 사용할 수 있는 거야? 아직 생성전 인데?
네, 가능합니다. Swift에서는 클래스의 프로퍼티 타입으로 자기 자신을 참조할 수 있습니다. Swift 컴파일러는 클래스가 정의될 때, 아직 인스턴스화되지 않아도 해당 클래스의 이름을 타입으로 사용하는 것을 허용하기 때문입니다.
Node 클래스가 정의되는 순간 Swift 컴파일러는 Node 타입을 알고 있기 때문에 클래스 내부의 프로퍼티로 Node?를 사용할 수 있습니다. 이는 자기 참조(Self-referencing)라고 하며, 연결 리스트처럼 자기 자신의 인스턴스를 참조하는 구조에서 자주 사용됩니다.
왜 가능한지 더 자세히 설명
클래스의 정의가 컴파일되는 시점에서 Swift는 클래스의 구조를 인식합니다. 이때 Swift는 클래스가 자기 자신을 참조하는 속성을 가질 수 있도록 허용하는데, 이 경우 Node?는 옵셔널 타입이므로 nil로 초기화될 수 있어 인스턴스를 사용하지 않고도 타입을 참조할 수 있습니다.
이러한 접근 방식 덕분에 클래스의 내부에서 같은 클래스 타입을 사용할 수 있으며, 이는 연결 리스트나 트리 구조처럼 자기 자신을 참조해야 하는 자료구조를 만드는 데 매우 유용합니다.
2. LinkedList 클래스
LinkedList 클래스는 연결 리스트 전체를 관리하며, 노드를 추가하거나 제거하는 기능을 포함합니다.
- head: 리스트의 시작 노드를 가리키는 참조입니다. 연결 리스트는 head 노드에서 시작해 다음 노드를 하나씩 따라가며 리스트 전체를 탐색합니다.
append(_:) 메서드
append(_:) 메서드는 리스트의 마지막에 새로운 노드를 추가하는 기능을 합니다. 이 메서드는 아래 단계를 거쳐 노드를 추가합니다.
- Node를 만들고, value 파라미터로 전달된 데이터를 저장하는 새 노드를 만듭니다.
- head가 nil인 경우 (리스트가 비어 있는 경우), 새 노드를 head에 할당해 리스트의 첫 노드로 만듭니다.
- head가 nil이 아닌 경우, 즉 리스트가 비어있지 않으면, 리스트의 마지막 노드를 찾을 때까지 next를 따라갑니다.
- 마지막 노드의 next에 새 노드를 할당해 리스트의 끝에 노드를 추가합니다
func append(_ value: String) {
let newNode = Node(value: value)
if let tailNode = head {
var currentNode = tailNode
while let nextNode = currentNode.next {
currentNode = nextNode
}
currentNode.next = newNode
} else {
head = newNode
}
}
예시
리스트가 비어있는 경우 "Alice" 노드를 추가하면, "Alice"가 head가 됩니다. 이후 "Bob", "Charlie"를 추가하면 리스트는 "Alice" -> "Bob" -> "Charlie" 순으로 연결됩니다.
remove(_:) 메서드
remove(_:) 메서드는 특정 값을 가진 노드를 리스트에서 제거하는 기능을 합니다. 아래 단계를 거쳐 노드를 삭제합니다.
- head부터 시작하여 각 노드의 value와 제거할 값을 비교합니다.
- 일치하는 값이 있는 노드를 찾으면, 해당 노드를 리스트에서 제거합니다.
- 만약 head 노드가 제거할 값과 일치하면, head를 다음 노드로 이동시켜 리스트의 시작점을 변경합니다.
- 일치하는 값이 head 이후에 있는 경우, 이전 노드(previousNode)의 next를 현재 노드의 next로 변경해 현재 노드를 리스트에서 분리합니다
func remove(_ value: String) {
var previousNode: Node?
var currentNode = head
while currentNode != nil {
if currentNode?.value == value {
if previousNode == nil {
head = currentNode?.next
} else {
previousNode?.next = currentNode?.next
}
break
}
previousNode = currentNode
currentNode = currentNode?.next
}
}
예시
예를 들어, 리스트가 "Alice" -> "Bob" -> "Charlie"일 때, "Bob"을 제거하려고 하면 "Alice"의 next를 "Charlie"로 변경합니다. 결과적으로 리스트는 "Alice" -> "Charlie"가 됩니다.
요약
- Node 클래스는 연결 리스트의 각 노드를 정의하며, 값(value)과 다음 노드(next)를 포함합니다.
- LinkedList 클래스는 리스트 전체를 관리하며, append(_:)를 통해 노드를 추가하고, remove(_:)를 통해 특정 값을 가진 노드를 제거할 수 있습니다.
- 이 연결 리스트는 단일 연결 리스트 형태로 구현되어 있으며, head에서 시작해 next를 따라가며 노드를 순차적으로 탐색하는 구조입니다.
🔥 실제로 Swift로 앱을 개발할 때, LinkedList는 Array보다 적합한 특정 상황에서만 사용됩니다. 대부분의 경우 Swift에서는 Array를 더 많이 사용합니다. Array는 메모리 접근 속도가 빠르고 Swift 표준 라이브러리에 강력한 기능들이 많이 포함되어 있기 때문에 일반적으로 더 편리하고 효율적입니다.🔥
'정보 > 레벨 0' 카테고리의 다른 글
Garbage Collection이란? (0) | 2024.10.31 |
---|---|
PNG와 JPG 차이점 (0) | 2024.10.31 |
가상 메모리(Virtual Memory)의 개념과 동작 원리에 대해 설명해주세요. (2) | 2024.10.04 |
암호화와 보안의 기본 개념, iOS 앱 보안을 위한 방안에 대해 설명해주세요. (2) | 2024.10.04 |
동시성 프로그래밍의 개념과 iOS에서의 동시성 처리 방식에 대해 설명해주세요. (5) | 2024.10.02 |