Have a data source singleton with
class hugestuff ..{
var dataSources: [String: ThingWithIncrediblyLargeArrays] ..
var currentThing: String
}
ThingWithIncrediblyLargeArrays
is indeed a struct
. A typical use might be enormous tables of values to display in a grid on a screen. Each ThingWithIncrediblyLargeArrays
- that is to say each entry in the .dataSources
dictionary - could be a different screen, say.
struct ThingWithIncrediblyLargeArrays {
var rawUnsorted: [[String]]
var liveSorted: [[String]]
var swapValues: [[String]]
}
So throughout the app, you're accessing
cell.data = hugestuff.dataSources[currentThing].unsortedRows[3489412]
and so on.
And indeed sometimes changing things ...
hugestuff.dataSources[currentThing].searchRows =
hugestuff.dataSources[currentThing].unsortedRows.filter{blah}
This is all fantastic but purely as sugar it would be more convenient and sturdy if
cell.data = hugestuff.hot.unsortedRows[3489412]
hugestuff.hot.searchRows = hugestuff.hot.unsortedRows.filter{blah}
Well now, I believe that to achieve that, if you do this
var hot: ThingWithIncrediblyLargeArrays {
get { dataSources[currentThing] }
set { dataSources[currentThing] = newValue }
}
it all works.
In other words, the Way to do a "pointer or macro like thing", in this situation with a dictionary in Swift, is as above.
In fact, does that safely and correctly works in the intended way? To wit,
(A)
x = hugestuff.hot.enormousArray[13]
will not pointlessly copy the array during the process and
(B)
hugestuff.hot.searchRows = .. some new or modified array
will indeed "actually change" that array, i.e., it won't just instead incorrectly change some transient copy of the array somewhere.
hugestuff.hot.searchRows = hugestuff.hot.other.sortwhatever
Is hoping to be the same as
hugestuff.dataSources["electrons"].searchRows =
hugestuff.dataSources["electrons"].other.sortwhatever
In other words I am hoping that hugestuff.hot.searchRows = ..
in fact actually changes hugestuff.dataSources["electrons"]
Is this perfectly correct?
I mention "current" Swift because, of course, Swift dramatically changed how arrays work at one point, and, Swift uses the ingenious ("But ask on SO before you try anything tricky") copy-only-on-need approach for big arrays (or indeed any arrays).
BTW here is a fantastically useful answer to a related question Is it absolutely the case that Swift >will not< deep copy a large array, when one "guard let" the array?
Coda, just for anyone browsing this, as well as the pure macro-like syntax sugar aspect ("it is much quicker to just type .hot"), a really useful thing is you can ..
var hot: .. {
get { dataSources[currentThing] ?? some empty value }
If dataSources["sales"] say does not exist yet (for whatever reason: whether by design, eg, you are building them lazily, streaming, processing on other cores, whatever, or indeed just a plain programming mistake) you can handle all that in there.
If ThingWithIncrediblyLargeArrays
is a class, hot
will trivially act like a "pointer". After all, classes are reference types, and hot
will return a reference.
From now on I will assume ThingWithIncrediblyLargeArrays
is a struct.
Array
is copy-on-write. For the array storage to be copied, it has to
Array
variables (i.e. isKnownUniquelyReferenced
is false)In both cases A and B, hot
's getter returns a copy of ThingWithIncrediblyLargeArrays
, but the arrays in the copies will share the same storage, because you are not writing to hugestuff.hot.enormousArray
.
However, hot
is not completely like a pointer. If you do:
hugeStuff.hot.enormousArray[someIndex] = something
hot
returns a copy of ThingWithIncrediblyLargeArrays
, which shares the array storage with dataSources[currentThing]
. Then you modified the enormousArray
of the copy returned by hot
. Now the array storage cannot be shared, so the whole array storage will be copied.
You can think of this as:
let temp = hugeStuff.hot
temp.enormousArray[someIndex] = something
hugeStuff.hot = temp
The array storage is not uniquely referenced here - temp
and huge.dataSources[someIndex]
share the storage.
A good way to determine whether array storages are copied is to use the "Allocations" tool in Instruments. Try it with this code:
struct ThingWithIncrediblyLargeArrays {
var enormousArray = Array(repeating: 1, count: 1000)
var searchRows = ["Row1", "Row2", "Row3"]
}
class HugeStuff {
var dataSources = ["Foo": ThingWithIncrediblyLargeArrays()]
var currentThing = "Foo"
var hot: ThingWithIncrediblyLargeArrays {
get { dataSources[currentThing]! }
set { dataSources[currentThing] = newValue }
}
}
let hugeStuff = HugeStuff()
// run each of these loops separately:
// case A
//for i in 0..<1000 {
// let x = hugeStuff.hot.enormousArray[i]
// print(x)
//}
// case B
//for i in 0..<1000 {
// hugeStuff.hot.searchRows = ["Row \(i)"]
// print("x")
//}
// case C
//for i in 0..<1000 {
// hugeStuff.hot.enormousArray[i] = i
// print("x")
//}
You can filter by "array", and you will see something like this in Instruments for case C:
The name _TtGCs23_ContiguousArrayStorageSi_
is the mangled name for ContiguousArrayStorage<Int>
. You can demangle these using the swift demangle
command.