Swift 배열 할당이 일치하지 않는 이유가 있습니까 (참조 또는 딥 카피)?


216

나는 문서를 읽고 있으며 언어의 디자인 결정 중 일부에서 끊임없이 머리를 흔들고 있습니다. 그러나 실제로 당황한 것은 배열을 처리하는 방법입니다.

나는 놀이터로 달려 가서 이것들을 시험해 보았다. 당신도 그들을 시도 할 수 있습니다. 첫 번째 예 :

var a = [1, 2, 3]
var b = a
a[1] = 42
a
b

여기 ab모두 [1, 42, 3]내가 받아 들일 수있는가. 배열이 참조됩니다-OK!

이제이 예제를보십시오 :

var c = [1, 2, 3]
var d = c
c.append(42)
c
d

c입니다 [1, 2, 3, 42]하지만 d입니다 [1, 2, 3]. 즉, d마지막 예제에서 변경 사항을 보았지만이 예제에서는 변경되지 않았습니다. 설명서에 따르면 길이가 변경 되었기 때문입니다.

자, 이건 어때?

var e = [1, 2, 3]
var f = e
e[0..2] = [4, 5]
e
f

e 이다 [4, 5, 3] 멋지다. 다중 인덱스 대체를 사용하는 것이 좋지만 f길이가 변경되지 않은 경우에도 STILL에서 변경 사항을 볼 수 없습니다.

요약하면 배열에 대한 공통 참조는 하나의 요소를 변경하면 변경 사항을 볼 수 있지만 여러 요소를 변경하거나 항목을 추가하면 사본이 만들어집니다.

이것은 나에게 매우 나쁜 디자인처럼 보입니다. 나는 이것을 생각하는 것이 맞습니까? 왜 배열이 이런 식으로 작동해야하는지 알 수없는 이유가 있습니까?

편집 : 배열이 변경되었으며 이제 값 의미가 있습니다. 훨씬 더 제정신!


95
기록을 위해, 나는이 질문이 종결되어야한다고 생각하지 않습니다. 스위프트는 새로운 언어이므로 우리 모두가 배우면서 잠시 동안 이와 같은 질문이있을 것입니다. 나는이 질문이 매우 흥미롭고 누군가가 방어에 대한 강력한 사건을 갖기를 희망한다.
Joel Berger

4
@Joel Fine, 프로그래머에게 물어보십시오. Stack Overflow는 특정 미확정 프로그래밍 문제를위한 것입니다.
bjb568

21
@ bjb568 : 그래도 의견이 아닙니다. 이 질문은 사실로 대답 할 수 있어야합니다. 일부 Swift 개발자가 와서 "X, Y 및 Z에 대해 그렇게했다"고 대답하면 바로 그 사실입니다. X, Y 및 Z에 동의하지 않을 수도 있지만 X, Y 및 Z에 대한 결정이 내려진 경우 이는 언어 디자인의 역사적 사실 일뿐입니다. std::shared_ptr비 원자 버전이없는 이유를 물었을 때와 마찬가지로 의견이 아닌 사실에 근거한 답변이있었습니다 (사실은위원회가 그것을 고려했지만 여러 가지 이유로 그것을 원하지 않았다는 것입니다).
Cornstalks

7
@JasonMArcher : 마지막 단락 만 의견에 근거합니다 (예, 아마도 제거해야합니다). 질문의 실제 제목 (실제 질문 자체로 취함)은 사실로 대답 할 수 있습니다. 이 배열은 그들이 일하는 방식으로 작동하도록 설계되었습니다 이유는.
Cornstalks 2018 년

7
예, API-Beast가 말했듯이 이것을 "반복 사본 언어 디자인"이라고합니다.
R. Martinho Fernandes

답변:


109

참고 배열 의미 및 구문 엑스 코드 베타 3 버전 변경 하였다 ( 블로그 게시물 문제는 더 이상 적용되지 않도록). 다음 답변은 베타 2에 적용되었습니다.


