読者です 読者をやめる 読者になる 読者になる

Septeni Engineer's Blog

セプテーニエンジニアが綴る技術ブログ

Objective-Cでzlibを使った圧縮

konoです。実に一ヶ月以上投稿してなく、弊社のブログ管理者から圧迫かけられたので、またちょっとした小ネタを。

忙しくて予定していた全文検索ネタは後回しです。

そちらはGW中にでもまとめようかと。

さて、今回は題名の通りObjective-CAPICocoa使ってます)での圧縮です。

素直にZipArchiveとか使えよって話ですが、色々と理由があって、サードパーティのライブラリを使わず自分でデータを圧縮する必要があり、Cocoa付属のzlibを使って圧縮を行ってみました。


マニアックなネタなんであんまり役に立たないかもしれませんが。

ちなみに今後、私はマニアックなネタ路線で行くつもりなので。

と言うわけで、以下コードの抜粋になります。

CompressUtil.h

#import <zlib.h>

@interface Compress : NSObject {
}

+ (NSData *) cmpDeflate:(NSData *) srcData;

@end


CompressUtil.m

#import "CompressUtil.h"

@implementation

+ (NSData *) cmpDeflate:(NSData *) srcData {

    // 構造体の定義
    z_stream zlibStrmStruct;

    // zalloc,zfree,opaqueにZ_NULLをセット
    zlibStrmStruct.zalloc = Z_NULL;
    zlibStrmStruct.zfree = Z_NULL;
    zlibStrmStruct.opaque = Z_NULL;

    // 圧縮アルゴリズムの初期化
    zlibStrmStruct.total_out = 0; //
    zlibStrmStruct.next_in = (Bytef *)[srcData bytes];
    zlibStrmStruct.avail_in = [srcData length];

    // 初期化
    int initError = deflateInit2(&zlibStrmStruct, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);

    if(initError != Z_OK) {
        // エラー処理
        return nil;
    }

    // アウトプット用メモリバッファ
    // (zlibのドキュメントによるとバッファサイズはavail_inより0.1%多く、さらに12バイト足したサイズが必要?)
    NSMutableData *cmpData = [NSMutableData dataWithLength:[srcData length] * 1.01 + 12];

    // 結果格納用
    int deflateStatus;

    // 全てのデータが圧縮されるまで繰り返し
    do {
        zlibStrmStruct.next_out = [cmpData mutablebytes] + zlibStrmStruct.total_out;
        zlibStrmStruct.avail_out = [cmpData length] - zlibStrmStruct.total_out];
        deflateStatus = deflate(&zlibStrmStruct, Z_FINISH);
    } while ( deflateStatus == Z_OK );

    // 圧縮後のステータスがZ_STREAM_END以外はエラーと判断
    if (deflateStatus = != Z_STREAM_END) {

        // エラー処理
        deflateEnd(&zlibStrmStruct);
        return nil;
    }

    // メモリ解放
    deflateEnd(&zlibStrmStruct);

    // サイズ確定
    [cmpData setLength:zlibStrmStruct.total_out];

    return cmpData;
}

@end


一応、zlibの公式ページへのリンクを貼っておきます。

が、私は「zlib」でググって出てくる和訳ページにお世話になりましたので、公式ページはほとんど見ていません。

さて、処理の流れですがおおざっぱに言うと、初期化→格納バッファ確保→圧縮といった感じで、ほんとおおざっぱですみません。

キモは初期化部分で、圧縮方式やら圧縮率やらを設定します。

int initError = deflateInit2(&zlibStrmStruct, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);


この部分ですが構文はこんな感じです。


ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, // データ構造体のアドレス
int level, //圧縮レベル(0〜9まで指定可)
int method, // 圧縮命令(Z_DEFLATED固定らしい)
int windowBits, // 圧縮方式や圧縮時のメモリ使用率を指定(だと思う)
int memLevel, // これもメモリ使用率に関係
int strategy)); // 圧縮アルゴリズム

関数名がdeflateInit2と2が付いていますが、deflateInitと無印もあります。

違いはあんまりちゃんと調べてないので、あやふやですがdeflateInitはzip圧縮、deflateInit2はzlib形式(gzipも指定可能)なんですかね。

それぞれの引数ですが、コメントアウトに大雑把な説明を書いておきました。

第4引数はちょっと複雑で、8から15までの整数を与えることができ、大きい数であるだけ圧縮率が高くなりますが、そのぶんメモリの使用量が増加するようです。

デフォルトの値は15です。

  • 8から-15の整数を指定すると、ZLIBヘッダとトレイラのない生のdeflateデータを作成して、15以上はGZIPエンコーディング、WINDOW-BITSに16を足すと、zlibラッパの代わりに、シンプルなgzipヘッダとトレイラが圧縮データの前後に書き出されます。

詳細はzlibのドキュメントでご確認を。

私自身あやふやな部分があるので、もうちょっと調べたら追記しようと思います。