今日のバッドノウハウです。
Adobe AIR for iOS では、iOS がアプリケーションに送信する Low memory warning を受けるととりあえずガベージコレクション(GC)を行うようです。
そのため、メモリ使用量が多いとフレームレートが下がり続け、アプリケーションがまともに動作しません。
理想はメモリ使用量を下げることですが、まあそこはとりあえず置いておいて、警告が来ても GC されないようにしたいと思います。この方法を取り入れればフレームレートが下がらずに綺麗にアプリケーションが異常終了してくれるようになります。
詳しく説明すると激しく冗長になるので、とりあえず今回は Adobe Native Extension (ANE) を作ったことがある前提で解説します。
ANE で行うこと
やりたいことは Low memory warning の delegate をオーバーライドして GC を抑制することです。
iOS の AppDelegate の実装は Adobe AIR の中に入ってしまってるため、実装を直接書き換えることは(簡単には)できません。
delegate injection
実装を動的に書き換えるためにリフレクション系の API を使いましょう。
// AirLowMemoryWarningSuppressor.h
# import <UIKit/UIKit.h>
# import "FlashRuntimeExtensions.h"
@interface AirLowMemoryWarningSuppressor : NSObject <UIApplicationDelegate>
@end
// AirLowMemoryWarningSuppressor.m
@implementation AirLowMemoryWarningSuppressor
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {}
@end
void AirLowMemoryWarningSuppressor_didReceiveMemoryWarning(id self, SEL _cmd, UIApplication* application)
{
NSLog(@"application did receive memory warning.");
}
void AirLowMemoryWarningSuppressor_injectDelegates(id delegate)
{
Class objectClass = object_getClass(delegate);
NSString *newClassName = [NSString stringWithFormat:@"CustomLowMemory_%@", NSStringFromClass(objectClass)];
Class modDelegate = NSClassFromString(newClassName);
if (modDelegate == nil) {
modDelegate = objc_allocateClassPair(objectClass, [newClassName UTF8String], 0);
SEL selectorToOverride = @selector(applicationDidReceiveMemoryWarning:);
Method m = class_getInstanceMethod(objectClass, selectorToOverride);
class_addMethod(modDelegate, selectorToOverride, (IMP)AirLowMemoryWarningSuppressor_didReceiveMemoryWarning, method_getTypeEncoding(m));
objc_registerClassPair(modDelegate);
}
object_setClass(delegate, modDelegate);
}
void AirLowMemoryWarningSuppressor_initializeContext(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctionsToTest, const FRENamedFunction** functionsToSet)
{
AirLowMemoryWarningSuppressor_injectDelegates([[UIApplication sharedApplication] delegate]);
*numFunctionsToTest = 0;
}
void AirLowMemoryWarningSuppressor_finalizeContext(FREContext ctx) { }
void LowMemoryInitializeExtension(void** extDataToSet, FREContextInitializer* ctxInitializerToSet, FREContextFinalizer* ctxFinalizerToSet)
{
*extDataToSet = NULL;
*ctxInitializerToSet = &AirLowMemoryWarningSuppressor_initializeContext;
*ctxFinalizerToSet = &AirLowMemoryWarningSuppressor_finalizeContext;
}
void LowMemoryFinalizeExtension(void *extData) { }
やってること
- AirLowMemoryWarningSuppressor extends UIApplicationDelegate クラスを作る
- そこに applicationDidReceiveMemoryWarning の delegate を空っぽ実装する
- ANE Context の初期化時に [[UIApplication sharedApplication] delegate] で現状の delegate のインスタンスを取得
- delegate のクラスを取得
- クラスを継承して新しいクラスを作る
- そのクラスの applicationDidReceiveMemoryWarning に AirLowMemoryWarningSuppressor_didReceiveMemoryWarning の実装をオーバーライドする(これしないと継承後のクラスのメソッドとして上書きできない)
- 現在の delegate インスタンスのクラスを新しいクラスとして認識させる
これで、Adobe AIR の delegate を無理やり自作クラスのインスタンスってことにできます。