ラベル iphone の投稿を表示しています。 すべての投稿を表示
ラベル iphone の投稿を表示しています。 すべての投稿を表示

2015年3月6日金曜日

cocos2d-xのメモリ対策について

どうも、俺@仕事中です。

昨年、cocos2d-x CCSprite(テクスチャ)の軽量化という記事を書きましたが、
今回も同じようにメモリ対策についてメモ。

■環境
・cocos2d-x 2.2.6
・Xcode 6.1.1

先に結論から言いますと、
CCSpriteFrameCacheやCCTextureCacheの使いすぎに注意、
または、メモリ不足に陥る前に正しくキャッシュデータを解放しましょう。
ということです。

CCSpriteFrameCacheCCTextureCacheは、
スプライトシートなどの画像データをメモリ上にキャッシュしてくれるため、
CCSpriteオブジェクトの生成時に高速&低負荷であることがメリットな反面、
メモリを消費します。(※1)

特にCCSpriteFrameCacheは大きなサイズは画像ファイル(スプライトシート)を
キャッシュすることがあるのでそのメモリ消費には要注意です。


例えば、
タイトル画面(TitleScene) → 遷移 → ゲーム画面(GameScene)
のような場合。

タイトル画面で表示するためのスプライトシートを読み込み、
またゲーム画面でもゲーム画面用のスプライトシートを読み込み、
といった処理を行うと、
両方の画面で読み込まれたスプライトシートはキャッシュされたままになります。




メモリが不足してくると、iOSでは
    AppController#applicationDidReceiveMemoryWarning:(UIApplication *)application; 
がコールされ、
cocos2d-xのバージョンによっては、
    CCDirector#purgeCachedData(void)
が呼ばれて、開発者が意図せず勝手にキャッシュデータが消去されます。

この状態だと、次にCCSpritFrameCacheからキャッシュデータを読み込もうとすると
エラーとなりアプリがクラッシュしてしまいます。


対応として、
1)CCDirector#purgeCachedData(void)を呼ばない。
2)不要になったタイミングでCCSpriteFrameCacheのキャッシュデータをクリアする。
が考えられます。

1)の方法は簡単&すぐに対応できますが、メモリ逼迫の原因は解消されません。
2)は、画面遷移の前などで
    CCSpriteFrameCache::sharedSpriteFrameCache()->removeSpriteFramesFromFile(const char *plist);
を呼べばOKです。

画面遷移の前というのは、
CCDirector#replaceScene(CCScene *pScene)
を呼ぶより前のタイミングです。
その該当するSceneのonExit()やデストラクタ内で、
    CCSpriteFrameCache::sharedSpriteFrameCache()->removeSpriteFramesFromFile(const char *plist);
を呼ぶと、
すでに遷移先のSceneオブジェクトが生成されてしまっているので、
タイミングとしてはちょっと遅いです。

※1) CCSpriteクラスのcreate(const char *fileName)メソッドや
initWithFile(const char *fileName)メソッドは、
内部でCCTextureCacheクラスのaddImage(const char *fileName)が呼ばれているため、
キャッシュされ、二回目以降の呼び出しは高速になる。


以上でぇぇぇぇぇす。

2013年2月16日土曜日

UINavigationBarのナビゲーションバーに背景画像を設定する

どうも、俺@休み中です。
最近はずっとiOSアプリ開発に勤しんでいます。 

UINavigationBarを使って、ヘッダー部分(ナビゲーションバー)に画像を設定したい場合。 QiitaのUINavigationBarの背景画像を設定する方法を参考にさせてもらいました。
#import <QuartzCore/QuartzCore.h>

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UIViewController *viewController = [[UIViewController alloc] initWithNibName:nil bundle:nil];
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:vc];

    // OSのバージョン取得
    float osVersion = [[[UIDevice currentDevice] systemVersion] floatValue];

    // iOS5未満
    if (osVersion < 5.0f) {
        UIImageView *imageView = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:@"background.png"] stretchableImageWithLeftCapWidth:0 topCapHeight:1]];
        navBg.layer.zPosition = -FLT_MAX;
        [navigationController.navigationBar insertSubView:imageView atIndex:0];

    // iOS5以上
    } else if (osVersion >= 5.0f) {
        navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:@"background.png"] forBarMetrics:UIBarMetricsDefault];
    }
}
です。
 iOS5以上から
- (void)setBackgroundImage:(UIImage *)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics

