Например, без этой второй перегрузки загрузка массива даст «UnsafeRawBufferPointer.load выходит за пределы». Есть ли способ справиться с обоими случаями без перегрузок?
let bytes: [UInt8] = [1, 0, 1, 0]
bytes.load() as Int32 // 0x1_00_01
bytes.load() as [Int16] // [1, 1]
public extension ContiguousBytes {
func load<T>(_: T.Type = T.self) -> T {
withUnsafeBytes { $0.load(as: T.self) }
}
func load<Element>(_: [Element].Type = [Element].self) -> [Element] {
withUnsafeBytes { .init($0.bindMemory(to: Element.self)) }
}
}
Я думаю, что было бы лучше сделать ваши универсальные методы более ограниченными, так как это приведет к неопределенному поведению для других типов. Я уже реализовал что-то похожее для этих методов, но суть в том, чтобы избежать его неправильного использования.
Неважно, я забыл, что вам нравится создавать общие типы, используя одно и то же имя вместо одного символа. Кстати, почему бы не просто func load<Element>() -> [Element] {
?
Аргументы позволяют вам передать метатип, как они это делают, например. Кодируемый. Я не склонен использовать метатипы таким образом, но это не будет API, если мы когда-либо не получим общие свойства.
Да я знаю, что это позволяет передать тип. Я просто не вижу никакого преимущества перед явной установкой результирующего типа или приведением при вызове этого метода, как вы.
Я тоже нет, но явно бытует другое мнение — иначе у нас не было бы ужасного требования использовать метатипы с Codable. Поэтому я всегда пишу эти функции вот так. Возможно, я делаю глупый выбор. ?
Итай Фербер — тот, кто спрашивает об этом
С точки зрения наличия двух перегрузок: вы, к сожалению, не можете их избежать. ContiguousBytes.withUnsafeBytes
дает вам доступ к базовому хранилище типа, который реализует ContiguousBytes
, поэтому буфер, заданный, скажем, [UInt8].withUnsafeBytes
, будет фактическим буфером, который экземпляр массива использует для данных хранить, а не указателем на сам в памяти (например, его длина, емкость, указатель памяти и т. д.).
Причина, по которой ваша первая перегрузка не может работать, заключается в том, что вы будете вызывать эквивалент:
withUnsafeBytes { rawBuffer in
rawBuffer.load(as: [Int16].self)
}
где load(as:)
пытается прочитать [1, 0, 1, 0]
, как если бы это содержимое было длиной, емкостью и указателем буфера одного экземпляра [Int16]
, но это не так — это всего лишь содержаниеbytes
.
Вам нужна вторая перегрузка, чтобы создать новый массив с содержание этого UnsafeRawBufferPointer
.
Что касается фактической реализации вашего варианта массива, начиная с SE-0333 Расширение возможностей использования withMemoryRebound, появившегося в Swift 5.7, правильный способ сделать это - использовать withMemoryRebound(to:_:)
, поскольку безусловное bindMemory(to:)
небезопасно, если буфер уже привязан к другому типу. withMemoryRebound(to:_:)
теперь всегда безопасно, даже если буфер привязан, если вы соответствуете требованиям выравнивания:
/// A raw buffer may represent memory that has been bound to a type.
/// If that is the case, thenT
must be layout compatible with the
/// type to which the memory has been bound. This requirement does not
/// apply if the raw buffer represents memory that has not been bound
/// to any type.
Это будет выглядеть как
func load<Element>(_: [Element].Type = [Element].self) -> [Element] {
withUnsafeBytes { rawBuffer in
rawBuffer.withMemoryRebound(to: Element.self) { typedBuffer in
Array(typedBuffer)
}
}
}
UnsafeRawBufferPointer.withMemoryRebound(to:_:)
выполнит математику привязки необработанных байтов к правому шагу Element
, хотя имейте в виду, что это допустимо для Только, если выравнивание необработанного буфера соответствует требованиям выравнивания Element
— в противном случае вы либо выйдете из строя, либо сработаете. неопределенное поведение.
Так это доступно в Xcode 13.3?
@LeoDabus Должно быть, да.
Однако это не так. ? Я буду следить за этим и отмечу это как ответ, если он в конечном итоге сработает. Между тем, ваш ответ неточен без квадратных скобок из моей подписи — эта перегрузка применима только к метатипам массива. Я также предполагаю, что вы можете использовать Array.init
напрямую вместо создания нового замыкания…?
Действительно, я ошибался — я считаю, что предложение изначально предназначалось для Swift 5.6, но появится в Xcode только в Swift 5.7. Я попробовал онлайн-площадку Swift со Swift 5.6, и у нее действительно было withMemoryRebound
(так что я не совсем это выдумывал! ?), но похоже, что она не попала в сам Xcode 13.3.
@ItaiFerber да, Xcode 13.3 — это Swift 5.6, поэтому нам нужно будет дождаться следующего выпуска.
@Jessy Извините за эту опечатку, обновил ответ, чтобы явно применить метатип Array.
Попробовал ваш код в Swift 5.7, используя набор инструментов для разработки стволов. ??
АФАИК, ты не можешь.