LoginSignup
10
11

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-02-18

背景

Int や Float などの数値情報をファイルなどに書き出す時は、テキストとして書き出す方法もありますが、そのままバイト列を書き出したり読み込んだりする場合もあります。

後者の場合、幅広い種々の環境で読み込もうとすると、エンディアンの問題に遭遇したりします。エンディアン自体の問題には深入りしませんが、汎用性の高いデータとして保存したい場合は、やっぱりここは押さえておきたいところです。

Int16, Int32, Int64 などは、Foundation に CFSwapIntXXBigToHost() とか CFSwapInt32HostToBig() とか用意されていて、保存時のエンディアンさえ決まっていれば、保存直前にさっと通してあげれば、対応できます。

一方、Float では CFSwapFloatBigToHost()CFSwapFloatHostToBig() なんてのは用意されていなくて、CFConvertFloat32HostToSwapped() とか CFConvertFloat32SwappedToHost() とかを使い分けなくてはいけません。しかも、CFSwappedFloat32 の中身を見ると、UInt32 が入っていたりして、ちょっとがっかりしますが、まぁ、FloatDoubleの場合はバイト列を入れ替えた値を戻り値を、また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

あ〜 CFByteOrderCFIndex なのに、 __CFByteOrderrawValueUInt32 でした。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
10
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
11