Com o Swift 3 inclinado em Data
vez de [UInt8]
, estou tentando descobrir qual é a maneira mais eficiente / idiomática de codificar / decodificar swifts em vários tipos de números (UInt8, Double, Float, Int64, etc) como objetos de dados.
Existe esta resposta para usar [UInt8] , mas parece estar usando várias APIs de ponteiro que não consigo encontrar em Data.
Eu gostaria de basicamente algumas extensões personalizadas que se parecem com:
let input = 42.13 // implicit Double
let bytes = input.data
let roundtrip = bytes.to(Double) // --> 42.13
A parte que realmente me escapa, eu olhei em vários documentos, é como posso obter algum tipo de ponteiro (OpaquePointer ou BufferPointer ou UnsafePointer?) De qualquer estrutura básica (que são todos os números). Em C, eu apenas colocaria um "e" comercial na frente dele, e pronto.
swift
swift3
swift-data
Travis Griggs
fonte
fonte
Respostas:
Nota: O código foi atualizado para Swift 5 (Xcode 10.2) agora. (As versões Swift 3 e Swift 4.2 podem ser encontradas no histórico de edição.) Além disso, dados possivelmente não alinhados agora são tratados corretamente.
Como criar a
Data
partir de um valorA partir do Swift 4.2, os dados podem ser criados a partir de um valor simplesmente com
Explicação:
withUnsafeBytes(of: value)
invoca o encerramento com um ponteiro de buffer cobrindo os bytes brutos do valor.Data($0)
pode ser usado para criar os dados.Como recuperar um valor de
Data
A partir do Swift 5, o
withUnsafeBytes(_:)
deData
invoca o encerramento com um "não digitado"UnsafeMutableRawBufferPointer
para os bytes. Oload(fromByteOffset:as:)
método que lê o valor da memória:Há um problema com essa abordagem: ela requer que a memória seja alinhada por propriedades ao tipo (aqui: alinhada a um endereço de 8 bytes). Mas isso não é garantido, por exemplo, se os dados foram obtidos como uma fatia de outro
Data
valor.Portanto, é mais seguro copiar os bytes para o valor:
Explicação:
withUnsafeMutableBytes(of:_:)
invoca o encerramento com um ponteiro de buffer mutável cobrindo os bytes brutos do valor.copyBytes(to:)
método deDataProtocol
(com o qualData
está em conformidade) copia bytes dos dados para esse buffer.O valor de retorno de
copyBytes()
é o número de bytes copiados. É igual ao tamanho do buffer de destino ou menos se os dados não contiverem bytes suficientes.Solução genérica # 1
As conversões acima agora podem ser facilmente implementadas como métodos genéricos de
struct Data
:A restrição
T: ExpressibleByIntegerLiteral
é adicionada aqui para que possamos inicializar facilmente o valor como “zero” - isso não é realmente uma restrição porque este método pode ser usado com tipos “trival” (inteiro e ponto flutuante) de qualquer maneira, veja abaixo.Exemplo:
Da mesma forma, você pode converter matrizes para
Data
e de volta:Exemplo:
Solução genérica # 2
A abordagem acima tem uma desvantagem: na verdade, funciona apenas com tipos "triviais", como inteiros e tipos de ponto flutuante. Os tipos "complexos" gostam
Array
eString
têm ponteiros (ocultos) para o armazenamento subjacente e não podem ser transmitidos apenas copiando a própria estrutura. Também não funcionaria com tipos de referência que são apenas ponteiros para o armazenamento de objeto real.Então resolva esse problema, pode-se
Defina um protocolo que define os métodos de conversão para
Data
e de volta:Implemente as conversões como métodos padrão em uma extensão de protocolo:
Escolhi um inicializador failable aqui, que verifica se o número de bytes fornecidos corresponde ao tamanho do tipo.
E, finalmente, declara conformidade com todos os tipos que podem ser convertidos com segurança para
Data
e de volta:Isso torna a conversão ainda mais elegante:
A vantagem da segunda abordagem é que você não pode fazer conversões não seguras inadvertidamente. A desvantagem é que você deve listar todos os tipos "seguros" explicitamente.
Você também pode implementar o protocolo para outros tipos que exigem uma conversão não trivial, como:
ou implemente os métodos de conversão em seus próprios tipos para fazer o que for necessário para serializar e desserializar um valor.
Ordem de bytes
Nenhuma conversão de ordem de byte é feita nos métodos acima, os dados estão sempre na ordem de byte do host. Para uma representação independente de plataforma (por exemplo, “big endian” ou “rede” ordem de bytes), use as propriedades de inteiros correspondentes resp. inicializadores. Por exemplo:
É claro que essa conversão também pode ser feita de maneira geral, no método de conversão genérico.
fonte
var
cópia do valor inicial significa que estamos copiando os bytes duas vezes? No meu caso de uso atual, estou transformando-os em estruturas de dados, para que possa transformá-los emappend
um fluxo crescente de bytes. Em C direto, isso é tão fácil quanto*(cPointer + offset) = originalValue
. Portanto, os bytes são copiados apenas uma vez.ptr: UnsafeMutablePointer<UInt8>
, poderá atribuir à memória referenciada por meio de algoUnsafeMutablePointer<T>(ptr + offset).pointee = value
que corresponda de perto ao seu código Swift. Existe um problema potencial: alguns processadores permitem apenas acesso alinhado à memória, por exemplo, você não pode armazenar um Int em um local de memória estranho. Não sei se isso se aplica aos processadores Intel e ARM usados atualmente.extension Array: DataConvertible where Element: DataConvertible
. Isso não é possível no Swift 3, mas planejado para o Swift 4 (até onde eu sei). Compare as "condicionalidades" em github.com/apple/swift/blob/master/docs/…Int.self
comoInt.Type
?Você pode obter um ponteiro não seguro para objetos mutáveis usando
withUnsafePointer
:Não sei como obter um para objetos imutáveis, porque o operador inout só funciona com objetos mutáveis.
Isso é demonstrado na resposta que você vinculou.
fonte
No meu caso, a resposta de Martin R ajudou, mas o resultado foi invertido. Então, fiz uma pequena mudança em seu código:
O problema está relacionado com LittleEndian e BigEndian.
fonte