본문 바로가기

Swift/기초 문법

컬렉션 타입 1편 - 배열

https://bbiguduk.gitbook.io/swift/language-guide-1/collection-types#accessing-and-modifying-an-array

 

콜렉션 타입 (Collection Types) | Swift

배열, 집합, 그리고 딕셔너리를 사용하여 데이터를 구성합니다. Swift는 콜렉션의 값을 저장하기 위한 배열 (array), 집합 (set), 딕셔너리 (dictionary)와 같은 3개의 원시적인 콜렉션 타입 (collection types)

bbiguduk.gitbook.io

 

  • Swift는 콜렉션의 값을 저장하기 위한 배열 (array), 집합 (set), 딕셔너리 (dictionary)와 같은 3개의 원시적인 콜렉션 타입 (collection types) 을 제공합니다.

  • Swift에 배열, 집합, 그리고 딕셔너리는 저장할 수 있는 값의 타입키에 대해 항상 명확합니다.
  • 이것은 실수로 콜렉션에 잘못된 타입을 추가할 수 없다는 의미입니다. 또한 콜렉션에서 검색할 값에 대해 타입이 명확하다는 것을 의미합니다

 

콜렉션의 가변성 (Mutablility of Collections)

  • 배열, 집합, 또는 딕셔너리를 생성하고 변수에 할당하면 생성된 콜렉션은 변경 가능 (mutable) 합니다.
  • 이것은 콜렉션이 생성된 후에 콜렉션의 아이템을 추가, 삭제, 또는 변경 할 수 있다는 뜻입니다.
  • 배열, 집합, 또는 딕셔너리를 상수에 할당하면 이 콜렉션은 불가변성이며 크기와 콘텐츠를 변경할 수 없습니다.

 

배열 (Arrays)

배열 (array) 은 순서대로 같은 타입의 값을 저장합니다. 같은 값은 배열에 다른 순서로 존재할 수 있습니다.

 

배열 타입 구문 (Array Type Shorthand Syntax)

  • Swift 배열의 타입은 Element 는 저장할 배열 값의 타입을 나타내는 Array<Element> 로 작성합니다.
  • 또한 짧게 [Element] 로 작성할 수도 있습니다.
  • 두 형식이 기능적으로 동일하지만 짧은 표현이 선호되며 배열 타입을 참조할 때 이 가이드 전체에서 사용됩니다.

 

빈 배열 생성 (Creating an Empty Array)

  • 초기화 구문을 사용하여 타입을 포함한 빈 배열을 생성할 수 있습니다: 
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)
// someInts now contains 1 value of type Int
someInts = []
// someInts is now an empty array, but is still of type [Int]

 

기본값 배열 생성 (Creating an Array with a Default Value)

  • 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]

 

배열을 더해 생성 (Creating Array by Adding Two Arrays Together)

  • 동등한 타입의 2개의 존재하는 배열을 덧셈 연산자 (+)를 통해 합쳐서 새로운 배열을 생성할 수 있습니다.
  • 새로운 배열의 타입은 합쳐진 2개의 배열의 타입으로 부터 추론됩니다: 
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]

 

 

배열 리터럴로 생성 (Creating an Array with an Array Literal)

  • 배열 콜렉션으로 하나 이상의 값을 작성하여 배열 리터럴 (array literal) 로 배열을 생성할 수 있습니다.
  • 배열 리터럴은 값을 리스트로 작성하고 콤마로 구분하며 대괄호로 둘러싸서 작성합니다: 
var shoppingList: [String] = ["Eggs", "Milk"]
// shoppingList has been initialized with two initial items
  • shoppingList 변수는 [String] 으로 쓰고 "문자열 값의 배열"로 선언됩니다.
  • 이 배열은 String 의 값 타입을 가지고 있기 때문에 String 값만 저장이 가능합니다.
  • 여기서 shoppingList 배열은 배열 리터럴 안에 쓰여진 2개의 String 값 ("Eggs" 와 "Milk")으로 초기화 되었습니다.
  • 이 경우 배열 리터럴은 2개의 String 값을 포함합니다.
  • 이것은 shoppingList 변수의 선언 타입 (String 값만 포함될 수 있는 배열)과 일치하므로 2개의 초기 항목으로 shoppingList 를 초기화하는 방법으로 배열 리터럴의 할당이 허용됩니다.
  • Swift의 타입 추론에 덕분에 같은 타입의 값을 포함하는 배열 리터럴로 초기화하면 배열의 타입을 명시할 필요가 없습니다.
  • shoppingList 의 초기화는 아래와 같이 간단하게 작성될 수 있습니다: 
var shoppingList = ["Eggs", "Milk"]

 

배열 접근과 수정 (Accessing and Modifying an Array)

  • 메서드와 프로퍼티 또는 서브 스크립트 구문을 사용하여 배열에 접근 및 수정이 가능합니다.
  • 배열에 아이템 갯수를 알려면 읽기 전용 count 프로퍼티로 확인하면 됩니다: 