성능상의 이유입니다. 기본적으로 배열은 가능한 한 배열을 복사하지 않으려 고합니다 (및 "C와 유사한 성능"). 언어 을 인용하려면 :

배열의 경우 배열 길이를 수정할 수있는 작업을 수행 할 때만 복사가 수행됩니다. 여기에는 항목을 추가, 삽입 또는 제거하거나 범위가 지정된 첨자를 사용하여 배열의 항목 범위를 대체하는 것이 포함됩니다.

나는 이것이 약간 혼란 스럽지만 적어도 그것이 어떻게 작동하는지에 대한 명확하고 간단한 설명이 있음에 동의합니다.

이 섹션에는 어레이가 고유하게 참조되는지 확인하는 방법, 어레이를 강제 복사하는 방법 및 두 어레이가 스토리지를 공유하는지 여부를 확인하는 방법에 대한 정보도 포함되어 있습니다.


61
디자인에서 BIG 적기를 해제하고 복사한다는 사실을 알게되었습니다.
Cthutu

9
맞습니다. 엔지니어는 언어 설계의 경우 이것이 바람직하지 않으며 향후 Swift 업데이트에서 "수정"하기를 희망한다고 설명했습니다. 레이더로 투표하십시오.
Erik Kerber

2
Linux 하위 프로세스 메모리 관리의 COW (Copy-On-Write)와 같은 것입니다. 아마도 우리는 그것을 복사 길이 변경 (COLA)이라고 부를 수 있습니다. 나는 이것을 긍정적 인 디자인으로 본다.
justhalf

3
@justhalf 나는 혼란스러워하는 초보자들이 SO에 와서 왜 그들의 배열이 공유되지 않았는지 (더 명확하지 않은) 이유를 물을 수 있습니다.
John Dvorak

11
@justhalf : COW는 어쨌든 현대 세계에서 비관적이며, 둘째, COW는 구현 전용 기술이며,이 COLA는 완전히 무작위 공유 및 공유 해제로 이어집니다.
강아지

25

스위프트 언어의 공식 문서에서 :

첨자 구문으로 단일 값을 설정하면 배열의 길이를 변경할 가능성이 없으므로 첨자 구문으로 새 값을 설정하면 배열이 복사되지 않습니다. 그러나 배열에 새 항목을 추가 하면 배열의 길이를 수정합니다 . 그러면 새 값을 추가 할 때 Swift가 배열새 사본 을 작성하라는 프롬프트를 표시 합니다. 따라서 a는 배열의 독립적 인 독립 복사본입니다 .....

이 문서에서 어레이 할당 및 복사 동작 섹션 전체를 읽으십시오 . 당신은 발견 할 것이다 당신이 할 때 배열의 항목의 범위를 교체 한 후 배열이 모든 항목에 자신의 복사본을합니다.


4
감사. 나는 그 질문을 모호하게 언급했다. 그러나 아래 첨자 범위를 변경해도 길이가 변경되지 않고 여전히 복사되는 예를 보여주었습니다. 따라서 사본을 원하지 않으면 한 번에 한 요소 씩 변경해야합니다.
Cthutu

21

Xcode 6 베타 3에서 동작이 변경되었습니다. 배열은 더 이상 참조 형식이 아니며 쓰기시 복사 메커니즘을 갖습니다. 즉, 하나 또는 다른 변수에서 배열의 내용을 변경하자마자 배열이 복사되고 하나의 사본이 변경됩니다.


이전 답변 :

다른 사람들이 지적했듯이 Swift는 한 번 에 단일 인덱스의 값을 변경할 때를 포함하여 가능한 경우 배열 복사 하려고합니다 .

배열 변수 (!)가 고유한지 확인하려면 (예 : 다른 변수와 공유하지 않는 경우) unshare메소드를 호출 할 수 있습니다 . 이미 하나의 참조가없는 경우를 제외하고 배열을 복사합니다. 물론 copy메소드를 호출 할 수도 있습니다.이 메소드는 항상 사본을 작성하지만 다른 변수가 동일한 배열을 보유하지 않도록하려면 공유 해제 를 선호합니다.

