Recently, I wrote this code without thinking about it very much:
myObject.myCollection.forEach { myObject.removeItem($0) }
where myObject.removeItem(_) removes an item from myObject.myCollection.
Looking at the code now, I am puzzled as to why this even works - shouldn't I get an exception along the lines of Collection was mutated while being enumerated?
The same code even works when using a regular for-in loop!
Is this expected behaviour or am I 'lucky' that it isn't crashing?
This is indeed expected behaviour – and is due to the fact that an Array in Swift (as well as many other collections in the standard library) is a value type with copy-on-write semantics. This means that its underlying buffer (which is stored indirectly) will be copied upon being mutated (and, as an optimisation, only when it's not uniquely referenced).
When you come to iterate over a Sequence (such as an array), be it with forEach(_:) or a standard for in loop, an iterator is created from the sequence's makeIterator() method, and its next() method is repeatedly applied in order to sequentially generate elements.
You can think of iterating over a sequence as looking like this:
let sequence = [1, 2, 3, 4]
var iterator = sequence.makeIterator()
// `next()` will return the next element, or `nil` if
// it has reached the end sequence.
while let element = iterator.next() {
// do something with the element
}
In the case of Array, an IndexingIterator is used as its iterator – which will iterate through the elements of a given collection by simply storing that collection along with the current index of the iteration. Each time next() is called, the base collection is subscripted with the index, which is then incremented, until it reaches endIndex (you can see its exact implementation here).
Therefore, when you come to mutate your array in the loop, its underlying buffer is not uniquely referenced, as the iterator also has a view onto it. This forces a copy of the buffer – which myCollection then uses.
So, there are now two arrays – the one which is being iterated over, and the one you're mutating. Any further mutations in the loop won't trigger another copy, as long as myCollection's buffer remains uniquely referenced.
This therefore means that it's perfectly safe to mutate a collection with value semantics while enumerating over it. The enumeration will iterate over the full length of the collection – completely independant of any mutations you do, as they will be done on a copy.
I asked a similar question in the Apple Developer
Forum and the answer is "yes, because of the value semantics of Array".
#originaluser2 said that already, but I would argue slightly different:
When myObject.removeItem($0) is called, a new array is created and
stored under the name myObject, but the array that forEach() was called upon is not modified.
Here is a simpler example demonstrating the effect:
extension Array {
func printMe() {
print(self)
}
}
var a = [1, 2, 3]
let pm = a.printMe // The instance method as a closure.
a.removeAll() // Modify the variable `a`.
pm() // Calls the method on the value that it was created with.
// Output: [1, 2, 3]
The Collection was copied before starting the iteration , and the code inside Foreach is applied on the real collection but the iteration is happening on the copied Collection which will be deleted after the last iteration .
Related
We all know an array in swift is a value type, this means after copying or assigning an array to another, modify the new array will not effect the old one. Such as:
var a = ["a", "b", "c", "d", "e"]
var b = a
b[0] = "1"
print(a[0]) // a
print(b[0]) // 1
But I'm wondering how could an array work like that. The length for a 'var' array is dynamical. Usually we must alloc some heap memory to contain all the values. And I do peek some source codes for struct Array, the underlining buffer for an array is implemented using a class. But when copying a struct which contains class or memory pointer member, the class and alloced memory will not copied by default.
So how could an array copy its buffer when copy or assign it to another one?
Assignment of any struct (such as Array) causes a shallow copy of the structure contents. There's no special behavior for Array. The buffer that stores the Array's elements is not actually part of the structure. A pointer to that buffer, stored on the heap, is part of the Array structure, meaning that upon assignment, the buffer pointer is copied, but it still points to the same buffer.
All mutating operations on Array do a check to see if the buffer is uniquely referenced. If so, then the algorithm proceeds. Otherwise, a copy of the buffer is made, and the pointer to the new buffer is saved to that Array instance, then the algorithm proceeds as previously. This is called Copy on Write (CoW). Notice that it's not an automatic feature of all value types. It is merely a manually implemented feature of a few standard library types (like Array, Set, Dictionary, String, and others). You could even implement it yourself for your own types.
When CoW occurs, it does not do any deep copying. It will copy values, which means:
In the case of value types (struct, enum, tuples), the values are the struct/enum/tuples themselves. In this case, a deep and shallow copy are the same thing.
In the case of reference types (class), the value being copied is the reference. The referenced object is not copied. The same object is pointed to by both the old and copied reference. Thus, it's a shallow copy.
Say I have a class which has an Array of object Photo:
class PhotoManager {
fileprivate var _photos: [Photo] = []
var photos: [Photo] {
return _photos
}
}
I read one article which says the following:
By default in Swift class instances are passed by reference and
structs passed by value. Swift’s built-in data types like Array and
Dictionary, are implemented as structs.
Meaning that the above getter returns a copy of [Photo] array.
Then, that same article tries to make the getter thread-safe by refactoring the code to:
fileprivate let concurrentPhotoQueue = DispatchQueue(label: "com.raywenderlich.GooglyPuff.photoQueue",
attributes: .concurrent)
fileprivate var _photos: [Photo] = []
var photos: [Photo] {
var photosCopy: [Photo]!
concurrentPhotoQueue.sync {
photosCopy = self._photos
}
return photosCopy
}
The above code explictly make a copy of self._photos in getter.
My questions are:
If by default swift already return an copy (pass by value) like the article said in the first place, why the article copy again to photosCopy to make it thread-safe? I feel myself do not fully understand these two parts mentioned in that article.
Does Swift3 really pass by value by default for Array instance like the article says?
Could someone please clarify it for me? Thanks!
I'll address your questions in reverse:
Does Swift3 really pass by value by default for Array instance like the article says?
Simple Answer: Yes
But I'm guessing that is not what your concern is when asking "Does Swift3 really pass by value". Swift behaves as if the array is copied in its entirety but behind the scenes it optimises the operation and the whole array is not copied until, and if, it needs to be. Swift uses an optimisation known as copy-on-write (COW).
However for the Swift programmer how the copy is done is not so important as the semantics of the operation - which is that after an assignment/copy the two arrays are independent and mutating one does not effect the other.
If by default swift already return an copy (pass by value) like the article said in the first place, why the article copy again to photosCopy to make it thread-safe? I feel myself do not fully understand these two parts mentioned in that article.
What this code is doing is insuring that the copy is done in a thread-safe way.
An array is not a trivial value, it is implemented as multi-field struct and some of those fields reference other structs and/or objects - this is needed to support an arrays ability to grow in size, etc.
In a multi-threaded system one thread could try to copy the array while another thread is trying to change the array. If these are allowed to happen at the same time then things easily can go wrong, e.g. the array could change while the copy is in progress, resulting in an invalid copy - part old value, part new value.
Swift per se is not thread safe; and in particular it will not prevent an array from being changed while a copy is being performed. The code you have addresses this by using a GCD queue so that during any alteration to the array by one thread all other writes or reads to the array in any other thread are blocked until the alteration is complete.
You might also be concerned that their are multiple copies going on here, self._photos to photoCopy, then photoCopy to the return value. While semantically this is what happens in practice there will probably only be one complex copy (and that will be thread safe) as the Swift system will optimise.
HTH
1) In code example what you provided will be returned copy of _photos.
As wrote in article:
The getter for this property is termed a read method as it’s reading
the mutable array. The caller gets a copy of the array and is protected
against mutating the original array inappropriately.
that's mean what you can access to _photos from outside of class, but you can't change them from there. Values of photos could be changed only inside class what make this array protected from it accidental changing.
2)Yes, Array is a value-type struct and it will be passed by value. You can easily check it in Playground
let arrayA = [1, 2, 3]
var arrayB = arrayA
arrayB[1] = 4 //change second value of arrayB
print(arrayA) //but arrayA didn't change
UPD #1
In article they have method func addPhoto(_ photo: Photo) what add new photo to _photos array what makes access to this property not thread-safe. That's mean what value of _photos could be changed on few thread in same time what will lead to issues.
They fixed it by writing photos on concurrentQueue with .barrier what make it thread-safely, _photos array will changed once per time
func addPhoto(_ photo: Photo) {
concurrentPhotoQueue.async(flags: .barrier) { // 1
self._photos.append(photo) // 2
DispatchQueue.main.async { // 3
self.postContentAddedNotification()
}
}
}
Now for ensure thread safety you need to read of _photos array on same queue. That's only reason why they refactored read method
Recently, I wrote this code without thinking about it very much:
myObject.myCollection.forEach { myObject.removeItem($0) }
where myObject.removeItem(_) removes an item from myObject.myCollection.
Looking at the code now, I am puzzled as to why this even works - shouldn't I get an exception along the lines of Collection was mutated while being enumerated?
The same code even works when using a regular for-in loop!
Is this expected behaviour or am I 'lucky' that it isn't crashing?
This is indeed expected behaviour – and is due to the fact that an Array in Swift (as well as many other collections in the standard library) is a value type with copy-on-write semantics. This means that its underlying buffer (which is stored indirectly) will be copied upon being mutated (and, as an optimisation, only when it's not uniquely referenced).
When you come to iterate over a Sequence (such as an array), be it with forEach(_:) or a standard for in loop, an iterator is created from the sequence's makeIterator() method, and its next() method is repeatedly applied in order to sequentially generate elements.
You can think of iterating over a sequence as looking like this:
let sequence = [1, 2, 3, 4]
var iterator = sequence.makeIterator()
// `next()` will return the next element, or `nil` if
// it has reached the end sequence.
while let element = iterator.next() {
// do something with the element
}
In the case of Array, an IndexingIterator is used as its iterator – which will iterate through the elements of a given collection by simply storing that collection along with the current index of the iteration. Each time next() is called, the base collection is subscripted with the index, which is then incremented, until it reaches endIndex (you can see its exact implementation here).
Therefore, when you come to mutate your array in the loop, its underlying buffer is not uniquely referenced, as the iterator also has a view onto it. This forces a copy of the buffer – which myCollection then uses.
So, there are now two arrays – the one which is being iterated over, and the one you're mutating. Any further mutations in the loop won't trigger another copy, as long as myCollection's buffer remains uniquely referenced.
This therefore means that it's perfectly safe to mutate a collection with value semantics while enumerating over it. The enumeration will iterate over the full length of the collection – completely independant of any mutations you do, as they will be done on a copy.
I asked a similar question in the Apple Developer
Forum and the answer is "yes, because of the value semantics of Array".
#originaluser2 said that already, but I would argue slightly different:
When myObject.removeItem($0) is called, a new array is created and
stored under the name myObject, but the array that forEach() was called upon is not modified.
Here is a simpler example demonstrating the effect:
extension Array {
func printMe() {
print(self)
}
}
var a = [1, 2, 3]
let pm = a.printMe // The instance method as a closure.
a.removeAll() // Modify the variable `a`.
pm() // Calls the method on the value that it was created with.
// Output: [1, 2, 3]
The Collection was copied before starting the iteration , and the code inside Foreach is applied on the real collection but the iteration is happening on the copied Collection which will be deleted after the last iteration .
How to initialise an array with maximum capacity without RepeatedValues?
var anotherThreeDoubles = Array(count: 3, repeatedValue: 2.5)
Like in this example with repeatedValue. Can we initialise without a value?
CMD-clicking the Array type in Xcode finds me the following function definition (along with the doc comment):
/// Ensure the array has enough mutable contiguous storage to store
/// minimumCapacity elements in. Note: does not affect count.
/// Complexity: O(N)
mutating func reserveCapacity(minimumCapacity: Int)
So in a way, you can tell an Array to pre-allocate storage for the given capacity by doing something like this:
var results: [T] = []
results.reserveCapacity(100)
And theoretically, hundred appends on the array should then performs better than without the capacity reservation call.
To enforce "maximum" capacity though, there is no way to do that short of a custom code manually putting nils into an array of Optional<T>s capped to the maximum size as suggested by #Bryan in the question comments.
For Swift 3.0, Value must be an optional type
var arrImages = [UIImage?](repeating: nil, count: 64)
UPDATE: As #chakrit says, you can use reserveCapacity now. This was added in later betas and is now available in the release version of Swift.
Arrays in Swift work differently than they do in Objective-C. In Swift you can't create an Array that has pre-allocated memory but does not contain elements. Just create an Array() and add as many elements as you want (3 in your case).
If you absolutely must do it this way, then use NSMutableArray like this:
var anotherThreeDoubles = NSMutableArray(capacity: 3)
I hope I understood the question correctly. Feel free to explain further.
As Jernej said, you can use NSMutableArray in this case. Note that both NSMutableArray and Swift Arrays do not actually limit how many elements you can add:
var anotherThreeDoubles = Array(count: 3, repeatedValue:10)
anotherThreeDoubles += 10 //another 3 doubles now has 4 elements
var arr: Array = NSMutableArray(capacity: 3)
arr.append(10)
arr.append(10)
arr.append(10)
arr.append(10) //arr now has 4 elements
Even though there's a reserveCapacity function, it's not advised
from the docs:
The Array type’s append(:) and append(contentsOf:) methods take care of this detail for you, but reserveCapacity(:) allocates only as much space as you tell it to (padded to a round value), and no more. This avoids over-allocation, but can result in insertion not having amortized constant-time performance.
Apple says it's better to let the array's append and append(contentsOf:) functions to take care of this for you unless you know the capacity of the array without calling the count property on a collection. They also have an example on the page I linked above.
I want to add items to mutable array from a dictionary. Problem is I want to check existing array items before adding new item. If same item is already there in the array, I want to replace it. else add the new item.
How could I do it?
You could perhaps use an NSMutableSet rather than an NSMutableArray. The addObject method on NSMutableSet will only "add a given object to the set, if it is not already a member."
If you'd like to check membership before adding to the set anyway, you can check the result of:
[mySet containsObject:myObjectFromDictionary]
...which returns a simple BOOL value indicating whether the set already contains an object whose isEqual method returns true when your object is passed to it.
(For a little extra functionality, NSCountedSet will keep track of the number of objects added to the "set" for which isEqual: returns true)
You could compare the result of : [yourArray indexOfObject:yourObject]; against NSNotFound to know if the object is in the array.
It will give you the index of the object to replace, or if it is equal to NSNotFound, you will add it.
Objects equality is tested with isEqual: method.
NSArray class reference.
On the face of it, both Vincent's and Rich's answers are correct.
However, there is a conceptual issue in the original question that hasn't been addressed.
Namely, that "membership in an array" via indexOfObject: (or containsObject: in a set) is ultimately done by comparing the two objects using isEqual:.
If isEqual: returns YES, then the two objects better had damned well be functionally identical in your code or else you have other, significantly more serious, problems in your design and implementation.
Thus, the real question should be "How do I detect if an object is already in an array and not add it?" and Rich's and Vincent's answer are both still correct.
I.e. you should only need to check for presence and, if present, take no action.
(Note that there are esoteric situations where replacement is actually warranted, but they are both truly esoteric and not generally used within the context of a mutable collection)