2D arrays: Index out of range - arrays

I'm pretty new to Swift, and I'm trying to store my data as a 2D AnyObject array. I have declared var internalData = [[AnyObject]]() and a struct like so:
struct WiFiData {
var latency: Double
var duration: Double
}
Now, in another function, I would like to switch on the instance variable currentExit, which is an enum:
private var currentExit = Exit.A
enum Exit {
case A
case B
case C
case NotSelected
}
func someFunc() {
switch self.currentExit {
case .A:
self.internalData[0].append(WiFiData(latency: 1.5, duration: 4.0) as AnyObject) // <- CRASHES ON THIS LINE
......// other cases
}
However, it always crashes on the line self.internalData[0].append(WiFiData(latency: 1.5, duration: 4.0) as AnyObject) with the following error:
fatal error: Index out of range
Can anyone tell me why and how I could fix it? Any help is appreciated, thanks!

You instantiated a 2d array with [[AnyObject]](), but it's still empty, meaning there's nothing at self.internalData[0] for you to append to. There are a lot of things you could do to handle this, but if you know you're going to have 3 arrays inside self.internalData you may as well instantiate each internal array like so:
self.internalData = [[AnyObject](), [AnyObject](), [AnyObject]()]
Now you do have an empty array at self.internalData[0] that you can append items to.
It seems, though, like if you already know you're going to have 3 arrays of WiFiData it'd be even better just to create 3 variables:
var a = [WiFiData]()
var b = [WiFiData]()
etc.
and then you can access the individual arrays by name.

There are no "2D arrays" in Swift. There are arrays of arrays, which are different. For example, there is no promise that each row will have the same number of columns in an array of arrays.
In your case, the problem is that internalData has no elements in it. There has to be an internalData[0] element already created before you can append to it. You're assignment of var internalData = [[AnyObject]]() means there's no element 0. It's an empty array (of arrays).
Also keep in mind that the type [[AnyObject]] is very likely to cause a lot of problems for you. AnyObject is useful for working with Cocoa APIs, but generally creates a lot of headaches and should very rarely be part of a Swift property. You should almost certainly create some more specific type to hold your data. It's unclear from your example what you expect internalData to hold. Your code suggests, though, that you mean something more like:
var internalData: [Exit: [ExitRecord]] = [:]
enum Exit {
case A
case B
case C
case NotSelected
}
enum ExitRecord {
case wifi(latency: Float, duration: Float)
}
func someFunc() {
switch self.currentExit {
case .A:
var currentRecords = internalData[.A] ?? []
currentRecords.append(.wifi(latency: 1.5, duration: 4.0))
internalData[.A] = currentRecords
......// other cases
}

Related

Swift Array with Only Two Possible Values

I want to create an array, let's say called nums and I want this array to hold either a Double or an Error value, and nothing else. Currently, I can get around the problem by instantiating nums as follows:
var nums = Array<(Double?, Error?)>()
Then I go through and do something like:
nums.append((5.0, nil))
nums.append((nil, Error.invalidNumber))
nums.append((10.0, nil))
This works, but instead of having nums have either a Double or an Error like I want, it has a tuple of those values. How can I change the instantiation of the array so that I only need to append one of the values to the array?
My goal is to be able to do this:
nums.append(5.0)
nums.append(Error.invalidNumber)
nums.append(10.0)
Result if the type for your needs. It is a generic enum whose value can either be success(Value) (where Value is Double for your case) or failure(Error).
var nums = [Result<Double, Error>]()
And then you can do:
nums.append(.success(5.0))
nums.append(.failure(Error.invalidNumber))
nums.append(.success(10.0))
What you want is not a tuple but an enum:
enum DoubleOrNothing {
case double(Double)
case nothing(Error)
}
Now it can only be one or the other and the "value" is the associated value of the case that it is. Just make an array of that enum and you're home free.
enum MyError : Error {
case invalidNumber
}
var nums = Array<DoubleOrNothing>()
nums.append(.double(5.0))
nums.append(.nothing(MyError.invalidNumber))
nums.append(.double(10.0))
David's answer is what to go with if your errors are all the same type. Otherwise, you need to use throwing get accessors. (A [() throws -> Double].)
var getDoubleArray = [
{ 5.0 },
{ throw Error.invalidNumber },
{ 10 }
]

Swift Allocate Array of Pointers

How can I use UnsafeMutablePointer<T?> as UnsafeMutablePointer<UnsafeRawPointer?>!
e.g. Trying to allocate n blocks of memory for type T, to get values from a CFSet cfsetref:
var array = UnsafeMutableRawPointer<T?>.allocate(capacity: n)
CFSetGetValues(cfsetref, array) // error
Which gives the error
Cannot convert value of type 'UnsafeMutablePointer<T?>' to expected
argument type 'UnsafeMutablePointer<UnsafeRawPointer?>!'
I tried declaring array as UnsafeMutablePointer<UnsafeRawPointer?> then doing
for i in 0..<n {
var d = array[i]
d?.bindMemory(to: T.self, capacity: 1)
}
But I still get EXC_BAD_INSTRUCTION errors when attempting to access array[i] (after binding the memory again to T)
Many things depends on how you get your cfsetref and what actually is T.
But anyway, CFSetGetValues expects UnsafeMutablePointer<UnsafeRawPointer?>! as shown in the error message.
let n = CFSetGetCount(cfsetref)
let array = UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: n)
array.initialize(to: nil, count: n)
CFSetGetValues(cfsetref, array)
To safely access the contents of array, you need to know how T is managed by Swift ARC. For example, assuming T is NSNumber, you need to tell Swift ARC to manage the result with writing something like this:
let managedArray = UnsafeMutableBufferPointer(start: array, count: n).map{Unmanaged<NSNumber>.fromOpaque($0!).takeRetainedValue()}
print(managedArray)
But as well as other CF-collection types, the better way to handle CFSet is bridging it to Swift Set:
if let swiftSet = cfsetref as? Set<NSNumber> {
let swiftArray = Array(swiftSet)
print(swiftArray)
}
How do you get your cfsetref and what actually is T? With such information, I would try to tell you what sort of code you need to write in your actual case.

Switch using array not running?

For some reason my program refuses to run no matter what I do. Right now the code below gives me error
EXC_BAD_INSTRUCTION (code=EXC_BAD_i386_INVOP, subcode=0x0)
the code:
var array = [String]()
array[0] = "lmao"
switch array[0] {
case "lmao":
print("fug")
default:
print("fugger")
}
You are not allocating any space in the array - it is empty, so accessing element 0 causes an error. Use array.append("lmao") rather than array[0] = "lmao".
Alternatively, if you know exactly how many elements you need (say, 10) use
var array = [String](repeating: "", count: 10)
as your declaration, then you can directly set array[0] through to array[9]. You'll still get an error accessing array[10], because the array does not have 11 members...
You should use append method to add a value into your array:
array.append("lmao")
It would be a good idea to take some minutes to read Apple's documentation on swift array. It will help you to understand how things like memory allocations and mutability works.
Here is a fully working example:
var str = "Hello, playground"
var array = [String]()
array.append("lmao")
switch array[0] {
case "lmao":
print("fug")
default:
print("fugger")
}
(Which displays fug)

Storing a reference to array in swift

I want to pass an array to an object and store a reference to this array. I want to be able to modify this array within this object and make sure that it's modified everywhere else.
Here is what I am trying to accomplish (how the code doesn't work)
class Foo {
var foo : Array<Int>
init(foo: Array<Int>) {
self.foo = foo
}
func modify() {
foo.append(5)
}
}
var a = [1,2,3,4]
let bar = Foo(a)
bar.modify()
print(a) // My goal is that it will print 1,2,3,4,5
My findings so far
A) The array (by default) are passed strange way. It's a reference until you modify an array length. As soon as you modify a length it will be copied and modified. As result, if I append or delete anything from it in the object it won't be seen outside
B) I can use inout on a function parameter. This will allow me to modify it within this function. However, as soon as I will try to assign it to some object member I am again struck by A)
C) I can wrap an array in some Container class. This probably is the cleanest way. However, I serialize/deserialize these objects and I would rather not put it in Container (because I will have to work around some things for serialization and deserialization and sending it to the server).
Are there anything else? Am I missing some Swift construct which allows me to do that?
You'll have to use an NSArray or NSMutableArray for this because Swift Arrays are value types so any assignment will make a copy.
You could make use of Swifts (very un-swifty) UnsafeMutablePointer.
Since (from your post) the behaviour references to arrays can't really seem be trusted, instead keep an UnsafeMutablePointer companion to the class inner array foo as well as any "external" arrays that you want to be binded to foo, in the sense that they are both just pointers to same address in memory.
class Foo {
var foo : [Int]
var pInner: UnsafeMutablePointer<Int>
init(foo: [Int]) {
pInner = UnsafeMutablePointer(foo)
self.foo = Array(UnsafeBufferPointer(start: pInner, count: foo.count))
}
func modify(inout pOuter: UnsafeMutablePointer<Int>) {
foo.append(5) // <-- foo gets new memory adress
pInner = UnsafeMutablePointer(foo)
pOuter = pInner
}
}
var a = [1,2,3,4] // first alloc in memory
var pOuter: UnsafeMutablePointer<Int> = UnsafeMutablePointer(a)
var bar = Foo(foo: a) // 'bar.foo' now at same address as 'a'
print(bar.foo) // [1,2,3,4]
bar.modify(&pOuter) // -> [1,2,3,4,5]
a = Array(UnsafeBufferPointer(start: pOuter, count: bar.foo.count))
/* Same pointer adress, OK! */
print(bar.pInner)
print(pOuter)
/* Naturally same value (same address in memory) */
print(bar.foo)
print(a)
Pointers can be dangerous though (hence the fitting type name), and, again, very un-swifty. Anyway...
/* When you're done: clear pointers. Usually when using
pointers like these you should take care to .destroy
and .dealloc, but here your pointers are just companions
to an Array property (which has a pointer an reference
counter itself), and the latter will take care of the
objects in memory when it goes out of scope. */
bar.pInner = nil
pOuter = nil
Now, what happens when either a or foo goes out of scope, will it break the variable that are not out of scope, or does Swift contain some clever reference counting that realises a memory address is still in use? I haven't investigated this, but feel free to indulge yourself in that.
From the Swift Programming Language,
Structures are always copied when they are passed around in your code, and do not use reference counting.
If you examine the contents of the array variable, you will see that indeed the append works:
class Foo {
var foo : Array
init(_ foo: Array) {
self.foo = foo
}
func modify() {
foo.append(5)
}
func printFoo() {
print("self.foo: \(foo)")
}
}
let a = [1,2,3,4]
let bar = Foo(a)
bar.modify()
bar.printFoo()
print("a: \(a)")
produces
self.foo: [1, 2, 3, 4, 5]
a: [1, 2, 3, 4]
You have taken a copy of a, not a reference to a.
a is declared a constant hence cannot be modified. If you are planning to modify the contents of a, declare it as a variable. i.e.,
var a = [1,2,3,4]
I haven't tested this but, as you are using a class to wrap the array, I see no reason why the following would not work.
class Foo {
var foo : Array<Int>
init(foo: inout Array<Int>) {
self.foo = foo
}
func modify() {
foo.append(5)
}
}
let a = [1,2,3,4]
let bar = Foo(&a)
bar.modify()
print("a: \(a)") // a: [1,2,3,4,5]