var a = [1, 2, 3]
var b = a
b.unshare()
a[1] = 42
a               // [1, 42, 3]
b               // [1, 2, 3]

흠, 그 unshare()방법은 정의되어 있지 않습니다.
Hlung

1
@Hlung 베타 3에서 제거되었으며 답변을 업데이트했습니다.
Pascal

12

동작은 Array.Resize.NET 의 방법 과 매우 유사합니다 . 무슨 일이 일어나고 있는지 이해하려면 역사를 살펴 보는 것이 도움이 될 수 있습니다.. C, C ++, Java, C # 및 Swift 토큰 .

C에서 구조는 변수의 집합에 지나지 않습니다. .구조 유형의 변수 에을 적용하면 구조 내에 저장된 변수에 액세스합니다. 객체에 대한 포인터 는 변수 집계를 보유 하지 않지만 식별 합니다. 구조를 식별하는 포인터가 있으면-> 연산자를 사용하여 포인터로 식별 된 구조 내에 저장된 변수에 액세스 할 수 있습니다.

C ++에서 구조와 클래스는 변수를 집계 할뿐만 아니라 코드를 첨부 할 수도 있습니다. .변수를 사용 하여 메소드를 호출하면 해당 메소드가 변수 자체의 내용에 따라 작동하도록 요청 합니다 . ->객체를 식별하는 변수를 사용 하면 해당 메소드가 객체에 작용하도록 요구합니다. 식별 변수에 의해.

Java에서 모든 사용자 정의 변수 유형은 단순히 객체를 식별하고 변수에 대해 메소드를 호출하면 변수로 식별 된 객체를 메소드에 알려줍니다. 변수는 복합 데이터 유형을 직접 보유 할 수 없으며 메소드가 호출 된 변수에 액세스 할 수있는 방법도 없습니다. 이러한 제한은 의미 상 제한적이지만 런타임을 크게 단순화하고 바이트 코드 유효성 검사를 용이하게합니다. 이러한 단순화는 시장이 그러한 문제에 민감한 시점에 Java의 리소스 오버 헤드를 줄임으로써 시장에서 관심을 끌 수 있도록 돕습니다. 그들은 또한 토큰과 같은 토큰이 필요하지 않다는 것을 의미했습니다.. C 또는 C ++에서 사용되는 . Java는 ->C 및 C ++과 같은 방식으로 사용할 수 있었지만 제작자는 단일 문자를 사용하기로 선택했습니다.. 다른 목적으로는 필요하지 않았기 때문입니다.

C # 및 기타 .NET 언어에서 변수는 개체를 식별하거나 복합 데이터 형식을 직접 보유 할 수 있습니다. 복합 데이터 유형의 변수에 사용될 때 변수의 내용.작용 합니다. 참조 유형의 변수에 사용될 때 식별 된 객체에 작용.그것에 의해. 어떤 종류의 연산들에있어서, 시맨틱 구별은 특별히 중요하지 않지만, 다른 것들에게는 중요하다. 가장 문제가되는 상황은 호출 된 변수를 수정하는 복합 데이터 유형의 메소드가 읽기 전용 변수에서 호출되는 상황입니다. 읽기 전용 값 또는 변수에서 메소드를 호출하려고 시도하는 경우, 컴파일러는 일반적으로 변수를 복사하고 메소드가 해당 조치를 취하게하고 변수를 버립니다. 이것은 일반적으로 변수를 읽는 방법으로는 안전하지만 변수에 쓰는 방법으로는 안전하지 않습니다. 불행히도 .does는 아직 어떤 방법으로 이러한 대체 방법을 안전하게 사용할 수 있고 어떤 방법을 사용할 수 없는지를 나타내는 수단이 없습니다.

