背景
Int や Float などの数値情報をファイルなどに書き出す時は、テキストとして書き出す方法もありますが、そのままバイト列を書き出したり読み込んだりする場合もあります。
後者の場合、幅広い種々の環境で読み込もうとすると、エンディアンの問題に遭遇したりします。エンディアン自体の問題には深入りしませんが、汎用性の高いデータとして保存したい場合は、やっぱりここは押さえておきたいところです。
Int16
, Int32
, Int64
などは、Foundation に CFSwapIntXXBigToHost()
とか CFSwapInt32HostToBig()
とか用意されていて、保存時のエンディアンさえ決まっていれば、保存直前にさっと通してあげれば、対応できます。
一方、Float
では CFSwapFloatBigToHost()
、CFSwapFloatHostToBig()
なんてのは用意されていなくて、CFConvertFloat32HostToSwapped()
とか CFConvertFloat32SwappedToHost()
とかを使い分けなくてはいけません。しかも、CFSwappedFloat32 の中身を見ると、UInt32
が入っていたりして、ちょっとがっかりしますが、まぁ、Float
やDouble
の場合はバイト列を入れ替えた値を戻り値を、またFloat
やらDouble
として戻り値にしていたらそれはそれで問題でしょうが。
func CFConvertFloat32HostToSwapped(arg: Float32) -> CFSwappedFloat32
func CFConvertFloat32SwappedToHost(arg: CFSwappedFloat32) -> Float32
といわけで、これらを使い分けるに為には、ホストを判定する必要があります。そのな時は、CFByteOrderGetCurrent()
を使います。普通に書くと以下のようになりそうなもんですが…
switch CFByteOrderGetCurrent() {
case CFByteOrderLittleEndian: // code
case CFByteOrderBigEndian: // code
}
ところが Expression pattern type '__CFByteOrder' cannot match values of type 'CFByteOrder' (aka 'Int')
と怒られてしまいます。で定義を調べてみたらこうでした。
public struct __CFByteOrder : RawRepresentable, Equatable {
public init(_ rawValue: UInt32)
public init(rawValue: UInt32)
public var rawValue: UInt32
}
public var CFByteOrderUnknown: __CFByteOrder { get }
public var CFByteOrderLittleEndian: __CFByteOrder { get }
public var CFByteOrderBigEndian: __CFByteOrder { get }
public typealias CFByteOrder = CFIndex
public func CFByteOrderGetCurrent() -> CFByteOrder
あ〜 CFByteOrder
は CFIndex
なのに、 __CFByteOrder
の rawValue
は UInt32
でした。Playground で確認してみてもその通り。Apple さんお願いしますよぉ。
CFByteOrderGetCurrent().dynamicType // "Int.Type"
CFByteOrderLittleEndian.rawValue.dynamicType // "UInt32.Type"
という訳でエラーが出ないように書くには以下の通り
switch UInt32(CFByteOrderGetCurrent()) {
case CFByteOrderLittleEndian.rawValue: // little endian
case CFByteOrderBigEndian.rawValue: // big endian
}
ちなみに、NSFileHandle
を使って読み書きをするなら、こんなコードが便利かもしれません。CGRect などは ちゃんと分解して保存するか、extension のメソッドを増やして対応しましょう。
extension NSFileHandle {
private func read<T>() -> T? {
let data = self.readDataOfLength(sizeof(T))
if data.length == sizeof(T) {
let value = UnsafePointer<T>(data.bytes)
return value.memory
}
return nil
}
private func write<T>(value: T) {
var value = value
let data = NSData(bytes: &value, length: sizeof(T))
self.writeData(data)
}
// 他省略
// float and double
func readFloat() -> Float? {
switch UInt32(CFByteOrderGetCurrent()) {
case CFByteOrderLittleEndian.rawValue:
if let value = self.read() as CFSwappedFloat32? {
return CFConvertFloat32SwappedToHost(value)
}
case CFByteOrderBigEndian.rawValue:
return self.read() as Float?
default: fatalError("Unknown Endian")
}
return nil
}
func writeFloat(value: Float) {
switch UInt32(CFByteOrderGetCurrent()) {
case CFByteOrderLittleEndian.rawValue:
self.write(CFConvertFloat32HostToSwapped(value))
case CFByteOrderBigEndian.rawValue:
self.write(value)
default: fatalError("Unknown Endian")
}
}
func readDouble() -> Double? {
switch UInt32(CFByteOrderGetCurrent()) {
case CFByteOrderLittleEndian.rawValue:
if let value = self.read() as CFSwappedFloat64? {
return CFConvertFloat64SwappedToHost(value)
}
case CFByteOrderBigEndian.rawValue:
return self.read() as Float64?
default: fatalError("Unknown Endian")
}
return nil
}
func writeDouble(value: Double) {
switch UInt32(CFByteOrderGetCurrent()) {
case CFByteOrderLittleEndian.rawValue:
self.write(CFConvertFloat64HostToSwapped(value))
case CFByteOrderBigEndian.rawValue:
self.write(value)
default: fatalError("Unknown Endian")
}
}
}
NSFileHandle extension の全コードはこちら。
https://gist.github.com/codelynx/932150fd13f0317df264
あとがき
ちなみに CFByteOrderUnknown
てのもあるみたいだけど、まいいか。あと、仮にハマッたとしても、5分で解決できそうな問題を結構時間かけて記事にするのはどうなのかなと思ったりします。
swift --version
Apple Swift version 2.1.1 (swiftlang-700.1.101.15 clang-700.1.81)
Target: x86_64-apple-darwin15.3.0