Swift结构和数组的冲突定义

时间:2014-06-27 11:05:14

标签: swift

在Swift编程语言中,它说:

  1. “Swift整数,浮点数,布尔值,字符串,数组和字典 - 中的所有基本类型都是值类型,并且是作为幕后结构实现。“

  2. 结构实例始终按值传递,类实例始终通过引用传递”

  3. “如果将Array实例分配给常量或变量,或将Array实例作为参数传递给函数或方法调用,则不会复制数组内容 at分配或呼叫发生的点。相反,两个数组共享相同的元素值序列。当您通过一个数组修改元素值时,结果可以通过另一个数组观察到。“

  4. 很明显,Swift在阵列方面自相矛盾。数组是值类型还是引用类型?

6 个答案:

答案 0 :(得分:8)

数组是特殊情况。它们是具有特殊行为的值类型。

在您找到第3点的页面上,有足够的信息可以理解原因:

  

Swift的Array类型的赋值和复制行为更多   比其词典类型复杂。数组提供类似C的   使用数组的内容并复制时的性能   数组的内容仅在需要复制时

并且,在您声明后立即:

  

相反,两个数组共享相同的元素值序列。什么时候   你通过一个数组修改一个元素值,结果是   通过对方可以观察到。

     

对于数组,仅在执行操作时进行复制   有可能修改数组的长度。

所以,阅读这些观点我们理解:

  • 数组是struct但具有特殊行为,主要原因是为了保持类似C的性能。
  • 只有在复制的数组的长度发生变化时才会进行复制

最后一件事:请记住,复制数组时,只复制值类型(结构,枚举,基本类型)。不复制对象,只复制其引用(指针)

答案 1 :(得分:2)

基于#2 #3 之间的干扰,我试图通过实践中的示例找到问题的答案。它真的只基于 Swift 类型。

我这里有一个Array,我用随机数填写。

var arrayOfNumbers: Array<UInt32> = Array()
for _ in 0..100 {
    arrayOfNumbers.append(arc4random())
}

我会检查它返回的内容:

let myReturnedArray: Array<UInt32> = self.myReferenceTest(arrayOfNumbers, referenceArray: &arrayOfNumbers)

if arrayOfNumbers === myReturnedArray {
    println("(rS)") // returned value is the same instance
} else {
    println("(rD)") // returned value is a different instance
}

我有一个带有两个参数的测试方法,第一个是Array本身,第二个只是同一个Array的引用。我试图用那种方法做不同的事情来看看会发生什么。


第一种情况

func myReferenceTest (directory: Array<UInt32, Inuit referenceArray: Array<UInt32) -> Array<UInt32> {
    if directArray === referenceArray {
        println("(pS)") // the same instance...
    } else {
        println("(pD)") // different instance ...
    }

    return directArray
}

将打印"(pS)",因此看起来引用已作为第一个参数传递而不是结构的副本。 consol说"(rS)",返回的值与引用相同,而不是副本。


第二种情况

func myReferenceTest(directArray: Array<UInt32>, inout referenceArray: Array<UInt32>) -> Array<UInt32> {
    if directArray === referenceArray {
        println("(pS)")
    } else {
        println("(pD)")
    }

    directArray[0] = 12

    return directArray
}

它仍然表示相同的"pS""rS",但如果我打印原始数组的[0]元素,它会更新为12,但是directArray是根本不是inout paremeter。 引用被传递,并且返回了引用,我还在数组上调用了非变异方法,并且我能够进行某些更改。


第三种情况

func myReferenceTest(directArray: Array<UInt32>, inout referenceArray: Array<UInt32>) -> Array<UInt32> {
    if directArray === referenceArray {
        println("(pS)")
    } else {
        println("(pD)")
    }

    var myOtherArray = directArray
    myOtherArray.append(arc4random())

    return myOtherArray
}

控制台说"pS""rD"因为我在mutating中调用了导致数组被复制的directArray方法(O(n)),我在另一个实例上做了更改。


第五种情况

func myReferenceTest(directArray: Array<UInt32>, inout referenceArray: Array<UInt32>) -> Array<UInt32> {
    if directArray === referenceArray {
        println("(pS)")
    } else {
        println("(pD)")
    }

    var myOtherArray = directArray
    myOtherArray.append(arc4random())
    referenceArray = myOtherArray

    return myOtherArray
}

与最近的版本相同,但会再次在控制台"pS""rS"上说明。所以似乎返回了引用,而不是myOtherArray的副本。


第6例

