Swift で BigEndian LittleEndian の判定でハマってしまったのでメモ

# 背景

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()` を使います。普通に書くと以下のようになりそうなもんですが…

```.swift
	switch CFByteOrderGetCurrent() {
	case CFByteOrderLittleEndian: // code
	case CFByteOrderBigEndian: // code
	}
```

ところが `Expression pattern type '__CFByteOrder' cannot match values of type 'CFByteOrder' (aka 'Int')` と怒られてしまいます。で定義を調べてみたらこうでした。

```.swift
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"
```

という訳でエラーが出ないように書くには以下の通り

```.swift
	switch UInt32(CFByteOrderGetCurrent()) {
	case CFByteOrderLittleEndian.rawValue: // little endian
	case CFByteOrderBigEndian.rawValue: // big endian
	}
```

ちなみに、`NSFileHandle` を使って読み書きをするなら、こんなコードが便利かもしれません。CGRect などは ちゃんと分解して保存するか、extension のメソッドを増やして対応しましょう。

```.swift
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` てのもあるみたいだけど、まいいか。あと、仮にハマッたとしても、５分で解決できそうな問題を結構時間かけて記事にするのはどうなのかなと思ったりします。

```.console
swift --version
Apple Swift version 2.1.1 (swiftlang-700.1.101.15 clang-700.1.81)
Target: x86_64-apple-darwin15.3.0
```