Consider the following toy example Swift code:
protocol Testable{}
class MyObj : Testable{}
class Test {
var arr:[Testable] = []
var didRun:Bool = false
func run() -> [Testable]{
if(didRun){
println("arr has \(arr.count) elements")
for e in arr{ // following access causes EXC_BAD_ACCESS
println(e)
}
return arr
} else{
provider({ (myArr : [AnyObject]) -> () in
self.arr = myArr as [MyObj]
self.didRun = true
})
return []
}
}
func provider( cb : ([AnyObject] -> ()) ){
let a:[MyObj] = [MyObj(),MyObj(),MyObj()]
cb(a)
}
}
and calling it the following way:
let t = Test()
t.run()
t.run()
This compiles but crashes at runtime when trying to iterate over the returned array. The arr.count
is also garbage, returns a random big number such as 232521760
and arr
itself points to somewhere far away around 0xfffffff9
, meaning clearly it's garbage.
My question is why is this? Compiler does not complain about type errors. Why am I not able to use the myArr
array, does the compiler make myArr
de-allocated after leaving the closure?
I can fix by changing the provider
call to be:
provider({ (myArr : [AnyObject]) -> () in
for e in myArr{
self.arr.append(e as MyObj)
}
self.didRun = true
})
but I'm more interested in why my first code does not work.
I would appreciate it if someone could explain to me closure semantics in Swift and why the above produces such errors.
Edit: as noted by @SevenTenEleven (an Apple employee) on the ADF thread related to this question:
It looks like there are issues with some covariant array assignments; please file a bug so we can either properly ban them at compile-time or properly implement them at runtime.
Let's do this, I did.
After doing some experiments and research, I came to the following conclusions:
[AnyObject]
to [MyObj]
Since it seems like provider
would always return Testable
, I was able to get your code working by changing your provider
function declaration and explicitly marking a
variable as an array of Testable
:
func provider(cb: [Testable] -> ()) {
let a : [Testable] = [MyObj(), MyObj(), MyObj()]
cb(a)
}
Then there's no need to downcast, so there's no error. Here's the whole code:
protocol Testable {}
class MyObj : Testable {}
class Test {
var arr : [Testable] = []
var didRun = false
func run() -> [Testable] {
if didRun {
println("arr has \(arr.count) elements")
for e in arr {
println(e)
}
return arr
} else {
provider() { (myArr : [Testable]) in
self.arr = myArr
self.didRun = true
}
return []
}
}
func provider(cb: [Testable] -> ()) {
let a : [Testable] = [MyObj(), MyObj(), MyObj()]
cb(a)
}
}
let t = Test()
t.run()
t.run()
The preceeding code outputs:
arr has 3 elements
_TtC5hgfds5MyObj
_TtC5hgfds5MyObj
_TtC5hgfds5MyObj
It seems swift doesn't like for-in loops on arrays of AnyObject or protocols. However, if you change it to an old-fashioned for-i loop, things work just fine.
So instead of:
for e in arr { // causes EXC_BAD_ACCESS
Just write:
for var i = 0; i < arr.count; ++i { // works fine
let e = arr[i]
...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With