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