print("The shopping list contains \(shoppingList.count) items.")
// Prints "The shopping list contains 2 items."
  • 부울 isEmpty 프로퍼티를 사용하여 배열의 count 프로퍼티 값이 0 인지 아닌지 빠르게 판단할 수 있습니다: 
if shoppingList.isEmpty {
    print("The shopping list is empty.")
} else {
    print("The shopping list is not empty.")
}
// Prints "The shopping list is not 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
  • 서브 스크립트 구문 (subscript syntax) 을 사용하여 배열의 값을 가져올 수 있습니다.
  • 배열의 이름 뒤에 바로 대괄호를 붙이고 가져올 값의 인덱스를 넣어 해당 값을 가져올 수 있습니다:
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" 으로 배열 끝에 추가하려고 하면 런타임 에러가 발생합니다.
  • 바꾸려면shoppingList[shoppingList.count - 1] = "Salt" 이렇게 해야 배열 마지막 값을 변경할 수 있다.
  • 변경할 값들이 변경할 범위와 다른 길이를 가지고 있더라도 서브 스크립트 구문으로 범위 안에 값을 한번에 변경할 수 있습니다.
  • 아래 예제는 "Chocolate Spread", "Cheese" 를 "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:) 메서드를 호출하면 인덱스가 0 인 쇼핑 리스트에 가장 맨 앞에 "Maple Syrup" 의 값을 가진 새로운 아이템을 추가합니다.
  • 비슷하게 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
  • 아이템이 삭제되면 배열의 공간도 삭제되므로 인덱스 0 인 값은 "Six eggs" 와 같습니다:
firstItem = shoppingList[0]
// firstItem is now equal to "Six eggs"
  • 배열의 마지막 아이템을 삭제하고 싶다면 배열의 count 프로퍼티의 사용을 피하기 위해 remove(at:) 메서드 보다 removeLast() 메서드를 사용하십시오.
  • 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

 

배열 반복 (Iterating Over an Array)

  • 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

 

 

중첩 배열 (Nested Arrays)

배열은 다른 배열을 요소로 가질 수 있습니다. 이를 중첩 배열이라고 하며, 각 요소에 접근하려면 여러 단계의 인덱싱이 필요합니다.

let nestedArray = [[1, 2], [3, 4], [5, 6]]
print(nestedArray[1][0]) // 3
for (index, value) in market.enumerated() {
    print("Items: \(value)")
    for (index, value) in market[index].enumerated() {
        print("Item \(index + 1): \(value)")
    }
}

// Items: ["apple", "banana", "melon"]
// Item 1: apple
// Item 2: banana
// Item 3: melon
// Items: ["사과", "바나나", "멜론"]
// Item 1: 사과
// Item 2: 바나나
// Item 3: 멜론

 

배열의 성능

배열은 Swift에서 값 타입(Value Type)입니다.

하지만 배열이 복사될 때 실제로는 값이 복사되지 않고 참조를 공유하며, 수정이 발생하면 복사가 이루어지는 Copy-On-Write(COW) 방식으로 작동합니다. 이 성능 최적화는 큰 배열을 다룰 때 매우 중요합니다.
이를 설명하며 "복사를 방지하기 위해 값이 필요하지 않다면 참조를 활용한다"는 내용을 추가할 수 있습니다.

 

Copy-On-Write의 동작 원리

할당 시:

var marketA: [String] = ["사과", "바나나"]
var marketB = marketA

 

  • 이 시점에서 marketB는 marketA와 동일한 참조를 공유합니다.
  • 배열의 데이터는 복사되지 않고, 둘 다 동일한 데이터에 대한 공유 참조를 가집니다.

 

수정 전:

  • marketB를 읽기만 한다면 두 변수는 여전히 같은 데이터를 참조하고 있습니다. 
print(marketA) // ["사과", "바나나"]
print(marketB) // ["사과", "바나나"]

 

 

수정 시:

  • 수정이 발생할 때 Swift는 실제로 데이터를 복사하여 marketB에 독립적인 복사본을 만듭니다.
  • 따라서 marketA와 marketB는 이후로 서로 다른 데이터를 갖게 됩니다.
marketB.append("딸기")
print(marketA) // ["사과", "바나나"] (변경되지 않음)
print(marketB) // ["사과", "바나나", "딸기"] (독립적인 복사본에 변경)

 

왜 Copy-On-Write 방식을 사용하는가?

  1. 효율성:
    • 배열을 복사하지 않고 참조를 공유하기 때문에 메모리와 성능이 최적화됩니다.
    • 불필요한 복사를 피할 수 있습니다.
  2. 안전성:
    • 배열이 수정되면 독립적인 복사를 생성하여, 값 타입의 특성(독립적인 값을 가짐)을 유지합니다.

정리

  • 복사 시점: marketB는 marketA와 참조를 공유합니다. 데이터를 수정하기 전까지 실제 데이터는 복사되지 않습니다.
  • 수정 시점: 배열이 수정되면 독립적인 데이터 복사가 발생합니다(COW).

'Swift > 기초 문법' 카테고리의 다른 글