Adobe AIR for iOS の Low memory warning による GC 大量発生を無視する方法

今日のバッドノウハウです。

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 を使いましょう。

```objective-c
// 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 を無理やり自作クラスのインスタンスってことにできます。