Data <-> MutableRandomAccessSlice

Я действительно борюсь с тем, что someData[start...stop] возвращает MutableRandomAccessSlice, мой someData был let для начала, так зачем мне мутабельная вещь? Почему бы мне не получить только RandomAccessSlice, Что действительно расстраивает, так это то, что он возвращает то, что API довольно несовместимо с исходным кодом. С Data, Я могу использовать .withUnsafeBytesно не так с этим потомством. И то, как вы превращаете Срез обратно в Данные, тоже не ясно. Там нет инициала, который принимает один из них.

Я мог бы использовать subdata(in:) метод вместо подписки, но тогда какой смысл в индексе, если я только когда-либо захочу, чтобы представление подколлекции имело вид оригинальной коллекции. Кроме того, subdata Метод может делать только открытые поддиапазоны, поэтому индекс может делать как закрытые, так и открытые. Это что-то, что они еще не дошли до финала Swift3?

2 ответа

Решение

Помните, что MutableRandomAccessSlice Вы получаете обратно тип значения, а не ссылочный тип. Это просто означает, что вы можете изменить его, если хотите, но это не имеет ничего общего с тем, из чего вы его вырезали:

let x = Data(bytes: [1,2,3]) // <010203>
var y = x[0...1]
y[0] = 2
x // <010203>

Если вы посмотрите в коде, вы заметите, что намерением является возвращение пользовательского типа среза:

public subscript(bounds: Range<Index>) -> MutableRandomAccessSlice<Data> {
    get {
        return MutableRandomAccessSlice(base: self, bounds: bounds)
    }
    set {
        // Ideally this would be:
        //   replaceBytes(in: bounds, with: newValue._base)
        // but we do not have access to _base due to 'internal' protection
        // TODO: Use a custom Slice type so we have access to the underlying data
        let arrayOfBytes = newValue.map { $0 }
        arrayOfBytes.withUnsafeBufferPointer {
            let otherData = Data(buffer: $0)
            replaceBytes(in: bounds, with: otherData)
        }
    }
}

Тем не менее, пользовательский фрагмент все равно не будет приемлем для функции, которая принимает данные. Это согласуется с другими типами, такими как Array, который срезает ArraySlice, который нельзя передать там, где ожидается Array. Это сделано по замыслу (и, вероятно, для данных также по тем же причинам). Проблема заключается в том, что срез "закрепляет" всю память, которая его поддерживает. Так что, если вы взяли 3-байтовый фрагмент из мегабайта данных и сохранили его в иваре, весь мегабайт должен находиться рядом. Теория (в соответствии с разработчиками Swift, с которыми я говорил) состоит в том, что массивы могут быть массивными, поэтому вы должны быть осторожны с их нарезкой, в то время как строки обычно намного меньше, так что строка может быть разрезана до строки.

По моему опыту, вы, как правило, хотите subdata(in:), Мои эксперименты с ним заключаются в том, что по скорости он очень похож на нарезку, поэтому я считаю, что он все еще копируется при записи (но, похоже, он не закрепляет память и в моих первоначальных тестах). Пока что я тестировал только на Mac. Вполне возможно, что на устройствах iOS есть более существенные различия в производительности.

Основываясь на комментариях Роба, я просто добавил следующее расширение Python:

extension Data {
    subscript(start:Int?, stop:Int?) -> Data {
        var front = 0
        if let start = start {
            front = start < 0 ? Swift.max(self.count + start, 0) : Swift.min(start, self.count)
        }
        var back = self.count
        if let stop  = stop {
            back = stop < 0 ? Swift.max(self.count + stop, 0) : Swift.min(stop, self.count)
        }
        if front >= back {
            return Data()
        }
        let range = Range(front..<back)
        return self.subdata(in: range)
    }
}

Таким образом, я могу просто сделать

let input = Data(bytes: [0x60, 0x0D, 0xF0, 0x0D])
input[nil, nil] // <600df00d>
input[1, 3]     // <0df0>
input[-2, nil]  // <f00d>
input[nil, -2]  // <600d>
Другие вопросы по тегам