Conflicting definition of Swift struct and array

后端 未结 6 1018
孤街浪徒
孤街浪徒 2021-02-01 09:56

In Swift Programming Language, it says:

  1. “all of the basic types in Swift—integers, floating-point numbers, Booleans, strings, arrays and dictio

相关标签:
6条回答
  • 2021-02-01 10:43

    The Array is struct:

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

    And all struct are always copied when they are passed around in the code. Classes are passed by reference.

    So the Array are value types.

    0 讨论(0)
  • 2021-02-01 10:49

    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:

    • array are struct but with a special behavior, the mainly reason is to conserve C-like performance.
    • the copy is made only when the length of the copied array is changed

    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

    0 讨论(0)
  • 2021-02-01 10:50

    Array in swift are value type when i try the following code:

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

    It print different value for myArray1 and my Array2. If they share same reference the value must be same.

    0 讨论(0)
  • 2021-02-01 10:53

    Array is indeed a struct, a value type. However, they went into great lengths to make it behave like a reference type (special copy behaviour, subscripts can modify even constant arrays, they even defined === operator especially for arrays!).

    This was probably done because of performance reasons but the resulting behavior is buggy and hard to work with.

    To quote Apple (https://devforums.apple.com/thread/228695)

    Array semantics were in flux at the time of Beta 1, and have been revised to provide full value semantics like Dictionary and String. This will be available in later betas.

    -Chris

    So, we can expect the Array behavior to change greatly in future betas.

    0 讨论(0)
  • 2021-02-01 10:54

    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.

    0 讨论(0)
  • 2021-02-01 10:55

    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.


    1st case

    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.


    2nd case

    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.


    3rd case

    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.


    5th case

    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.


    6th case

    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.


    not conclusion but observation instead

    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.

    0 讨论(0)
提交回复
热议问题