iPhone/iPad用ドット絵エディタ「EDGE touch」

最近のエントリー

一つ前の投稿と関連していますが、例えば巨大な画像をロードしてUIImageViewに貼ったとして、何かしらInstrumentsのグラフに大きな変化が見られるかと思ったんです。
ところが実際にInstrumentsを動かしてみると、UIImageのメモリ消費量は48byte…ってまあそりゃあCGImageとかラップしてるだけのクラスでしょうから実体は別のところにあるんだろうって事は予想がつきますね。

で….実体はどこにあるんでしょう…。
(たぶんOpenGLのテクスチャか何かになっていると思うのですが。)

#Activity Monitorにはそれなりに反応が有るっぽいんですが…

ARCのおかげでメモリリークの心配も無くなりヒャッハーしていたところ、メモリが枯渇するという問題に打ち当たりました。

よくARCでも以下の話は耳(目)にしますね。

  • autoreleaseオブジェクトがループ処理などで溜まり、メモリが枯渇する(@autoreleasepoolで回避)
  • [UIImage imageNamed:]が画像をキャッシュするから避けた方がよい
  • 相互参照などでメモリがリークしてしまう(弱参照などで回避)

この辺は自分でも気をつけて開発をしていたつもりなのですが、動作テストを行っているうちに、「ゆっくりと操作しているうちは問題無いのに、素早く操作をするとメモリが枯渇する」という挙動がみられました。

というわけで、ARCの挙動を確認すべく、簡単なサンプルを作ってみました。

#import "ViewController.h"

#define ImageSize CGSizeMake(2000, 2000)

@implementation ViewController
{
    int _click;
    UIImageView *_imageView;
}

- (void)loadView
{
    [super loadView];
    self.view.backgroundColor = [UIColor whiteColor];
    self.view.multipleTouchEnabled = YES;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%d", ++_click);
    
    // 以前作ったものを破棄
    if (_imageView != nil) {
        [_imageView removeFromSuperview];
        _imageView = nil;
    }
    
    // でかい画像を作る
    UIGraphicsBeginImageContext(CGSizeMake(2000, 2000));
    [[UIColor colorWithHue:(float)rand() / RAND_MAX saturation:1 brightness:1 alpha:1] set];
    CGRect rc = {0};
    rc.size = ImageSize;
    UIRectFill(rc);
    UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    // ImageViewで貼る
    _imageView = [[UIImageView alloc] initWithImage:img];
    _imageView.frame = CGRectMake(50, 50, 300, 300);
    [self.view addSubview:_imageView];
}

@end

ソースを見ていただければ一目瞭然ですが、タッチすると大きなサイズのUIImageを作って、UIImageViewにはめ込んでビューに追加するというものです。
タップ二度目以降は、前回のUIImageViewをremoveFromSuperviewしてnilしているので、参照カウンタが0になり、UIImageViewもUIImageもARCにより解放されるであろうと思って組んだものです。
(要はUIImageViewとUIImageは最大1つしか存在しないつもりで書いたプログラムです。)

実際にiPad2(iOS6.1.3)で動かしてみると、ゆっくりとタップしている場合は何回タップしてもメモリが枯渇する事はありませんが、両手を使って(マルチタッチ対応なので)素早く連打すると、だいたいカウンタ40ぐらいでメモリが枯渇してアプリがクラッシュします。

こういった挙動を見ると、「CPUが暇な時に何か解放する処理が走っている」と考えるのが自然だと思うのですが、そういった挙動が果たして公式な動作として存在するのかどうか。いや、有るならば本当にメモリが枯渇しそうになったときにアプリが一瞬止まっても構わないので溜まっているメモリを強制的に解放する術がないのか(むしろ勝手にやってくれないのか)など、疑問は尽きません。

この辺、お詳しい方がいらっしゃいましたら、メールまたはツイッター(@takabosoft)などでご教示願えると助かります。
(なんだかすごく初歩的な話のような気がしてならないですが。)

