참고 : 코드는 현재 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>
설명:
값을 검색하는 방법 Data
Swift 5에서 withUnsafeBytes(_:)
of Data
는 UnsafeMutableRawBufferPointer
바이트에 "untyped" 로 클로저를 호출합니다 . load(fromByteOffset:as:)
상기 방법은 상기 메모리로부터 값을 읽어
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
값 의 조각으로 획득 된 경우와 같이 보장되지는 않습니다 .
따라서 바이트를 값 에 복사 하는 것이 더 안전 합니다.
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
설명:
의 반환 값은 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"으로 쉽게 초기화 할 수 있습니다.이 메서드는 어쨌든 "trival"(정수 및 부동 소수점) 유형과 함께 사용할 수 있기 때문에 실제로 제한이 아닙니다. 아래를 참조하십시오.
예:
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)
}
}
또는 값을 직렬화 및 역 직렬화하기 위해 필요한 모든 작업을 수행하기 위해 자신의 유형으로 변환 메서드를 구현합니다.
바이트 순서
위의 방법에서는 바이트 순서 변환이 수행되지 않으며 데이터는 항상 호스트 바이트 순서입니다. 플랫폼 독립적 표현 (예 : "빅 엔디안"일명 "네트워크"바이트 순서)의 경우 해당 정수 속성 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
}
물론이 변환은 일반적인 변환 방법으로도 일반적으로 수행 할 수 있습니다.