Swift - преобразование массива протоколов для ввода сбоев с фатальной ошибкой unsafeBitCast

Я пытаюсь создать "отфильтрованный" массив типа протокола

У меня есть ряд структур (Assessment, Level и Gate), которые соответствуют нескольким различным протоколам - Stageable, Repeatable и Testable:

protocol Stageable
{
    var index : Int { get }
    var steps : [Step] { get }
}

protocol Testable
{
    var threshold : Float { get }
}

protocol Repeatable
{
    var sessions: Int { get }
}

struct Gate : Stageable, Testable, Repeatable
{
    private(set) var index : Int
    private(set) var steps : [Step]

    private(set) var threshold : Float

    private(set) var sessions : Int
}

struct Assessment : Stageable, Testable
{
    private(set) var index : Int
    private(set) var steps : [Step]

    private(set) var threshold : Float
}

struct Level : Stageable, Repeatable
{
    private(set) var index : Int
    private(set) var steps : [Step]

    private(set) var sessions : Int
}

Шаг - еще одна структура. Классы не используются.

Эти структуры заполняются непосредственно перед добавлением в массив. Массив обычно имеет форму [Оценка, Гейт, Уровень, Уровень]. Все данные структур заполняются из файла XML.

В некоторых случаях я хочу просмотреть только «Уровни» в массиве, поэтому я делаю следующее:

// stages = [Assessment, Gate, Level, Level, ...]
let levels = stages.filter{ $0 is Level }

Если я запрошу это, все будет хорошо, например. Levels.count - это то, что я ожидал. Однако, если я теперь хочу преобразовать массив в [Уровень], он вылетает со следующей ошибкой:

fatal error: can't unsafeBitCast between types of different sizes

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

В настоящее время используется Xcode 7 beta 5.


person barnabus    schedule 01.09.2015    source источник


Ответы (2)


Приведение массивов структур проблематично, поскольку структуры являются типами значений. Это означает, что каждый элемент массива структур занимает размер структуры в памяти. Это отличается от массивов стандартных объектов, потому что они передаются по ссылке. Каждый элемент в массиве объектов является ссылкой (указателем на определенную область памяти).

Чтобы продемонстрировать это, рассмотрим следующие

class ABC {
    private var i = 0
    private var j = 1
    private var k = 2
}

print(sizeof(UIViewController))
print(sizeof(UIImage))
print(sizeof(NSObject))
print(sizeof(ABC))

Каждый из операторов print выводит 8 на моей платформе, который является размером адреса памяти (который, очевидно, отличается от объема памяти, занятого экземплярами этого класса).

С другой стороны, когда я беру код из вашего вопроса и делаю

print(sizeof(Stageable))
print(sizeof(Level))

Я получаю 40 и 24 соответственно, которые являются фактическими размерами экземпляров этих структур в памяти. Это означает, что массив типа [Stageable] состоит из блоков по 40 байтовых элементов, тогда как массив типа [Level] состоит из блоков по 24 байта. В результате вы не можете выполнять преобразование между такими типами массивов, потому что это потребовало бы перезаписи памяти массива.

В качестве альтернативы вы можете использовать метод map для принудительного преобразования типа:

let levels = stages.filter({ $0 is Level }).map({ $0 as! Level })

Вышеупомянутое также можно упростить, используя метод flatMap:

let levels = stages.flatMap({ $0 as? Level })
person hennes    schedule 02.09.2015
comment
Спасибо за это очень ясное объяснение и демонстрацию хорошего варианта использования FlatMap. - person barnabus; 02.09.2015

Что ж, когда вы выполняете этот код:

let levels = stages.filter{ $0 is Level }

ваш levels тип станет [Stageable]. Теперь, чтобы преобразовать [Stageable] в [Level], вы можете использовать этот код:

var l = levels.map{ $0 as! Level }
person pacification    schedule 01.09.2015