In Swift Programming Language, it says:
“all of the basic types in Swift—integers, floating-point numbers, Booleans, strings, arrays and dictionaries—are value types, and are implemented as structures behind the scenes.”
“structure instances are always passed by value, and class instances are always passed by reference”
“If you assign an Array instance to a constant or variable, or pass an Array instance as an argument to a function or method call, the contents of the array are not copied at the point that the assignment or call takes place. Instead, both arrays share the same sequence of element values. When you modify an element value through one array, the result is observable through the other.”
Clearly, Swift is contradicting itself when it comes to array. Is array a value type or reference type?
bases on interferece between #2 and #3, I'm trying to find the answer to your question via an example in practice. it is truly based on Swift types only.
I have an Array
here, and I fill it up with random numbers.
var arrayOfNumbers: Array<UInt32> = Array()
for _ in 0..100 {
arrayOfNumbers.append(arc4random())
}
I will check what it returns:
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
}
and I have a test method with two parameters, the first is the Array
itself, the second one is just the reference of the same Array
. I'm trying to do different things in that method to see what will happen.
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
}
that will print "(pS)"
, so it looks the reference has been passed over as first paramater not the copy of the struct. the consol says "(rS)"
, the the returned value was the same reference and not a copy.
func myReferenceTest(directArray: Array<UInt32>, inout referenceArray: Array<UInt32>) -> Array<UInt32> {
if directArray === referenceArray {
println("(pS)")
} else {
println("(pD)")
}
directArray[0] = 12
return directArray
}
it says still the same "pS"
and "rS"
, but if I print the original array's [0]
element it is updated to 12
, however directArray
was not an inout
paremeter at all. the reference was passed over, and the reference was returned, and I also called a non-mutating method on the array, and I was able to make certain changes.
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
}
the console says "pS"
but "rD"
becase I've called a mutating
method in the directArray
that caused the array was copied (O(n)
), and I made the changes on another instance.
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
}
same as the recent one, but it will say on console "pS"
and "rS"
again. so it seems the reference was returned and not the copy of the myOtherArray
.
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
}
it will show "pS"
and "rD"
again, and the reference array's first element is 12
and it looks the directArray
was copied after I've call a mutating
method on the referenceArray
. you can check it without doubt: the referenceArray
is still identical to my original array but the directArray
is different now.
If I don't return the directArray
that will be released at all when the method is run out of its scope, becase the copy is created in the method's scope only.
it seems the Array
is always creates a new instance of itself when you call a mutating
method on it, which is in accordance with the Swift documantation about the mutating
keyword:
However, if you need to modify the properties of your structure or enumeration within a particular method, you can opt in to mutating behavior for that method. The method can then mutate (that is, change) its properties from within the method, and any changes that it makes are written back to the original structure when the method ends. The method can also assign a completely new instance to its implicit
self
property, and this new instance will replace the existing one when the method ends.
(source)
that behaviour is pretty much as same as the Swift logic here: the compiler tries to not extend the code with copying of any object until it certainly necessary, which looks – in the case of Array
at least – happaning when a mutating
method is called on the object.
you will find more information about which methods of which Swift types are mutating
in the Swift Standard Library Reference, here.
nevertheless, even if the Array
looks to be defined as a struct, it defintely behaves like a class, because they have some class-level privileges:
- Type casting enables you to check and interpret the type of a class instance at runtime.
- Reference counting allows more than one reference to a class instance.
I have not found evidence the rest two privileges of classes, but having these two class-level privilages indicates the Array
is definitely more than a simple struct
, which makes the statement #1 ambiguous.
having those prvileges indicates the reference is being passed (logically) in every case.
the Dictionary
is a simpliest case, becase it looks an plain NSMutableDictionary
behind the scenes, when I ask the class's name via object_getClassName(...)
, it clearly refers to an instance of a mutable dictionary from Obj-C, which is defintely not a struct
even if the declaration of Dictionary
indicates a struct
.
it is pontless to go further in guessing of what happens in the engine, the compiler is still Beta so we don't know how the final compiler will work; evenetually many conspiration-theory can be defined about them (or already hve been), I've tried to focus on the facts only which can be provable during identifying them in background, which may be changed in the future, but those are the facts currently.
Array are special cases. They are value types with a special behavior.
At the page where you found the point 3) there are enough information to understand why:
The assignment and copy behavior for Swift’s Array type is more complex than for its Dictionary type. Array provides C-like performance when you work with an array’s contents and copies an array’s contents only when copying is necessary
and, immediately after you statement:
Instead, both arrays share the same sequence of element values. When you modify an element value through one array, the result is observable through the other.
For arrays, copying only takes place when you perform an action that has the potential to modify the length of the array.
So, reading these points we understand that:
Last thing: remember that, when an array is copied, only value types (structures, enumerations, primitive types) are copied. Objects are not copied, only their references (pointers) are copied
Array are just value types. That is correct. And it is very likely to use COW(Copy-On-Write) to avoid high copying cost of brute-force value-type semantics implementation. So it will be copied only when the new instance is going to be mutated.
There was some trouble with this semantics issue in beta period, but it was eliminated in later betas because having a grey-colored behaviour is insane, and now (Swift version 1.0) it is strictly value type semantics.
Your point #3 is just an explanation about the optimisation --- which is an implementation details meaningful only for optimisations. Forget about implementation details when you thinking about semantics. Just use arrays and dictionaries just like any other value-type stuffs. And remember the implementation details only when you need to concern about the performance and optimisation. In most cases, just using is fine in both of semantics or performance perspectives.
BTW, reference type means it has automatic/implicit identity that can be referenced.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With