Array는 Collection Type에 속한다.
리터럴을 []로 둘러싸는 것으로 쉽게 배열을 만들 수 있고 둘러쌓인 요소를 통해 타입 추론이 가능하다.
배열은 정수, 문자열, 클래스 등 모든 종류의 요소를 저장 가능하다.
private let intArray: [Int] = [1, 2, 3, 4, 5] //리터럴을 괄호([])로 둘러싸는 것으로 배열을 나타낼 수 있다.
private let stringArray = ["type", "annotation"]
//배열 속 요소들을 통한 타입 추론이 이루어진다.
private var emptyDoubleArray: [Double] = []
private var emptyStringArray: Array<String> = Array()
Array의 모든 요소를 사용해야 하는 작업을 수행할 경우 for-in 구문을 사용하면 쉽게 처리 가능하며 isEmpty, count, first, last 등의 프로퍼티를 통해 배열의 요소에 대한 작업이 가능하다. 또한 첨자를 통해 배열의 요소에 접근할 수도 있으며 모든 요소는 항상 0에서부터 시작한다. -1과 같은 음수나 배열의 크기를 넘어서는 위치를 참조하려고 하면 에러가 발생한다.
if !intArray.isEmpty { //isEmpty를 통해 bool값을 받아 조건 판단
for i in intArray{ for-in // for-in 구문을 통해 모든 요소에 대해 print 실행
print(i)
}
}
print(intArray.count) // 5
print(intArray.first) // 1
print(intArray.last) // 5 >> 요소가 set 되어있지 않을 수 있어서 옵셔널 값을 가진다.
Array에 새로운 요소를 추가하거나 삭제하기 위한 메서드의 예시는 다음과 같다.
var myPets: [String] = ["dog", "cat", "gold fish"]
myPets.append("lion") // 배열의 마지막에 lion 추가
myPets.insert("bird", at: 0) // 배열의 인덱스 0에 bird를 추가하고 기존 요소들을 뒤로 이동
myPets.insert(contentsOf: ["ant", "spider"], at: 1) // 배열의 인덱스 1부터 ant, spider를 추가
myPets.remove(at: 0) // 인덱스 0에 위치한 요소 삭제
myPets.removeSubrange(0...2) //인덱스 0...2에 대한 요소 삭제
myPets.removeFirst() // 배열의 첫 번째 요소 삭제
if let i = myPets.firstIndex(of: "lion") { // if let을 통해 옵셔널을 벗기고 lion이 존재하는 경우에 lion을 ferret으로 변경
myPets[i] = "ferret"
}
모든 배열은 내용 저장을 위해 특정한 메모리를 할당 받는다. .append 등의 요소 추가에 대한 대비이다. 이후 예약된 메모리를 초과하게 될 경우 더 큰 메모리 영역을 할당 받으며 이때 할당 받는 영역은 기존 영역의 2배이다. 이러한 기하급수적 증가를 통해 배열의 크기가 커질 수록 재할당 되는 빈도를 낮출 수 있다.
미리 배열에 들어갈 요소를 파악할 수 있는 경우 .reserveCapacity(_:)를 통해 미리 정해줌으로써 재할당을 방지할 수 있으나 동적으로 증가하는 배열의 경우에는 오히려 선형적으로 성능이 감소할 수 있다.
배열을 복사하게 되면 각 배열은 독립적으로 존재한다. 따라서 원본 배열 A의 값을 변경하여도 복사된 배열 B에선 값이 변경되지 않는다. 그러나 배열의 요소가 클래스의 인스턴스라면 배열에 저장된 값은 외부에 있는 클래스의 객체에 대한 참조이다. 따라서 해당 배열을 복사한 뒤 객체를 수정한다면 복사 된 배열에서도 수정된 사항을 관찰할 수 있다. 만약 복사된 배열 중 하나의 배열에서 참조하는 객체를 변경할 경우엔 변경 한 배열만 변경된 객체를 참조한다. 말보다는 직접 코드를 보는 것이 훨씬 이해가 쉬웠다.
class IntReferenceClass {
var num = 10
}
var firstClassArray = [IntReferenceClass(), IntReferenceClass()] //firstClassArray에 IntReferenceClass 객체를 [0],[1]에 참조한다.
var secondClassArray = firstClassArray // firstClassArray가 참조하는 객체를 secondClassArray에서 동일하게 참조한다.
var thirdClassArray = [IntReferenceClass(), IntReferenceClass()]//thirdClassArray에 IntReferenceClass 객체를 [0],[1]에 참조한다.
firstClassArray[0].num = 100 //firstClassArray가 참조하고 있는 객체의 값을 수정한다.
print(firstClassArray[0].num) //"100"이 출력
print(secondClassArray[0].num) //"100"이 출력 -> 같은 객체를 참조하고 있기 때문에 수정이 관찰 된다.
print(thirdClassArray[0].num) //"10"이 출력
firstClassArray[0] = IntReferenceClass() //새로운 객체를 참조한다. 따라서 더이상 secondClassArray와 같은 객체를 참조하지 않는다.
firstClassArray[0].num = 200 //200으로 수정
print(firstClassArray[0].num) //"200" 출력
print(secondClassArray[0].num) //"100" 출력
//-----------------------------------------
struct IntReferenceStruct {
var num = 10
}
var firstStructArray = [IntReferenceStruct(), IntReferenceStruct()]
var secondStructArray = firstStructArray
firstStructArray[0].num = 200
print(firstStructArray[0].num) // 200이 출력
print(secondStructArray[0].num) // 10이 출력
//struct와 class에는 차이가 있다는 걸 보기 위해 시도해본 코드
표준 라이브러리의 모든 가변 크기 컬렉션과 같은 Array는 쓰기 시 복사 최적화를 사용한다. (이전에 cs를 가볍게 배웠을 때 들었던 부분인데 문법 공부를 하면서 이렇게 바로 볼 수 있을 줄은 몰랐다.)
복사본이 생길 경우 저장소를 공유하며 첫 번째 변경 작업이 발생할 때 복사본이 생성되고 생성된 복사본의 저장소에서 수정이 이루어진다.
var originalArray: [Int] = [1, 2, 3, 4, 5]
var firstCopy = originalArray
var secondCopy = originalArray
originalArray[0] = 100 //새로운 저장소에 복사본이 생기고 100으로 변경
firstCopy //
secondCopy // firstCopy와 여전히 같은 원본 저장소를 공유하고 있음