Swift EXC_BAD_ACCESS при использовании массивов в замыкании

Рассмотрим следующий игрушечный пример кода Swift:

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)
    }
}

и называя это следующим образом:

 let t = Test()
 t.run()
 t.run()

Это компилируется, но вылетает во время выполнения при попытке перебрать возвращаемый массив. arr.count также мусор, возвращает случайное большое число, такое как 232521760 а также arr сама указывает куда-то далеко 0xfffffff9это означает, что это мусор.

Мой вопрос, почему это? Компилятор не жалуется на ошибки типа. Почему я не могу использовать myArr массив, делает ли компилятор myArr распределяется после выхода из закрытия?

Я могу исправить, изменив provider позвонить, чтобы быть:

provider({ (myArr : [AnyObject]) -> () in
    for e in myArr{
      self.arr.append(e as MyObj)
    }
    self.didRun = true
 })

но меня больше интересует, почему мой первый код не работает.

Я был бы признателен, если бы кто-нибудь мог объяснить мне семантику замыкания в Swift и почему вышеизложенное приводит к таким ошибкам.

2 ответа

Решение

Изменить: как отметил @SevenTenEleven (сотрудник Apple) в теме ADF, связанной с этим вопросом:

Похоже, что есть проблемы с некоторыми ковариантными присвоениями массивов; пожалуйста, отправьте сообщение об ошибке, чтобы мы могли либо правильно заблокировать их во время компиляции, либо правильно реализовать их во время выполнения.

Давайте сделаем это, я сделал.


Проведя некоторые эксперименты и исследования, я пришел к следующим выводам:

  • Это не имеет ничего общего с коллизиями и внешними границами.
  • Ошибка происходит только если вы опущены [AnyObject] в [MyObj]
  • Ошибка происходит только в том случае, если "внешняя" переменная объявлена ​​как массив типов протоколов.

Так как кажется provider всегда вернется TestableЯ смог заставить ваш код работать, изменив provider объявление функции и явная маркировка a переменная как массив Testable:

func provider(cb: [Testable] -> ()) {
    let a : [Testable] = [MyObj(), MyObj(), MyObj()]
    cb(a)
}

Тогда нет нужды опускаться, поэтому нет ошибки. Вот весь код:

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()

Предыдущий код выводит:

arr has 3 elements
_TtC5hgfds5MyObj
_TtC5hgfds5MyObj
_TtC5hgfds5MyObj

Кажется, swift не любит циклы for-in для массивов AnyObject или протоколов. Однако, если вы измените его на старомодный цикл for-i, все будет хорошо.

Так что вместо:

for e in arr { // causes EXC_BAD_ACCESS

Просто пиши:

for var i = 0; i < arr.count; ++i { // works fine
    let e = arr[i]
    ...
}
Другие вопросы по тегам