ちなみに、今回のテストプログラムに限っては以下の一行を入れると素早く操作しても枯渇しないで済みます。

    // 以前作ったものを破棄
    if (_imageView != nil) {
        _imageView.image = nil; // この一行を入れる
        [_imageView removeFromSuperview];
        _imageView = nil;
    }

iOS6でCore Graphicsのアフィン変換を使って絵文字を360°回転させてみたのですが、45°、135°、225°、315°が描画されず、しかもその付近の角度でも変に伸びてしまっているのが確認できます。

IMG_0094
(クリックで拡大できます)

ほぼ同じコードで、「あ」を回してみた結果はこちら。なんら問題ありません。
IMG_0095

再現コードです(iPad)。

#import "TestView.h"

#define AreaSize (44)
#define FontSize (30)
#define RenderCount ((int)(360))

@implementation TestView

- (void)drawRect:(CGRect)rect
{
    CGContextRef c = UIGraphicsGetCurrentContext();
    UIFont *angleFont = [UIFont systemFontOfSize:10];
    
#if 1 
    UIFont *font = [UIFont fontWithName:@"AppleColorEmoji" size:FontSize];
    unichar chars[] = {0xd83d, 0xde03, 0};
    NSString *str = [[NSString alloc] initWithCharacters:chars length:2];
#else
    UIFont *font = [UIFont systemFontOfSize:FontSize];
    NSString *str = @"あ";
#endif
    CGSize size = [str sizeWithFont:font];
    
    // ちょっとずつ回転させながら絵文字を描画
    float x = 0, y = 0;
    for (int i = 0; i < RenderCount; i++) {
        // 行列で回転&移動させる
        CGContextSaveGState(c);
        CGAffineTransform t = CGAffineTransformIdentity;
        t = CGAffineTransformTranslate(t, x + (AreaSize - size.width) / 2, y + (AreaSize - size.height) / 2);
        t = CGAffineTransformTranslate(t, +size.width / 2, +size.height / 2);
        float angle = 2 * M_PI * i / RenderCount;
        t = CGAffineTransformRotate(t, -angle);
        t = CGAffineTransformTranslate(t, -size.width / 2, -size.height / 2);
        CGContextConcatCTM(c, t);
        [str drawAtPoint:CGPointZero withFont:font];        
        CGContextRestoreGState(c);
        
        // 枠
        UIRectFrame(CGRectMake(x, y, AreaSize, AreaSize));
        
        // 角度文字
        NSString *angleStr = [NSString stringWithFormat:@"%.1f°", angle * 180 / M_PI];
        [angleStr drawAtPoint:CGPointMake(x + 2, y) withFont:angleFont];
        
        // 次の位置へ
        x += AreaSize;
        if (x + AreaSize > self.bounds.size.width) {
            x = 0;
            y += AreaSize;
        }
    }
}

ちなみにiOS5では問題無く表示されますので、iOS6からの不具合かと思います。
適当にググっても、この辺のトラブルについてはまったくヒットせず困り中です。

何か解決策をお持ちの方はメールやTwitter(@takabosoft)でこっそり教えて頂けると助かります。
※公式のフォーラムやサポートにも聞いてみようと思いますが、何せ英語なのでうまく伝わるかどうか・・・。

追記

keynoteでもテキストオブジェクトを45度などにすると絵文字が消えるのを確認しました(;^ω^)

▼アカペラカバー
Zedd – Clarity Acapella Cover – Mike Tompkins – ft. Foxes

▼原曲
Zedd – Clarity (Official Video) ft. Foxes

Zeddの1stアルバム(Clarity)を数カ月前に購入しましたが、どの曲も素敵です。
オススメ。

というわけでV-METALで作ってみた第6弾です。
テンプレを使いまわしているせいで、毎回楽器の構成が同じでつまんないかもしれません(;´∀`)

もともとパイプオルガンか何かで演奏するクラシック音楽なのですが、デザエモンのサンプルにロックアレンジされたものがあって、これが当時めちゃくちゃカッコよくてこればっかり聴いていました。