왕복 데이터로/데이터에서 빠른 번호 유형
스위프트 3가 기울어진 상태에서Data
에 [UInt8]
저는 데이터 객체로서 다양한 숫자 유형(UINT8, Double, Float, Int64 등)을 인코딩/디코드하는 가장 효율적인 방법이 무엇인지 알아보려고 합니다.
[UINT8]을 사용하는 것에 대한 답이 있는데, Data에서 찾을 수 없는 다양한 포인터 API를 사용하고 있는 것 같습니다.
기본적으로 다음과 같은 사용자 지정 확장 기능을 몇 가지려고 합니다.
let input = 42.13 // implicit Double
let bytes = input.data
let roundtrip = bytes.to(Double) // --> 42.13
제가 정말로 저를 피하는 부분은 제가 어떻게 어떤 기본 구조(모든 숫자)에서 어떤 종류의 포인터(OpaquePointer, BufferPointer 또는 SafeedPointer?)를 얻을 수 있는지입니다.C에서, 저는 그저 그 앞에 앰퍼샌드를 찰싹찰싹 때릴 것입니다. 그러면 그렇지요.
참고: 현재 Swift 5(Xcode 10.2)에 대한 코드가 업데이트되었습니다. (Swift 3 및 Swift 4.2 버전은 편집 내역에서 확인할 수 있습니다.)정렬되지 않은 데이터도 이제 올바르게 처리됩니다.
작성방을 Data
Swift 4.2를 기준으로 데이터는 단순히 다음과 같은 값으로 생성할 수 있습니다.
let value = 42.13
let data = withUnsafeBytes(of: value) { Data($0) }
print(data as NSData) // <713d0ad7 a3104540>
설명:
withUnsafeBytes(of: value)
값의 원시 바이트를 덮는 버퍼 포인터를 사용하여 폐쇄를 호출합니다.- 원시 버퍼 포인터는 바이트 시퀀스이므로 데이터를 생성하는 데 사용할 수 있습니다.
에서 Data
스위프트 5를 기준으로 다음 중 하나.Data
" "untyped"로 합니다.UnsafeMutableRawBufferPointer
바이트 단위로메모리에서 값을 읽는 방법:
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes {
$0.load(as: Double.self)
}
print(value) // 42.13
이 접근 방식에는 한 가지 문제가 있습니다.메모리가 유형에 맞게 정렬되어 있어야 합니다(여기서는 8바이트 주소에 정렬됨).그러나 이는 보장되지 않습니다. 예를 들어 데이터가 다른 데이터의 조각으로 획득된 경우Data
value.value.value.
따라서 바이트를 다음 값으로 복사하는 것이 더 안전합니다.
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
var value = 0.0
let bytesCopied = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
assert(bytesCopied == MemoryLayout.size(ofValue: value))
print(value) // 42.13
설명:
withUnsafeMutableBytes(of:_:)
값의 원시 바이트를 포함하는 가변 버퍼 포인터로 닫힘을 호출합니다.- 방법은
DataProtocol
에)Data
데이터에서 해당 버퍼로 바이트를 복사합니다.
의 반 의 반환값copyBytes()
복사된 바이트 수입니다.대상 버퍼의 크기와 같거나 데이터에 바이트가 충분하지 않으면 더 작습니다.
일반 솔루션 #1
위의변은이일방반쉽법수있다구습의 될 수 .struct Data
:
extension Data {
init<T>(from value: T) {
self = Swift.withUnsafeBytes(of: value) { Data($0) }
}
func to<T>(type: T.Type) -> T? where T: ExpressibleByIntegerLiteral {
var value: T = 0
guard count >= MemoryLayout.size(ofValue: value) else { return nil }
_ = Swift.withUnsafeMutableBytes(of: &value, { copyBytes(to: $0)} )
return value
}
}
조건 약조 T: ExpressibleByIntegerLiteral
는 값을 "0"으로 쉽게 초기화할 수 있도록 여기에 추가되었습니다. 이 방법은 어쨌든 "트리벌"(트리벌 및 부동소수점) 유형과 함께 사용할 수 있기 때문에 실제로는 제한 사항이 아닙니다. 아래를 참조하십시오.
예:
let value = 42.13 // implicit Double
let data = Data(from: value)
print(data as NSData) // <713d0ad7 a3104540>
if let roundtrip = data.to(type: Double.self) {
print(roundtrip) // 42.13
} else {
print("not enough data")
}
마찬가지로 어레이를 다음으로 변환할 수 있습니다.Data
뒤 뒤 및:
extension Data {
init<T>(fromArray values: [T]) {
self = values.withUnsafeBytes { Data($0) }
}
func toArray<T>(type: T.Type) -> [T] where T: ExpressibleByIntegerLiteral {
var array = Array<T>(repeating: 0, count: self.count/MemoryLayout<T>.stride)
_ = array.withUnsafeMutableBytes { copyBytes(to: $0) }
return array
}
}
예:
let value: [Int16] = [1, Int16.max, Int16.min]
let data = Data(fromArray: value)
print(data as NSData) // <0100ff7f 0080>
let roundtrip = data.toArray(type: Int16.self)
print(roundtrip) // [1, 32767, -32768]
일반 솔루션 #2
위의 접근 방식에는 한 가지 단점이 있습니다.실제로는 정수 및 부동 소수점 유형과 같은 "사소한" 유형에서만 작동합니다.다음과 같은 "복잡한" 유형Array
그리고.String
기본 스토리지에 대한 포인터가 있으며(숨김) 구조 자체를 복사하는 것만으로는 전달할 수 없습니다.또한 실제 객체 스토리지에 대한 포인터일 뿐인 참조 유형에서는 작동하지 않습니다.
그래서 그 문제를 해결할 수 있습니다.
로
Data
뒤 뒤 및:protocol DataConvertible { init?(data: Data) var data: Data { get } }
프로토콜 확장에서 변환을 기본 메서드로 구현합니다.
extension DataConvertible where Self: ExpressibleByIntegerLiteral{ init?(data: Data) { var value: Self = 0 guard data.count == MemoryLayout.size(ofValue: value) else { return nil } _ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} ) self = value } var data: Data { return withUnsafeBytes(of: self) { Data($0) } } }
제공된 바이트 수가 유형의 크기와 일치하는지 확인하는 실패한 이니셜라이저를 선택했습니다.
그리고 마지막으로 안전하게 변환할 수 있는 모든 유형의 적합성을 선언합니다.
Data
뒤 뒤 및:extension Int : DataConvertible { } extension Float : DataConvertible { } extension Double : DataConvertible { } // add more types here ...
이는 변환을 더욱 우아하게 만듭니다.
let value = 42.13
let data = value.data
print(data as NSData) // <713d0ad7 a3104540>
if let roundtrip = Double(data: data) {
print(roundtrip) // 42.13
}
두 번째 접근 방식의 장점은 안전하지 않은 변환을 실수로 수행할 수 없다는 것입니다.단점은 모든 "안전한" 유형을 명시적으로 나열해야 한다는 것입니다.
또한 다음과 같은 단순하지 않은 변환이 필요한 다른 유형에 대해 프로토콜을 구현할 수 있습니다.
extension String: DataConvertible {
init?(data: Data) {
self.init(data: data, encoding: .utf8)
}
var data: Data {
// Note: a conversion to UTF-8 cannot fail.
return Data(self.utf8)
}
}
또는 값을 직렬화 및 역직렬화하는 데 필요한 모든 작업을 수행하기 위해 사용자 유형의 변환 방법을 구현합니다.
바이트 순서
위의 방법으로는 바이트 순서 변환이 수행되지 않으며, 데이터는 항상 호스트 바이트 순서로 유지됩니다.플랫폼 독립적 표현(예: "big endian" 또는 "network" 바이트 순서)의 경우 해당 정수 속성 resp를 사용합니다.이니셜라이저예:
let value = 1000
let data = value.bigEndian.data
print(data as NSData) // <00000000 000003e8>
if let roundtrip = Int(data: data) {
print(Int(bigEndian: roundtrip)) // 1000
}
물론 이러한 변환은 일반적인 변환 방법으로도 수행할 수 있습니다.
다음을 사용하여 안전하지 않은 포인터를 변경할 수 있습니다.
withUnsafePointer(&input) { /* $0 is your pointer */ }
인아웃 연산자는 가변 객체에서만 작동하기 때문에 불변 객체에 대해 하나를 얻을 수 있는 방법을 모르겠습니다.
이는 연결한 답변에서 확인할 수 있습니다.
저의 경우 마틴 R의 답변이 도움이 되었지만 결과는 뒤집혔습니다.그래서 코드를 조금 바꿨습니다.
extension UInt16 : DataConvertible {
init?(data: Data) {
guard data.count == MemoryLayout<UInt16>.size else {
return nil
}
self = data.withUnsafeBytes { $0.pointee }
}
var data: Data {
var value = CFSwapInt16HostToBig(self)//Acho que o padrao do IOS 'e LittleEndian, pois os bytes estavao ao contrario
return Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
}
}
그 문제는 리틀 엔디안과 빅 엔디안과 관련이 있습니다.
언급URL : https://stackoverflow.com/questions/38023838/round-trip-swift-number-types-to-from-data
'programing' 카테고리의 다른 글
잭슨이 스프링 내에서 알 수 없는 속성을 무시하도록 글로벌하게 설정하는 방법은 무엇입니까? (0) | 2023.08.02 |
---|---|
최대 절전 모드에서 문자열을 DB 시퀀스에 매핑하는 방법 (0) | 2023.08.02 |
일치하는 명령 dotnet-project model-server를 찾을 수 있는 실행 파일이 없습니다. (0) | 2023.08.02 |
볼륨에 단일 파일을 마운트하는 방법 (0) | 2023.08.02 |
install wordpress with cliin docker에서 mariadb 컨테이너와의 "데이터베이스 연결 설정 오류"가 발생합니다. (0) | 2023.08.02 |