Swift에서 집계의 메소드는 호출 된 변수를 수정할지 여부를 명시 적으로 표시 할 수 있으며 컴파일러는 변수의 임시 사본을 변경하지 않고 읽기 전용 변수에 대해 변경 메소드를 사용하지 못하게합니다. 버린다). 이러한 차이점으로 인해 .토큰을 사용하여 호출 된 변수를 수정하는 메소드를 호출하는 것이 Swift에서 .NET보다 훨씬 안전합니다. 불행히도, .변수에 의해 식별되는 외부 객체에 작용 하는 것과 동일한 토큰이 그 목적으로 사용 된다는 사실 은 혼란의 가능성이 남아 있음을 의미합니다.

타임머신이 있고 C # 및 / 또는 Swift의 생성으로 돌아 가면 언어 가 C ++ 사용에 훨씬 가까운 방식으로 언어 .->토큰을 사용하게하여 이러한 문제를 둘러싼 혼란을 소급 적으로 피할 수 있습니다. 모두 단위 및 참조 유형의 방법은 사용할 수 .에 따라 행동하는 변수 가 호출 된시와 ->에 따라 행동하는 (복합) 또는 (참조 유형)하여 확인 된 것. 그러나 어느 언어도 그렇게 설계되지 않았습니다.

C #에서 호출 된 변수를 수정하는 메소드의 일반적인 방법은 변수를 ref매개 변수로 메소드 에 전달 하는 것입니다. 따라서 20 개의 요소로 구성된 배열을 식별 Array.Resize(ref someArray, 23);할 때 호출 하면 원래 배열에 영향을주지 않으면 서 23 개의 요소로 구성된 새로운 배열을 식별하게됩니다. 이 메소드를 사용하면 메소드가 호출 된 변수를 수정해야합니다. 많은 경우 정적 메소드를 사용하지 않고 변수를 수정할 수있는 것이 유리합니다. 구문 을 사용하여 의미 스위프트 주소 . 단점은 변수에 어떤 방법이 작용하고 값에 어떤 방법이 작용하는지 명확하지 않다는 것입니다.someArraysomeArrayref.


5

나에게 이것은 상수를 변수로 대체하면 더 합리적입니다.

a[i] = 42            // (1)
e[i..j] = [4, 5]     // (2)

첫 번째 줄은 크기를 변경할 필요가 없습니다 a. 특히 메모리 할당이 필요하지 않습니다. 의 값에 관계없이이 i작업은 간단한 작업입니다. 후드 아래에서 a포인터라고 생각하면 상수 포인터가 될 수 있습니다.

두 번째 줄은 훨씬 더 복잡 할 수 있습니다. i및 의 값에 따라 j메모리 관리를 수행해야 할 수도 있습니다. 이것이 e배열의 내용을 가리키는 포인터라고 생각하면 더 이상 포인터가 상수 포인터라고 가정 할 수 없습니다. 새 메모리 블록을 할당하고 이전 메모리 블록에서 새 메모리 블록으로 데이터를 복사 한 다음 포인터를 변경해야 할 수도 있습니다.

언어 설계자들은 가능한 한 (1) 경량을 유지하려고 시도한 것 같습니다. (2) 어쨌든 복사가 필요할 수 있기 때문에 항상 복사하는 것처럼 작동하는 솔루션에 의지했습니다.

이 방법은 복잡하지만 "(2) i 및 j가 컴파일 타임 상수이고 컴파일러가 e의 크기가 흐르지 않는다고 유추 할 수있는 경우와 같은 특수한 경우에는 더 복잡하지 않게되어 기쁩니다. 변경하지 않으면 복사하지 않습니다 " .


마지막으로 Swift 언어의 디자인 원칙에 대한 이해를 바탕으로 일반적인 규칙은 다음과 같습니다.

  • 상수 사용 (let기본적으로 항상 )를 하면 큰 놀라움이 없습니다.
  • 변수 ( var)는 반드시 필요한 경우에만 사용 하고, 놀랍게도 여기에주의하십시오 (여기서는 : 일부 상황에서는 배열의 이상한 암시 적 사본).

