Массив типизированных протоколов не может быть опущен к массиву конкретного типа

protocol P : class {
    var value:Int {get}
}

class X : P {
    var value = 0

    init(_ value:Int) {
        self.value = value
    }
}

var ps:[P] = [X(1), X(2)]
for p in ps {
    if let x = p as? X {   // works for a single variable
        ...
    }
}

if let xs = ps as? [X] {   // doesn't work for an array (EXC_BAD_ACCESS)
    ...
}

Если P - это класс вместо протокола, то код работает правильно. Какая разница между классом и протоколом? Они оба реализованы как указатели в куче, не так ли? Вышеприведенный код может быть скомпилирован успешно, но сбой во время выполнения. Что означает эта ошибка EXC_BAD_ACCESS?


Благодаря @Antonio, но я до сих пор не понимаю, как работает этот пример кода.

let someObjects: [AnyObject] = [
    Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"),
    Movie(name: "Moon", director: "Duncan Jones"),
    Movie(name: "Alien", director: "Ridley Scott")
]
for movie in someObjects as [Movie] {
    println("Movie: '\(movie.name)', dir. \(movie.director)")
}

Является ли AnyObject специальным случаем?

ссылка: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/TypeCasting.html#//apple_ref/doc/uid/TP40014097-CH22-XID_498


protocol P {

}

@objc class X : P {

}

@objc class Y : X {

}

var xs:[X] = [Y(), Y()]
var ps:[P] = [Y(), Y()]


xs as? [Y]  // works
ps as? [Y]  // EXC_BAD_ACCESS

Я пробовал этот код на детской площадке. Поскольку это чистый быстрый код, я думаю, что это не имеет ничего общего с @objc.

Ответы

Ответ 1

Игнорирование необязательной привязки на мгновение и использование прямого назначения:

let x = ps as [X]

сообщается о следующей ошибке выполнения:

fatal error: array element cannot be bridged to Objective-C

Это означает, что downcast из массива протоколов в массив усыновителей требует привязки obj-c. Это можно легко решить, объявив протокол совместимым с objc:

@objc protocol P : class {
    var value:Int {get}
}

С этим простым изменением код теперь работает и исключается исключение времени выполнения.

Теперь, как это решено, но оставляя проблему открытой. У меня пока нет ответа, но я попытаюсь углубиться в это.

Добавление: укажите "почему"

Я потратил некоторое время на изучение этой проблемы, и следующее - это то, с чем я пришел.

У нас есть протокол и класс, принимающие его:

protocol P {}
class X : P {}

Создаем массив из P:

var array = [P]()

Преобразование пустого массива в [X] работает:

array as [X] // 0 elements

Если мы добавим элемент в массив, произойдет ошибка времени выполнения:

array.append(X())
array as [X] // Execution was interrupted, reason: ...

Выход на консоль говорит, что:

fatal error: array element cannot be bridged to Objective-C

Поэтому для приведения массива объектов протокола к массиву его усыновителя требуется перемычка. Это оправдывает, почему @objc исправляет проблему:

@objc protocol P {}
class X : P {}

var array = [P]()
array.append(X())
array as [X] // [X]

Просеивая документацию, я выяснил причину этого.

Чтобы выполнить трансляцию, среда выполнения должна проверить, соответствует ли X протоколу P. В documentation четко указано, что:

Вы можете проверить соответствие протокола только в том случае, если ваш протокол отмечен атрибутом @objc

Чтобы убедиться, что (не то, что я не доверяю документации), я использовал этот код на игровой площадке:

protocol P {}
class X : P {}

let x = X()
let y = x is P

но я получаю другую ошибку, заявив, что:

Playground execution failed: <EXPR>:18:11: error: 'is' test is always true 
let y = x is P

Написав это в "обычный" проект, мы получим ожидаемое:

protocol P {}
class X {}

func test() {
    let x = X()
    let y = x is P
}

Cannot downcast from 'X' to [email protected] protocol type 'P'

Заключение: для того, чтобы массив с типизированным протоколом был опущен к конкретному типу, протокол должен быть помечен атрибутом @objc. Причина в том, что среда выполнения использует оператор is для проверки соответствия протокола, который в соответствии с документацией доступен только для мостовых протоколов.