というメソッドが加わったため、iOS5未満の方法で同じように実装しても思ったように表示されません。

以上でぇぇぇえぇぇぇす。

2012年3月13日火曜日

NSStringのstringByAppendingPathComponent注意

どうも、俺@仕事中です。
Objective-Cでゲームとか開発してると、ユーザのちょっとしたデータをファイルに保存したりしますよね。

その時のコードでエラー起こしてしまったので、対処法をメモ。

間違いソースがこれです。

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *directory = [paths objectAtIndex: 0];
self.filePath = [directory stringByAppendingPathComponent: @"hoge.plist"];
self.filePathは保存するファイルのパスを格納するNSStringなクラス変数です。

これだとstringByAppendingPathComponentメソッドは結果をautoreleaseしている(と思う)ので、
ココの処理を抜けた時点でself.filePathはリリースされてしまう(はず)。
別のメソッドでself.filePathを使おうとしても、リリースされたオブジェクトにアクセスしようとしてEXC_BAD_ACCESSになっちゃう。


NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *directory = [paths objectAtIndex: 0];
self.filePath = [[NSString alloc] initWithString: [directory stringByAppendingPathComponent: @"hoge.plist"]];
にしてやれば、self.filePathを自身のdeallocメソッド内でreleaseすることが出来る!


以上でぇぇえぇす。

2012年3月6日火曜日

Autoingestion.classを使うとjavax.net.ssl.SSLHandshakeExceptionが出ちゃう

どうも、俺@残業中です。

iPhoneアプリの販売データを取得するため、AppleはAutoingestion.classなるものを用意してくれてます。
iTunesConnectのSales and TrendsにあるデータをTSVデータとしてDLできます。
わざわざHTMLをスクレイピングしなくて良いですね!
ドキュメントはココ
とても分かりやすい参考サイトは iTunesConnectからアプリダウンロード数レポートを自動取得する方法 - zaru blog がとても分かりやすいです!

ところが、僕の開発環境だと、このプログラムを実行させるとタイトルの通り

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
になっちゃいます。ほとんどの人は関係ないと思いますが。。
今日はその解決法をばめもめも。

javaのkeytoolというコマンドを使って、証明書をインポートすれば良いそうです。
あまりjavaには明るくない俺なりに調べてみたのですが、
どうやら[SSLHandshakeException]信頼できない証明書のサイトと無理矢理接続する方法にあるように、
Javaで信頼できないSSL証明書を使ってHTTPコネクションを貼ろうとした場合に出る例外のようです。
AppleからのAutoingestion.classがどういう実装になっているのか調べてみれば分かるかもしれませんが、
とりあえず今回は解決優先ということで、「Sales And Trends」データを取得しにいくサーバへSSL通信するときの証明書が怪しいのでは?という仮説を立てて対応することにしました。

つまり、接続先の証明書をkeytoolを使ってインポートすれば良いのです。
証明書はhttps://itunesconnect.apple.comのものだと思います。
つまり、Autoingestion.classはitunesconnect.apple.comへ接続しにいっているはず。
(多分。。。tcpdumpとかでパケットを監視してないので嘘だったらごめんなさい。今度調べます。)

なので、FireFox(僕Mac版FireFox使ってます)とかで上記URLを叩き、
アドレスバーの左側(緑色になっているはず)をクリック>詳細を表示>「セキュリティ」タブ>証明書を表示...>「詳細」タブ>書き出す...
の操作を行えば証明書を取得できます。

この証明書ファイルを、開発サーバのjavaへ組み込みます。

$ keytool -import -keystore /usr/local/jdk1.6.0_16/jre/lib/security/cacerts -storepass changeit -file /path/to/証明書ファイル
これでOK。
-keystore は俺の環境での値を書きましたが、環境に合わせて書き換えて下さい。
-storepass はデフォルトでchangeitです。
-file は先程FireFoxから取得した証明書ファイルへのパスです。
何とこれでサーバのjava(/path/to/jdk/jre/lib/security/cacerts)に証明書データがインポートされ、
「信頼できる証明書」として扱われます。
オレオレ証明書を作成した場合などで使えますね!

keytoolについてはJava/keytool - 備忘録に詳しく書かれています。


これでAutoingestion.classを使えば、悩み解決されます!

$ java -cp . Autoingestion username password vendorid Sales Daily Summary `date +%Y%m%d -d "2 days ago"`

.gzファイルがDLされます!

以上でぇぇぇえぇす。