My class has a property that, in other languages, would be a simple Array of Strings, which would be initialized at an object's instantiation. In Swift, I have come up with the following:
class Foo {
var myArray: (String!)[]!
init(arraySize: Int, sourceOfData: SomeOtherClass){
myArray = Array<(String!)>(count: arraySize, repeatedValue:nil)
/* ... code to set the elements of the array using sourceOfData ... */
}
}
This is the only way I have been able to compile my code that allows pre-allocation of the Array's elements. However, I think all those exclamation marks make my code hard to read.
I know I can change my repeatedValue to an arbitrary non-nil string, and simplify the type to String[]!, but that would be a hack.
Also, I can do:
class Foo {
let myArray: String[] = []
init(sourceOfData: SomeOtherClass){
/*loop over sourceOfData*/{
myArray.append(/* computed String value */)
}
}
}
However, this has clearly worse performance, as the compiler cannot guess the length of my Array and allocate a contiguous block of memory for it. Normally, I would not care too much about optimizing the performance of this part of my code, but for this class it is critical.
Is there any way to have legible types without compromising performance?
You don't need to mark myArray as optional as long as you populate it in your init(). And if you can loop over sourceOfData using map something like this would work:
class Foo {
var myArray: String[]
init(sourceOfData: SomeOtherClass) {
myArray = sourceOfData.map {
return $0.computeStringValue()
}
}
}
if you really do need to use a loop, and you can at least determine how large an array you need, you can do something like this:
class Foo {
var myArray: String[]
init(sourceOfData: SomeOtherClass) {
myArray = Array<String>(count: SomeOtherClass.count, repeatedValue: "")
for i, item in enumerate(sourceOfData) {
myArray[i] = item.computeStringValue()
}
}
}
One last note about performance: LLVM is a very sophisticated compiler. You say that it is "obviously worse performance", but this is the kind of code for which static analysis may actually be able to determine the appropriate size of the array. I suggest profiling it with real data for your use case.
You can use the reserveCapacity method on Array. This will make sure there is enough pre-allocated memory to hold your data.
class Foo {
var myArray: String[]
init(sourceOfData: SomeOtherClass) {
myArray.reserveCapacity(sourceOfData.count)
// loop over data calling .append()
}
}
Related
I want to initialise elements only if an array has been set from outside the current class. Looks like a use for didSet!
// external API
var arr: [Display] = [] {
didSet {
array = arr
initialiseElements(arr: arr)
}
}
// internal
private var array: [Display] = []
but when we return the value from arr it might not be correct - I want to use array as a backing store.
I tried to use a getter on arr but this isn't allowed in Swift. How can I prevent the use of arr within the class, or otherwise ensure we only initiliseElements(:) via calls outside the class?
Why use didSet? why not set?
var arr: [Display] {
set {
array = newValue
initialiseElements(arr: newValue)
}
get {
return self.array
}
}
How can I prevent the use of arr within the class?
You can't, swift doesn't have such access control
otherwise ensure we only initiliseElements(:) via calls outside the class
Again, you can't.
It makes no sense logically as well, think of what you are asking for, you are asking class to declare a variable which itself cant set (read only) but should expose to it outside class to write (read + write) it?
How is that possible? What is the use case you are trying to solve here? If for some reason you ended up with this solution, may be solutioning is wrong! re think of your implementation.
Hope this helps
I have the following class, grossly simplified
class MyClass {
var largeArray: [Int] = []
init() {
largeArray.reserveCapacity(10000000)
... lots of code to add 10000000 various elements to largeArray
}
func mutateArray(idx: Int) {
largeArray[idx] = someVal
}
}
Surprisingly, when profiling this code, calls to mutateArray turned out to be very expensive, with most of the time spent
in _ArrayBufferProtocol.init(copying:), and some in _swift_release_dealloc. The time spent is proportional to the number of calls to mutateArray, indicating
that this happens every time the method is called.
Why is this happening? Is there any way to avoid it?
Your array's buffer leaks out of its MyClass encapsulation somewhere.
If largeArray is initialized within a MyClass object, it has sufficient capacity reserved up front, and you've never let anyone else have access to your class, or alias it yourself, then you couldn't possibly cause a CoW copy.
You should set var largeArray to private. Not only will that enforce the capsulation you're in need of, but it'll also show you what else is accessing this.
So I'm a little confused because of conflicting information, just looking for some clarity regarding memory allocation for Class properties.
So here are my assumptions, please let me know if any of them are wrong:
In Swift, except for Classes and Functions, everything is passed by Value.
Classes instances (objects) are allocated on the Heap
When you pass an object around, you are passing the pointer
When you reference a property on an object, the pointer is dereferenced, and the value of the property is retrieved
So here's my confusion, say my class has a String property, and an Int property. Both Swift data types, that get passed by value in any ordinary situation.
If I ask for let test = object.stringProperty, am I going to get a copy of my string value copied into my test variable?
Similarly, if I had a method inside of my class,
func getAllProperties() -> (String, Int) {
return (self.stringProperty, self.intProperty)
}
is object.getAllProperties() going to return a copy of the properties in a tuple?
I know it seems like a basic question, but after reading several sources I just ended up more uncertain than when I started
Yes and yes. It doesn't matter that the String and the Int were in a class. You asked for the String or the Int (or both), those are value types, you got copies.
It's easy to prove this to yourself, especially with the String. Just change something about it, and then look back at what the class instance is holding: it will be unchanged.
class C {
var stringProperty : String
init(string:String) {
self.stringProperty = string
}
}
let c = C(string:"hello")
var s = c.stringProperty
s.removeLast()
print(s) // hell
print(c.stringProperty) // hello
If you want to see the class-as-reference in action, make two of the same instance and do something to one of those:
class C {
var stringProperty : String
init(string:String) {
self.stringProperty = string
}
}
let c = C(string:"hello")
let d = c
c.stringProperty = "goodbye"
print(d.stringProperty) // goodbye
In my model, I have some arrays:
var thisArray = [Object]
var thatArray = [Object]
var anotherArray = [Object]
In my view controller, I want to switch on a value to determine which array I will append to:
var whichArray: [Object]!
switch someValue {
case .thisArray: whichArray = thisArray
case .thatArray: whichArray = thatArray // "He went thatArray!"
case .anotherArray: whichArray = anotherArray
}
whichArray.append(object)
But of course this won't work because Array is a value type.
Is there a way to do this? Of course I could do the following:
switch someValue {
case .thisArray: thisArray.append(object)
case .thatArray: thatArray.append(object)
case .anotherArray: anotherArray.append(object)
}
But that is so inelegant and redundant! And if there's other more complex things going on in the surrounding code, then it's especially so.
Is there a solution here? Is it possible to create a reference to a value type?
PS. Even better, though really its own question, is if I could use the name of the case (e.g., "thisArray" for someValue = .thisArray) to set the array, by name (i.e., avoid the whole switch statement and just say objectName.append(object) or something like that) but as far as I know this isn't a thing. Or maybe this IS possible? And maybe it's my birthday?
Since Arrays are value types - as you have said yourself - they can't be passed around (or assigned) as a reference. One solution would be to create a wrapper class for the Array which itself would then be a reference type. You can then assign this wrapper class instead of the arrays themselves.
Now, given that you also said you might prefer to access the Arrays by the name and completely get rid of the switch you could change your design to storing thisArray, thatArray and anotherArray in a Dictionary, with the keys being the different values for someValue.
This way you could simply append to the desired array with:
arrayDict[someValue]?.append(object)
(Given that you've properly set up the dictionary beforehand)
Like this for example:
enum Value {
case thisArray
case thatArray
case anotherArray
}
var arrayDict = [
Value.thisArray : [String](),
Value.thatArray : [String](),
Value.anotherArray : [String]()
]
arrayDict[.thatArray]?.append("Some String.")
For the sake of creating a short working example I've replaced Object with String but that obviously doesn't matter.
I would typically recommend solving this with closures. It's more powerful and safer. For example:
let append: (Object) -> Void
switch someValue {
case .thisArray: append = { thisArray.append($0) }
case .thatArray: append = { thatArray.append($0) }
case .anotherArray: append = { anotherArray.append($0) }
}
append(object)
(It would be ideal here to just say append = thisArray.append, but you can't do that in Swift today. It's a "partial application of a mutating function" and that's not currently legal.)
Even though Swift was designed to reduce pointer operations, pointers are still available:
var thisArray = [1,2,3]
var thatArray = [4,5,6]
var anotherArray = [7,8,9]
var ptr: UnsafeMutablePointer<[Int]>
let someValue = 2
switch someValue {
case 1: ptr = UnsafeMutablePointer(&thisArray)
case 2: ptr = UnsafeMutablePointer(&thatArray)
default: ptr = UnsafeMutablePointer(&anotherArray)
}
ptr.pointee.append(42)
print(thatArray) // [4,5,6,42]
A minor annoyance with this is that you have to call ptr.pointee to access the target array. If you assign the pointee to another variable (i.e. let whichArray = ptr.pointee), any modification to whichArray won't be reflected in the original array.
(I had to change your Object type to Int so that it runs in the IBM Swift Sandbox)
This question has been asked and answered for a couple other coding languages, but I think I may have a unique problem anyway. So, I want to duplicate a three dimensional array (filled with arbitrary objects). I believe I found that this:
var duplicateArray = originalArray
Does not work, since, for whatever reason, they thought it would a nice safety measure to have this create a duplicate array, but filled with pointers as sub-arrays instead of duplicating the sub-arrays as well. This seems like a strange design choice, since if duplicateArray and originalArray were one-dimensional, this would work as intended. Anyway, so I tried this (where object is some arbitrary object):
var duplicateArray = [[[object]]]()
for x in 0..<originalArray.count {
var tempArrYZ = [[object]]()
for y in 0..<originalArray[x].count {
var tempArrZ = [object]()
for z in 0..<originalArray[x][y].count {
let copiedObj = originalArray[x][y][z]
tempArrZ.append(copiedObj)
}
tempArrYZ.append(tempArrZ)
}
duplicateArray.append(tempArrYZ)
}
This still does not work; all the values in duplicateArray will act like a pointer for their values in originalArray. Perhaps someone has a simple way of deeply duplicating multidimensional arrays, or perhaps someone can find my error?
EDIT: How is this a duplicate of that other question? I'm asking specifically how to "deeply" duplicate. The question that's being referred to nebulously asked about duplicating arrays.
var duplicateArray = originalArray
Would work if the objects are not of reference type. However, for the reference type you need to actually create the copy of the object with copy. Your original code was pretty close.
var duplicateArray = [[[object]]]()
for x in 0..<originalArray.count {
var tempArrYZ = [[object]]()
for y in 0..<originalArray[x].count {
var tempArrZ = [object]()
for z in 0..<originalArray[x][y].count {
let copiedObj = originalArray[x][y][z].copy()
tempArrZ.append(copiedObj)
}
tempArrYZ.append(tempArrZ)
}
duplicateArray.append(tempArrYZ)
}
As already stated, your problem isn't really the copying of the array, it's the copying of Objects. Arrays, like all structs, are copied by value. Objects are copied by reference.
When you copy an array of objects, it's a brand new array with brand new references to the contained objects. Your code is simply creating additional references to the same objects then organizing them in a similar fashion.
Anyway, here's my simpler/functional implementation for copying arrays:
func copyArrayWithObjects <T: Copying>(items: [T]) -> [T]{
return items.map { $0.copy() }
}
func copy2DArrayWithObjects <T: Copying>(items: [[T]]) -> [[T]] {
return items.map(copyObjectsInArray)
}
func copy3DArrayWithObjects<T: Copying>(items: [[[T]]]) -> [[[T]]] {
return items.map(copy2DObjectInArray)
}
Then you can simply do this:
let copiedArray = copy3DArrayWithObjects(originalArray)
Theoretically I think it's possible to create a function to do this for an n-dimension array, but I haven't found a solution yet.
I think it would be best to write an extension on Array that adds conformance to NSCopying, which recursively copies the elements. This solution would be very elegant because it could scale to any number of dimmensions.
Swift arrays are value types so the snippet you provided is fine.
var duplicateArray = originalArray
See this example in a Playground as proof:
var array = [[["test"]]]
var newarray = array
// print different memory addresses
print(unsafeAddressOf(array[0][0][0])) // 0x00007ff7a302a760
print(unsafeAddressOf(newarray[0][0][0])) // 0x00007ff7a33000e0
If you use NSArray or reference types inside the Swift array, then they will no longer copy implicitly and will be treated with the same address - this can also be proved in the Playground. You would need to call copy() explicitly on reference types.