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]
...
}