본문 바로가기
Swift

메모리와 Array

by SeoB-P 2022. 1. 12.

Apple개발 문서에서는 Array를 swift로 어떻게 설명했는지 알아보자.

- 애플의 공식 문서에 나와있는 형태로는 frozen Struct로 Array가 구성이 되어있다.

여기서 @frozen이란 이제  라이브러리를 만들때 주로 사용하는 것으로 최적화를 위해 사용하는 것으로 구조체나 열거형에 사용이된다.

열거형의 케이스 또는 구조체의 저장된 인스턴스 프로퍼티를 추가, 제거, 또는 재정렬로 선언을 변경할 수 없다.

(extension으로 연산프로퍼티를 추가하는 것은 가능해보인다.)



 

이번에 중점적으로 볼것은 메모리와 Array!

 

  공식 문서에 따르면 애플의 Array는 내용을 보관하기 위해 특정 양의 메모리를 예약한다.

만약, 예약된 메모리 용량을 초과하기 시작하면 배열은 더 큰 메모리 영역을 할당하고 새저장소에 복사를 하는데 이는 이전 저장소의 배수의 크기로 커진다. 이 전략은 요소 추가가 일정한 시간에 발생하여 많은 추가 작업의 성능을 평균화 한다는 것을 의미한다고 한다.

즉, 재할당으로 인해 성능 비용(메모리 비용)이 있지만 어레이가 커질수록 발생 빈도는 줄어든다.

 

저장해야 하는 요소의 수를 대략적으로 알고 있다면 reserveCapcity(_:)capacitycount 메서드를 이용해서 중간 재할당을 방지하여 더큰 저장소를 할당 하지 않고 배열이 저장할 수 있는 요소의 수를 정할 수 있다. 

 

 - swift의 Array도 마찬가지로 연속적인 메모리 블럭을 가진다.

 만약 요소의 유형이 클래스 또는 @objc 프로토콜의 유형이라면 메모리연속블록 뿐만 아니라 NSArray의 인스턴스가 될수도 있다.

 NSArray의 임의의 하위 클래스는 배열이 될 수 있기 때문에 이 경우 표현이나 효율성에 대한 보장은 없다.

var numbers = [1, 2, 3, 4, 5]
var numbersCopy = numbers
numbers[0] = 100
print(numbers)
// Prints "[100, 2, 3, 4, 5]"
print(numbersCopy)
// Prints "[1, 2, 3, 4, 5]"

//값이 복사가 되어 원본의 변형이 사본에는 영향을 안끼침을 확인가능.

 배열의 요소가 클래스의 인스턴스인경우 다르게 나타날 수 있다.

왜냐하면 배열에 저장된 값은 배열외부에 있는 개체에 대한 참조이기 때문이다. 한 배열의 개체에 대한 참조를 변경하면 해당 배열에만 새 개체에 대한 참조가 있다. 그러나 두 배열에 동일한 개체에 대한 참조가 있으면 두 배열에서 해당 객체의의 속성에 대한 변경을 관찰 할 수 있다.

말이 어려운데 예제를 보면 이해가 잘 됬다.

// An integer type with reference semantics
class IntegerReference {
    var value = 10
}
var firstIntegers = [IntegerReference(), IntegerReference()]
var secondIntegers = firstIntegers

// Modifications to an instance are visible from either array
firstIntegers[0].value = 100
print(secondIntegers[0].value)
// Prints "100"

// Replacements, additions, and removals are still visible
// only in the modified array 배열만 바꿔보기.
firstIntegers[0] = IntegerReference() //새로운 IntegerReference를 firstIntegers의 첫번째 요소로 넣는다.
print(firstIntegers[0].value) //당연히 새로운 값이 들어왔으므로 default값인 10
// Prints "10"    
print(secondIntegers[0].value) //원래 가지고있던 참조인 100
// Prints "100"  //값이 다르다.

첫번째 예시의 경우 배열의 요소가 Struct이기 때문에 배열의 사본이 바뀌든 말든 마이웨이였다.

하지만 배열의 요소가 만약 Class라면.. 참조를 하기 때문에 firstIntegers의 첫번째 값을 변경 했을때 사본도 똑같이 변함을 알 수 있다.

그렇다면 배열의 사본이(secondIntegers)가 현재 원본(firstIntergers)의 첫번쨰 요소에 대한 참조를 하고 있는 와중에 

원본의 첫번째 요소를 변경하면 어떻게 될까??

결론은.. 원본은 당연히 배열안의 값을 바뀌었으므로 값이 바뀌고 예전 것을 참조하던 사본은 바뀌지 않는다.

왜냐하면 배열에 저장된 값은 배열외부에 있는 개체에 대한 참조니까..

 

그림으로 보면 조금더 이해가 쉽다.

 왼쪽 두개의 사각형 하나하나를 배열 그리고 작은 사각형으로 두개로 나눈것을 하나의 클래스라고 하고

기다란 직사각형을 메모리, 그안의 동그라미를 메모리 주소라고 하자.

위의 식대로라면 클래스들은 같은 메모리 주소를 참조 한다.

하지만 배열안에 원래 있던 값 대신 새로운 클래스를 넣게 되면 빨간 원과 같은 새로운 메모리 주소를 참조하게 된다.

이때, 원래 배열이 바뀌었다고 해서 배열의 사본의 메모리주소가 달라지지 않기 때문에 위의 코드예제와 같은 결과가 나온다.

 

 

 

 

표준 라이브러리에 있는 모든 가변사이즈의 collection들은 최적화된 복사 쓰기를 사용한다.

 Array의 여러 사본은 사본 중 하나를 수정할 때까지 동일한 저장소를 공유한다는 의미이다. 

위와같은 경우, 저장소를 고유하게 소유한 복사본으로 교체한 다음 제자리에서 수정된다.

복사량을 줄일 수 있는 최적화가 적용되는 경우가 있다.

 

배열이 복사본을 가지고 있는 경우 배열의 첫번째 수정작업에서 복사 비용이 발생함. 하지만 복사본이 없는 유일한 배열은 변경 작업시 복사비용이 발생하지 않고 그 배열의 저장소 제자리에서 변경작업을 이어갈 수 있다.

밑의 예에서는 numbers라는 배열은 같은 저장소를 공유하는 두개의 카피본과 생성이 되었다.

만약 원본의 배열이 수정이 된다면, 이 배열은 수정되기 전에 이 배열만의 고유한 카피 저장소를 만든다

그리고 numbers의 두개의 카피본은 원본 배열의 저장소를 계속해서 공유하고 있고 원본배열의 수정은 계속해서 일어난다.

만약, 원본만 있고 카피본이 없다면 원본배열의 수정이 일어나도 배열은 카피 저장소를 만들지 않을것이다.

var numbers = [1, 2, 3, 4, 5]
var firstCopy = numbers
var secondCopy = numbers

// The storage for 'numbers' is copied here
numbers[0] = 100
numbers[1] = 200
numbers[2] = 300
// 'numbers' is [100, 200, 300, 4, 5]
// 'firstCopy' and 'secondCopy' are [1, 2, 3, 4, 5]

https://developer.apple.com/documentation/swift/array

 

Apple Developer Documentation

 

developer.apple.com

https://bbiguduk.gitbook.io/swift/language-reference/attributes

 

속성 (Attributes) - Swift

Objective-C 로 표현될 수 있는 모든 선언에 이 속성을 적용합니다. 예를 들어 중첩되지 않은 클래스, 프로토콜, 제너릭이 아닌 열거형 (정수 원시값 타입으로 제한), 클래스의 프로퍼티, 그리고 메

bbiguduk.gitbook.io

 

댓글