Swift - UnsafePointerと上手に付き合う

ああなんてもったいない誤解。

> ということでUnsafePointer<host_basic_info>.alloc()でアンセーフなメモリを確保して、その中に返してもらって、利用後に速やかに破棄するという感じにしかならないようです。 - [SwiftからMachのhost_info()を呼び出す(UnsafePointerのキャスト)](http://qiita.com/rryu/items/3341100675454da672c3)

## Cの構造体はそのままSwiftの構造体として使える

* Cの構造体も、Swiftの構造体と同じ流儀で扱えます
* `UnsafePointer<cstruct>`を要求する関数には、`&cstruct`を渡すだけでおk

実際にご覧いただきましょう。

````swift
import Darwin // もちろんFoundationでもおk
func lastModified(path:String)->String? {
    var st = stat()    // *1
    let err = path.withCString { // *2
        lstat($0, &st) // *3
    }
    if err != 0 {
        path.withCString { perror($0) }
        return nil
    }
    let ct = ctime(&st.st_mtimespec.tv_sec) // *4
    return String.fromCString(ct)
}
````
````swift
println(lastModified("/etc/csh.cshrc"))    // 成功例
println(lastModified("/etc/csh.profile"))  // 失敗例
````

では解説。

1. `st`はCの`struct stat`にゼロ初期化
2. `.withCString`を使うと、引数ブロックの第一引数が`UnsafePointer<CChar>`になるので、C文字列を要求する関数はこの方法で使える
3. `st`の内容を設定
4. 構造体内の構造体もSwiftの流儀でアクセスできる

## 可変長バッファーには、SwiftのArrayが使える

````swift
import Darwin
let linebufsize = 4096
func printFileWithLineNumber(path:String) {
    let fp = path.withCString {
        fopen($0, "r") // *1
    }
    if fp == nil { return }
    var linebuf = [CChar](count:linebufsize, repeatedValue:0) // *2
    var lineCount = 0
    //          *3
    while fgets(&linebuf, Int32(linebufsize-1), fp) { 
        if let line = String.fromCString(&linebuf) {
            print("\(++lineCount):\t" + line)
        }
    }
    fclose(fp)
}
````
````
printFileWithLineNumber("/etc/csh.cshrc")
````

0. リテラルを直接C関数に書くことで、`"r"`が`UnsafePointer<Int8>`型推論される
1. バッファー`linebuf`はこうして確保してから
2. `&linebuf`をC関数に渡せばおk

## 追記: 初期化子cstruct()が未定義の場合

コメント欄で気づいたのですが、`host_basic_info`はゼロ初期化子が未定義なのですね。しかし、そういう場合もこう出来ます。

````swift
func blankof<T>(type:T.Type) -> T {
    var ptr = UnsafeMutablePointer<T>.alloc(sizeof(T))
    var val = ptr.memory
    ptr.destroy()
    return val
}
````

この`blankof(T)`はTがどんな値型でもゼロ初期化できるすぐれもの。`.destroy()`忘れもありえません。

これを使ってrefactorしてみました。

````swift
import Foundation
func getHostBasicInfo()->host_basic_info? {
    /// カーネル用のポートを取得する
    let hostPort = mach_host_self()
    /// ゼロ初期化済みのhost_basic_infoを用意
    var hostBasicInfo = blankof(host_basic_info)
    ///　HOST_BASIC_INFO_COUNTが定義されていないので自分で計算する
    var count = mach_msg_type_number_t(
        sizeof(host_basic_info) / sizeof(integer_t)
    )
    /// host_info()を呼び出して値を取得する
    var err = withUnsafePointer(&hostBasicInfo) {
        host_info(hostPort, HOST_BASIC_INFO, host_info_t($0), &count)
    }
    /// 問題なければhostBasicInfoを、あったらnilを返す
    return err == 0 ? hostBasicInfo : nil
}
````

## 追記: C構造体にだってextensible!

Swiftがイカすのは、importしたCの構造体すら`extension`の対象となること。なのでこうしておけば…

````swift
extension host_basic_info: Printable {
    public var description:String {
        return "\n".join(["host_basic_info(",
            "  mac_cpus:         \(self.max_cpus),",
            "  avail_cpus:       \(self.avail_cpus),",
            "  memory_size:      \(self.memory_size),",
            "  cpu_type:         \(self.cpu_type),",
            "  cpu_subtype:      \(self.cpu_subtype),",
            "  cpu_threadtype:   \(self.cpu_threadtype),",
            "  physical_cpu:     \(self.physical_cpu),",
            "  physical_cpu_max: \(self.physical_cpu_max),",
            "  logical_cpu:      \(self.logical_cpu),",
            "  logical_cpu_max:  \(self.logical_cpu_max),",
            "  max_mem:          \(self.max_mem)",
        ")"])
    }
}
````

あとはこれだけで、中身を確認できます。

````swift
println(getHostBasicInfo())
````

## まとめ

みてのとおり、どちらのコードサンプルにも、`blankof()`を除いて`UnsafePointer<toWhatever>`な型宣言は一つもありません。なのにCの構造体も関数も自在に使いこなせています。

実はこのあたりのことはAppleの[Interacting with C APIs]にもきちんと書かれています。が、コードサンプルが抽象的でいまいちわかりづらかった。上記みたいなサンプルを作ってみると手になじんできます。

[Interacting with C APIs]: https://developer.apple.com/library/prerelease/mac/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithObjective-CAPIs.html

私にとって高レベル側のSwift最大の魅力が[演算子](http://qiita.com/dankogai/items/202dcf26e5160aaf98da)なら、Cインターフェイスは低レベル側最大の魅力です。Perl5のXSでヒャッハーしてたころが懐かしい…

Enjoy!

Dan the Multilingual