5

내가 찾은 것은 : 배열에 길이가 변경 될 가능성이있는 경우에만 배열이 참조 된 사본의 변경 가능한 사본이 됩니다 . 마지막 예제에서 f[0..2]많은 색인을 생성하면 작업의 길이가 변경 될 수 있으므로 중복이 허용되지 않을 수 있으므로 복사됩니다.

var e = [1, 2, 3]
var f = e
e[0..2] = [4, 5]
e // 4,5,3
f // 1,2,3


var e1 = [1, 2, 3]
var f1 = e1

e1[0] = 4
e1[1] = 5

e1 //  - 4,5,3
f1 // - 4,5,3

8
"길이 변경으로 처리"나는이 길이 IFF에 카피 한 것입니다 변경하지만, 위의 인용와 함께, 나는 이것이 정말 걱정 "기능"나는 많은 사람들이 잘못 생각 하나 생각 파악할 수
Joel Berger

25
언어가 새로워 졌다고해서 눈부신 내부 모순이 포함 되어도 괜찮다는 의미는 아닙니다.
궤도에서 가벼움 경주

이것은 베타 3에서 "고정"되었으며, var배열은 이제 완전히 변경 가능하고 let배열은 완전히 변경할 수 없습니다.
파스칼

4

델파이의 문자열과 배열은 정확히 같은 "기능"을 가졌습니다. 구현을 살펴보면 의미가 있습니다.

각 변수는 동적 메모리에 대한 포인터입니다. 해당 메모리에는 참조 카운트와 그 뒤에 배열의 데이터가 포함됩니다. 따라서 전체 배열을 복사하거나 포인터를 변경하지 않고도 배열의 값을 쉽게 변경할 수 있습니다. 배열의 크기를 조정하려면 더 많은 메모리를 할당해야합니다. 이 경우 현재 변수는 새로 할당 된 메모리를 가리 킵니다. 그러나 원래 배열을 가리키는 다른 모든 변수를 쉽게 추적 할 수 없으므로 혼자 두십시오.

물론보다 일관된 구현을하는 것은 어렵지 않습니다. 모든 변수가 크기를 조정하도록하려면 다음과 같이하십시오. 각 변수는 동적 메모리에 저장된 컨테이너에 대한 포인터입니다. 컨테이너에는 정확히 두 개의 항목, 참조 횟수와 실제 배열 데이터에 대한 포인터가 있습니다. 어레이 데이터는 별도의 동적 메모리 블록에 저장됩니다. 이제 배열 데이터에 대한 포인터가 하나뿐이므로 쉽게 크기를 조정할 수 있으며 모든 변수에 변경 사항이 표시됩니다.


4

많은 Swift 얼리 어답터들이이 오류가 발생하기 쉬운 배열 의미론에 대해 불평했으며 Chris Lattner는 배열 의미론이 전체 가치 의미론을 제공하도록 개정되었다고 설명했습니다 (계정이있는 사용자를위한 Apple 개발자 링크 ). 이것이 정확히 무엇을 의미하는지 보려면 다음 베타 버전을 기다려야합니다.


1
새로운 배열 동작은 이제 iOS 8 / Xcode 6 베타 3에 포함 된 SDK로 제공됩니다.
smileyborg

0

이것을 위해 .copy ()를 사용합니다.

    var a = [1, 2, 3]
    var b = a.copy()
     a[1] = 42 

1
코드를 실행할 때 "[Int] '유형의 값에'copy '멤버가 없습니다"
jreft56

0

이후 Swift 버전에서 배열 동작에 변화가 있습니까? 방금 예제를 실행하십시오.

var a = [1, 2, 3]
var b = a
a[1] = 42
a
b

그리고 내 결과는 [1, 42, 3]과 [1, 2, 3]입니다

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.