Data.subdata(in:) приводит к EXC_BAD_INSTRUCTION

При попытке получить подданные объекта Data приложение аварийно завершает работу со следующей ошибкой:

Тема 1: EXC_BAD_INSTRUCTION (код = EXC_I386_INVOP, субкод = 0x0)

Ниже вы можете увидеть код. Это расширение Data. Надеюсь, кто-то может объяснить, почему это падает.

public extension Data {
    /// Removes and returns the range of data at the specified position.
    /// - Parameter range: The range to remove. `range` must be valid
    /// for the collection and should not exceed the collection's end index.
    /// - Returns: The removed data.
    mutating func remove(at range: Range<Data.Index>) -> Self {
        precondition(range.lowerBound >= 0, "Range invalid, lower bound cannot be below 0")
        precondition(range.upperBound < self.count, "Range invalid, upper bound exceeds data size")
        
        let removal = subdata(in: range) // <- Error occurs here
        removeSubrange(range)
        return removal
    }
}

РЕДАКТИРОВАТЬ - добавлены функции вызывающего абонента:

Это расширение вызывается из следующей функции:

func temporary(data: inout Data) -> Data {
    let _ = data.removeFirst()
    return data.remove(range: 0 ..< 3)
}

Который в свою очередь называется так:

var data = Data([0,1,2,3,4,5])
let subdata = temporary(data: &data)

person Bram    schedule 11.08.2020    source источник
comment
Data соответствует RangeReplaceableCollection. Почему бы вам не использовать removeSubrange? Кстати, Data.Index — это просто тип, псевдоним Int.   -  person Leo Dabus    schedule 12.08.2020
comment
Обратите внимание, что removeSubrange работает для любого RangeExpression   -  person Leo Dabus    schedule 12.08.2020
comment
removeSubrange не возвращает удаленные данные. Мне нужны удаленные данные, подобные data.removeFirst(), которые возвращают UInt8, которые были удалены. removeSubrange выполняет операцию правильно, но возвращает Void.   -  person Bram    schedule 12.08.2020
comment
Вы хотите, чтобы я показал, как это должно быть реализовано? Я имею в виду общий метод, похожий на removeSubrange, но возвращающий то, что удаляется. Я бы также удалил предварительные условия   -  person Leo Dabus    schedule 12.08.2020
comment
@LeoDabus Да, пожалуйста!   -  person Bram    schedule 12.08.2020
comment
Кстати, ваше предварительное условие неверно   -  person Leo Dabus    schedule 12.08.2020
comment
Почему? Диапазон может быть ниже нуля, в то время как индекс коллекции не может, а верхняя граница никогда не может быть больше или равна количеству байтов в коллекции. Если длина коллекции составляет 100 байт, максимальный индекс равен 99. Я считаю, что мои предварительные условия верны...   -  person Bram    schedule 12.08.2020
comment
попробуйте var data = Data([0,1,2,3,4,5]) let subdata = data.remove(at: 0..<6) выдаст Не удалось выполнить предварительное условие: диапазон недействителен, верхняя граница превышает размер данных Верхняя граница диапазона может быть равна count   -  person Leo Dabus    schedule 12.08.2020
comment
Ах, верно. Итак, count >= 0 правильный, другой должен быть count <= upperBound, конечно!   -  person Bram    schedule 12.08.2020
comment
Нет, должно быть наоборот upperbound <= count   -  person Leo Dabus    schedule 12.08.2020


Ответы (2)


Вы не предоставили достаточно информации, чтобы мы узнали причину сбоя. Одна вещь, которую я знаю неправильно в вашем методе, это ваше предварительное условие. Вы не сможете передать диапазон, чтобы удалить все элементы вашей коллекции. Кроме того, вы должны реализовать общий метод, который будет принимать RangeExpression вместо Range. Вот как я бы реализовал такой метод:

extension Data {
    /// Removes and returns the range of data at the specified position.
    /// - Parameter range: The range to remove. `range` must be valid
    /// for the collection and should not exceed the collection's end index.
    /// - Returns: The removed data.
    mutating func remove<R>(_ range: R) -> Data where R: RangeExpression, Index == R.Bound {
        defer { removeSubrange(range) }
        return subdata(in: range.relative(to: self))
    }
}

Применение:

var data = Data([0,1,2,3,4,5])
let subdata = data.remove(0..<6)
print(Array(data), Array(subdata))  // "[] [0, 1, 2, 3, 4, 5]\n"

Чтобы проверить, содержат ли ваши индексы данных определенный диапазон, прежде чем пытаться удалить, вы можете использовать оператор сопоставления с образцом:

var data = Data([0,1,2,3,4,5])
let range = 0..<7
if data.indices ~= range {
    let subdata = data.remove(range)
    print(Array(data), Array(subdata))
} else {
    print("invalid subrange")  // "invalid subrange\n"
}

Если вы хотите сделать то же самое с ClosedRange, вам нужно будет реализовать свой собственный оператор сопоставления с образцом для Range:

extension Range {
    static func ~=(lhs: Self, rhs: ClosedRange<Bound>) -> Bool {
        lhs.contains(rhs.lowerBound) && lhs.contains(rhs.upperBound)
    }
}

Применение:

var data = Data([0,1,2,3,4,5])
let range = 0...5
if data.indices ~= range {
    let subdata = data.remove(range)
    print(Array(data), Array(subdata))  // "[] [0, 1, 2, 3, 4, 5]\n"
} else {
    print("invalid subrange")
}
person Leo Dabus    schedule 11.08.2020
comment
Это похоже на более раннюю реализацию, которая у меня была. Это все еще дает мне ошибку. У меня есть основания полагать, что это может быть связано с какой-то ленивой загрузкой и/или свойствами, что означает, что некоторые данные еще не представлены на момент выполнения этого кода. Вроде объясняет ошибку неверных инструкций, так как к моменту подключения отладчика память загружена - person Bram; 12.08.2020
comment
Вы всегда должны проверять индексы данных перед удалением любого контента - person Leo Dabus; 12.08.2020
comment
Не могли бы вы подробнее рассказать об проверочных индексах? - person Bram; 12.08.2020
comment
Я обновил свой вопрос с функциями вызова. Ошибка сохраняется. - person Bram; 12.08.2020

Ошибка вызвана функцией removeFirst. В документации четко указано:

Вызов этого метода может сделать недействительными все сохраненные индексы этой коллекции. Не полагайтесь на ранее сохраненное значение индекса после изменения коллекции любой операцией, которая может изменить ее длину.

Похоже, это именно то, что вызывает мою ошибку. Я заменил removeFirst на remove(at:) и теперь все работает.

person Bram    schedule 12.08.2020