Can an [AnyObject] array be optionally downcast to a type-specific array?

I'm reading through the Swift documentation, looking at the section regarding type casting.
The documentation talks about getting an array of type [AnyObject] from Foundation frameworks stuff (what would be an NSArray * in Objective-C).
First, the documentation provides this example:
for object in someObjects {
let movie = object as Movie
println("Movie: '\(movie.name)', dir. \(movie.director)")
}
Now, I want to change the example slightly, to a case where I don't know all the objects are of type Movie, so I'd do this:
for object in someObject {
if let movie = object as? Movie {
println("Movie: '\(movie.name', dir. \(movie.director)")
}
}
The documentation then provides an example of a better way to write the first loop:
for movie in someObjects as [Movie] {
println("Movie: '\(movie.name)', dir. \(movie.director)")
}
Where we downcast someObjects from an [AnyObject] to a [Movie] so we don't have to downcast within the loop.
And this got me thinking, can the array be option downcast as a whole?
if let someMovies = someObjects as? [Movie] {
for movie in someMovies {
println("Movie: '\(movie.name)', dir. \(movie.director)")
}
}
Does this work? And if so, how bad is this from a performance standpoint? How long would it take to check the type of every object in a 10,000 element array using the optional downcast?
I understand that the implications between this snippet and my previous optional downcast snippet are different. The first will iterate through every object and only attempt to print if the object is a Movie, where the second will only enter the loop if the array can be downcast to a [Movie] array, in which case it will either print all or none, but I can imagine there are situations where this would be preferable.
You've got it -- it works exactly like your example code:
let strings = ["Hi", "Hello", "Aloha"]
let anyObjects: [AnyObject] = strings
if let downcastStrings = anyObjects as? [String] {
println("It's a [String]")
}
// console says "It's a [String]"
No idea about performance, but I wouldn't assume that it will have to iterate through the full array to determine if a downcast is possible.
So I got curious, and ran a quick test with 100,000 simple values in a couple different [AnyObject] configurations, where I'm trying to downcast the array to a [String] vs. downcasting the individual elements:
// var anyObjects: [AnyObject] = [AnyObject]()
// filled with random assortment of Int, String, Double, Bool
Running test with mixed array
downcast array execution time = 0.000522
downcast elements execution time = 0.571749
// var actuallyStrings: [AnyObject] = [AnyObject]()
// filled with String values
Running test with all strings
downcast array execution time = 1.141267
downcast elements execution time = 0.853765
It looks like it's super fast to dismiss the mixed array as non-downcastable, since it just needs to scan until it finds a non-String element. For an array that it can downcast, it clearly has to crunch through the whole array, and takes much longer, although I'm not sure why it's not the same speed as looping through the array and manually checking each element.
Let's try this
var someObjects = [
NSString(),
NSUUID()
]
let uuids = someObjects as? NSUUID[]
uuids is nil
var someOtherObjects = [
NSUUID(),
NSUUID()
]
let all_uuids = someOtherObjects as? NSUUID[]
all_uuids is equal to someOtherObjects
So it looks like it does work. You can use the expression to test if all elements of the array are of the expected type but it will not filter the array to select only the expected type.

Resources