func myReferenceTest(directArray: Array<UInt32>, inout referenceArray: Array<UInt32>) -> Array<UInt32> {
    if directArray === referenceArray {
        println("(pS)")
    } else {
        println("(pD)")
    }

    referenceArray.append(arc4random())
    directArray[0] = 12

    return directArray
}

它会再次显示"pS""rD",参考数组的第一个元素是12,看起来directArray在我调用{{}后被复制了在mutating上的1}}方法。您可以毫无疑问地检查它:referenceArray仍然与原始数组相同,但referenceArray现在不同了。

如果我没有返回当方法超出其范围时将要释放的directArray,那么只能在方法的范围内创建副本。


不是结论而是观察

当您在其上调用directArray方法时,Array似乎总是创建一个新的实例,这与{em> Swift 关于{的文档一致{1}}关键字:

  

但是,如果需要在特定方法中修改结构或枚举的属性,则可以选择变异该方法的行为。然后,该方法可以从方法中改变(即更改)其属性,并且当方法结束时,它所做的任何更改都将写回原始结构。该方法还可以为其隐式mutating属性分配一个全新的实例,并且该新实例将在方法结束时替换现有实例。

source

这种行为与这里的 Swift 逻辑非常相似:编译器试图不通过复制任何对象来扩展代码,直到它肯定是必要的,这看起来是{{{ 1}}至少 - 在对象上调用mutating方法时感到高兴。

您可以在 Swift标准库参考here中找到有关 Swift 类型self的哪些方法的更多信息。


然而,即使Array看起来被定义为 struct ,它也会像一样行事,因为它们有一些类级{ {3}}:

  
      
  • 类型转换使您可以在运行时检查和解释类实例的类型。
  •   
  • 引用计数允许对类实例进行多次引用。
  •   

我没有找到证据证明其余两个类的特权,但是这两个类级别的特权表明mutating肯定是更多而不是简单的mutating,使声明#1 不明确。

有这些prvileges表明在每种情况下都会传递(逻辑上)引用。


Array是一个最简单的情况,因为它在幕后看起来很简单Array,当我通过struct询问班级名称时,它显然是指一个可变的实例来自Obj-C的字典,即使Dictionary的声明表示NSMutableDictionary,也绝不是object_getClassName(...)

进一步猜测引擎中发生了什么是无用的,编译器仍然是 Beta ,所以我们不知道最终编译器将如何工作;在很多情况下,可以定义许多关于它们的理论 - (或者已经过去了),我试图只关注那些在背景中识别它们时可以证明的事实,这些事实可能会在将来改变,但那些是事实。目前。

答案 2 :(得分:1)

数组只是值类型。那是正确的。并且很可能使用COW(Copy-On-Write)来避免暴力值类型语义实现的高复制成本。因此,只有在新实例要进行变异时才会复制它。

在beta期间,这个语义问题出现了一些问题,但是在后来的测试版中它被淘汰了,因为灰色行为是疯狂的,而现在(Swift版本1.0)它是严格的值类型语义。

您的观点#3只是对优化的解释 - 这是一个仅对优化有意义的实现细节。在考虑语义时忘记实现细节。就像使用任何其他值类型的东西一样使用数组和字典。并记住实施细节仅在时需要关注性能和优化。在大多数情况下,只需使用在语义或性能方面都很好。

BTW,引用类型表示它具有可以引用的自动/隐式标识

答案 3 :(得分:0)

当我尝试以下代码时,

swift中的数组是值类型:

var myArray1 = [1, 2, 3]
var myArray2 = myArray1
myArray2 += 4;
println(myArray1);
println(myArray2);

它为myArray1和我的Array2打印不同的值。如果它们共享相同的引用,则该值必须相同。

答案 4 :(得分:0)

Array是struct:

struct Array<T> : MutableCollection, Sliceable {...}

在代码中传递所有结构时,它们总是被复制。 类通过引用传递。

所以Array是值类型。

答案 5 :(得分:0)

Array确实是一个struct,一个值类型。然而,他们竭尽全力让它的行为类似于引用类型(特殊的复制行为,下标甚至可以修改常量数组,甚至可以为数组定义===运算符!)。

这可能是由于性能原因造成的,但由此产生的行为是错误的并且难以使用。

引用Apple(https://devforums.apple.com/thread/228695

  

在Beta 1时,数组语义不断变化,并且已经过修改,以提供像Dictionary和String这样的完整值语义。这将在以后的测试版中提供。

     

-Chris

因此,我们可以预期未来测试版中的数组行为会发生很大变化。