Objective-cでBASE64変換

(2014-04-10追記)
この記事は古いです。
どうやらiOS7からNSDataにbase64対応メソッドが追加されています。
コチラ
—-

Amazon Web Serviceを使うのにBase64が必要となったんですけど、cocoaには含まれてないんですね。

今までライブラリに含まれている環境ばかりで育ったゆとりおじさんなもんで、ちょっとショックだったんですけど、
wikiで調べてみるとそんなに難しい変換ではなさそう。

Base64変換の手順を以下に挙げる。
  元データを6bitずつに分割。(6bitに満たない分は0を追加して6bitにする)
  各6bitの値を変換表を使って4文字ずつ変換。(4文字に満たない分は = 記号を追加して4文字にする)

変換例
1. 元データ
   文字列: “ABCDEFG”
   16進表現: 41, 42, 43, 44, 45, 46, 47
   2進表現: 0100 0001, 0100 0010, 0100 0011, 0100 0100, 0100 0101, 0100 0110, 0100 0111
2. 6bitずつに分割
   010000 010100 001001 000011 010001 000100 010101 000110 010001 11
3. 2bit余るので、4bit分0を追加して6bitにする
   010000 010100 001001 000011 010001 000100 010101 000110 010001 110000
4. 変換表により、4文字ずつ変換
   “QUJD”,”REVG”,”Rw”
5. 2文字余るので、2文字分 = 記号を追加して4文字にする
   “QUJD”,”REVG”,”Rw==”
6. Base64文字列
   “QUJDREVGRw==”
   http://ja.wikipedia.org/wiki/Base64

元データを6bitずつに分割して、そこから得られた値を変換表を使って対応する文字を取得すればいいんですな。
4文字ずつ変換ってことは6×4=24bit ・・3バイトずつ処理すれば良いってことで、
図解するとこんな感じ?
  ■■■■■■■■
 ■■■■
■■■■
 ■■
■■■■■■
 :1文字目 :2文字目 :3文字目 :4文字目

せっかくなのでNSDataのカテゴリにしてみました。

インターフェース部


@interface NSData (Base64)

//BASE64でエンコードされた文字列をデコードする
+(NSData*)dataWithBase64String:(NSString*)string;

//レシーバーの内容をBASE64でエンコードします
-(NSString*)stringByBase64Encode;

@end

実装側はこんな感じ



@implementation NSData (Base64)

//変換用テーブル
const static char base64table[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

/**
 * BASE64でエンコードされた文字列をデコードする
 */
+(NSData*)dataWithBase64String:(NSString*)string
{
    if (!string || string.length == 0) return nil;
    
    unsigned char idx[4];
    unsigned char val1,val2,val3;
    char buf;

    unsigned long datalength = string.length;
    NSMutableData* result = [NSMutableData data];
    
    //ポインタで処理するためにchar*へ変換
    const char* src = [string cStringUsingEncoding:NSASCIIStringEncoding];
    
    for(int i=0;i<datalength;i += 4)
    {
        //base64の先頭からのインデックスを求める(4文字ずつ処理する)
        for (int j=0; j < 4; j++)
        {
            buf = *(src + i + j);
            idx[j] = (buf == '=') ? 0 : strchr(base64table, buf) - base64table;
        }
        
        //求めたインデックスからバイト値を求める(6bit×4 から 8bit×3を生成)
        val1 = ((idx[0] & 0x3F) << 2 | (idx[1] & 0x30) >> 4 );
        val2 = ((idx[1] & 0x0F) << 4 | (idx[2] & 0x3C) >> 2 );
        val3 = ((idx[2] & 0x03) << 6 | (idx[3] & 0x3F) >> 0 );
        
        //結果を保存
        [result appendBytes:&val1 length:1];
        [result appendBytes:&val2 length:1];
        [result appendBytes:&val3 length:1];
    }
    
    return result;
}


/**
 * レシーバーの内容をBASE64でエンコードします
 */
-(NSString*) stringByBase64Encode
{
    unsigned char buf1,buf2,buf3;
    unsigned char idx[4];
    char bufchar[2];
    bufchar[1] = '\0';
    
    NSMutableString *result = [NSMutableString string];
    
    unsigned long datalength = self.length;
    const unsigned char* data = [self bytes];

    for(int i = 0 ; i < datalength ; i += 3 )
    {
        //2バイト目、3バイト目が存在するかチェック
        bool flg2 = ((i+1) < datalength);
        bool flg3 = ((i+2) < datalength);
        
        
        //3バイト(24bit)ずつ処理
        buf1 = data[i];
        buf2 = flg2 ? data[i+1]: '\0';
        buf3 = flg3 ? data[i+2]: '\0';
        
        //24bitを6bitに分け、それぞれに対応する文字へのインデックスを求める
        idx[0] = (buf1  >> 2);                        //buf1の6bit
        idx[1] = (buf1 & 0x03) << 4 | (buf2 >> 4);    //buf1の2bitとbuf2の4bit
        idx[2] = (buf2 & 0x0F) << 2 | (buf3 >> 6);    //buf2の4bitとbuf3の2bit
        idx[3] = (buf3 & 0x3F);                       //buf3の6bit
        
        //結果へ格納
        for (int j=0; j<4; j++)
        {
            if ((j == 2 && !flg2) || (j == 3 && !flg3))
            {
                [result appendString:@"="];                
            }
            else
            {
                bufchar[0] = base64table[idx[j]];
                [result appendString:[NSString stringWithCString:bufchar encoding:NSASCIIStringEncoding]];
            }
        }        
    }
    
    return result;
}

@end

実行速度は計っていないですが、デコード側でテーブルの検索を行っているので結構遅いのではないかと思います。
とはいえ、自分が使う分には十分に動作しているのでまぁいいや。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です