LoginSignup
21
21

More than 5 years have passed since last update.

swiftからC/ObjCを触る (例: String -> SHA256)

Posted at

 Swiftを単体で弄るだけでは面白くありません。ご先祖様が残してくださったC/ObjC資産をどんどん使っていきましょう。
 ブリッジングヘッダーについて調べ始めたあたりの人を念頭に置いた記事です。参考になる記事の纏めも兼ねています。

C => ObjC => Swift

 Swiftは書いていて愉しい言語ですが、Swiftで書かれたソースはまだまだ多くありません。しかも名前がややこしいため、簡単な処理であっても調べるのが大変です。「swift やりたいこと」で検索したらCDアルバムやクルマ、謎の組織あるいは並列処理が得意な方のswiftが出てきてイラつく経験は誰しも通る道でしょう。
 そういう時には、諦めることなくC/ObjCのコードを検索してみましょう。C/ObjCで書けることはほとんどそのままSwiftで書けますし、C/ObjCで書かれたライブラリーはswiftから容易に利用できるからです。

CommonCrypto

 タイトルにもあるように、この記事では文字列からSHA256を得ることを例に挙げます。幸いなことに、この問題については「Swift SHA256」と検索することで参考になる記事をいくつも発見できます。
 StringからSHA256その他のハッシュ値を得るには、CommonCrypto の持つ関数を使えば簡単です。これはSwiftの一部ではなく、オープンソースのC言語製ライブラリーです。

 ここでいきなり「アルゴリズムが分かっているのだから、swiftらしさ全開で書き直すべきだ」という人は多分いないでしょう。自在にC/ObjC資産を使いまわすほうが、遥かに「Swiftらしい」に違いありません。

 ということで、まずはブリッジングヘッダーを書きましょう。そもそも作り方が分からんという方は参考記事をご覧ください。

Bridging-Header.h
#import <CommonCrypto/CommonDigest.h>

 これで準備が整います。簡単ですねー。

 ただし、Swiftで愉しく楽して簡単に使えるのはC/ObjCのヘッダーだけです。C++を使おうとすると結構な手間がかかります。

 ちなみに、自分で用意したライブラリー (homebrewでインストールしたライブラリーとか) を使う場合には、ライブラリーをリンクしてヘッダーをインクルードするだけでなく、Xcodeにおいて Header Search Paths を通さなければいけません。インクルードすべきファイルがどこにあるか、知らせる必要があるからです。
 個人的には /usr/local/include をよく書き足します。

Objective-C without the baggage of C

f32262.png

 それってsmalltalk

 フェデリギお兄さん曰く、ObjCからCを取り外すとSwiftなんだそうです。ということで、Swiftにはポインタとかchar配列とかいう古代C言語は出てこない… ように見えます。
 しかし、それはピュアなSwiftの世界に出てこないだけの話であって、「出せない」わけではないのです。

extension String {
    var md5: String {
        let str = self.cStringUsingEncoding(NSUTF8StringEncoding)
        let strLen = CC_LONG(self.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
        let digestLen = Int(CC_MD5_DIGEST_LENGTH)
        let result = UnsafeMutablePointer<CUnsignedChar>.alloc(digestLen)

        CC_MD5(str!, strLen, result)

        var hash = NSMutableString()
        for i in 0..<digestLen {
            hash.appendFormat("%02x", result[i])
        }

        result.dealloc(digestLen)

        return String(format: hash as String)
    }
}
// 注:一部修正有

 君なんか写真と違わない?
 見るからにC言語っぽいですね。

 Swiftには、正常に動くC言語のロジックをほとんどそのまま持ち込むために必要な道具が揃っています。使えるものを再利用するわけですから、極めて生産的です。「なんかCっぽい」書き方もSwiftらしさと考えるべきでしょう。
 ただ、上の例はちょっと幾ら何でもC言語の風味が強すぎるような気がします。str! とアンラップしていることが気になりますし、メソッドなので使うときに () を付ける必要があるのも面倒です。

 ということで、書き直しましょう。いかにもSwiftっぽく。

extension String {
    var sha256: String! {
        if let cstr = self.cStringUsingEncoding(NSUTF8StringEncoding) {
            var chars = [UInt8](count: Int(CC_SHA256_DIGEST_LENGTH), repeatedValue: 0)
            CC_SHA256(
                cstr,
                CC_LONG(self.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)),
                &chars
            )
            return chars.map { String(format: "%02X", $0) }.reduce("", combine: +)
        }
        return nil
    }
}

// 使い方
println("Swift".sha256)
// -> AE8ED2743925477EAFAD0DFDB0D0832702634DA4DC6EC1AEFA4F3A61953E93F0

 オプショナル型のアンラップはif letで回避し、ポインターはArrayで代替し、forは使わずmapで済ませることができます。
 記憶力の貧弱な現代人としては、ポインターを直接触ったり、メモリ管理に手を出すのはなるべく避けたいところです。

 なお、ここでの戻り値はString!型にしています。これは真っ当な理由があるわけではなく、cStringUsingEncoding(NSUTF8StringEncoding)がどういう場合にnilを返すのか分からないので「たぶんnilが返ってくることはないだろう」と決めつけただけです。
 「絶対にnilが返ることはない」と断定できるなら、if letを使わずアンラップする方が単純なのですが… どうなんでしょうか。

 以上です。Swift/ObjC/Cの連携は記事も少なめですが、触れば案外なんとかなるので頑張ってください。

おまけ

 最初はif letのかわりにmapを使って書いたんですが、やたら見た目が分かりにくいので止めました。

extension String {
    var sha256: String! {
        return self.cStringUsingEncoding(NSUTF8StringEncoding).map { cstr in
            var chars = [UInt8](count: Int(CC_SHA256_DIGEST_LENGTH), repeatedValue: 0)
            CC_SHA256(
                cstr,
                CC_LONG(self.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)),
                &chars
            )
            return chars.map { String(format: "%02X", $0) }.reduce("", combine: +)
        }
    }
}
21
21
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
21
21