Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift EXC_BAD_ACCESS when using arrays in a closure

Tags:

closures

swift

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.

like image 762
rafalio Avatar asked Jul 31 '14 22:07

rafalio


2 Answers

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:

  • This has nothing to do with clousures and outer scopes
  • The error happens only if you downcast [AnyObject] to [MyObj]
  • The error hapens only if the "outer" variable is declared as an array of protocol types

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
like image 189
akashivskyy Avatar answered Nov 15 '22 08:11

akashivskyy


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]
    ...
}
like image 36
Yonat Avatar answered Nov 15 '22 09:11

Yonat