2016-05-10 10 views
2

私が取り組んでいる大規模なプロジェクトを手助けしようとする非常に単純なテストアプリケーションを書いています。Objective-C NSStringメモリリークの再利用

簡単に言えば、テストアプリは所定の回数だけループし、各ループの文字列に「1」を追加します。ループが1000の倍数に達すると、文字列がリセットされ、プロセスが再び開始されます。

コードは次のとおりです。私が見ていることは、メモリ使用量が予想以上に高いことです。繰り返しごとに約0.5MBが追加されます。

newStringは再利用されませんが、使用されていたメモリを回復することなく破棄され、新しいインスタンスが作成されたようです。

最終的に、このソフトウェアは100000をはるかに超える数をカウントする必要があります。 テストとして、私が反復を1000万に変更すると、5GB以上のメモリが必要になります!

誰か提案がありますか?これまでのところ、文字列のクリアを書き、ARCをオフにして手動で再作成するさまざまな方法がありますが、予想していたメモリ量を取り戻すことはできません。

ありがとうございました!

* ps。はい、この実際のソフトウェアはまったく意味がありませんが、私が言うように、一度修正された有用なコードに移行されるテストアプリケーションです。


int targetCount = 100000; 
NSString * newString; 

int main(int argc, const char * argv[]) { 
    @autoreleasepool { 
     process(); 
     return 0; 
    } 
} 

void process() { 
    for (int i=0; i<targetCount; i++) { 
     calledFunction(i); 
    } 
} 

void calledFunction(count) { 
    if ((count % 1000) == 0) { 
     newString = nil; 
     newString = @""; 
    } else { 
     newString = [NSString stringWithFormat:@"%@1", newString]; 
    } 
} 
+2

:-)勝つ、勝ちますか? – trojanfoe

+1

真剣に?それはそれを修正しましたか?笑。 あなたは私の新しいヒーローです! – user2729972

+0

これは、自動解放プールの排水頻度に関する質問です。しかし、これはとにかく使える素晴らしいパターンではありません。代わりに 'NSMutableString'を見てください。 – trojanfoe

答えて

5

あなたcalledFunction関数は、現在の自動解放プールが排出されるまで解放されません自動解放NSStringを作成します。

process関数は、ループ内でcalledFunctionを100,000回呼び出します。このループの期間中、現在の自動解放プールには排水する機会が与えられません。 processメソッドの終了に達するまでには、calledFunctionで作成されたNSStringオブジェクトの100,000インスタンスはすべてメモリに残っています。

大きなループを使用すると、以下のように追加の自動解放プールを追加するときに自動解放オブジェクトのビルドアップを回避するための一般的な解決策は:

void process() { 
    for (int i=0; i<targetCount; i++) { 
     @autoreleasepool { 
      calledFunction(i); 
     } 
    } 
} 
1

あなたの問題は、自動解放プールから茎ARCの時代にはやや時代遅れの特徴です。

alloc/initの組み合わせでオブジェクトを作成すると、結果のオブジェクトは呼び出し元が所有します。標準newの方法についても同様であり、allocに続いてinitと定義される。各init...方法について

クラスによっては、一致<type>...方法を持って、これはalloc/init/autoreleaseとして定義され、発呼者に所有されていないオブジェクトを返しています。たとえば、コードでstringWithFormat:を使用します。これはalloc/initWithFormat/autoreleaseと定義されています。

非所有返されるオブジェクトは、自動解放プールにあり、所有権が取られない限り、主自動解放プールのメインイベントループの反復ごとに一度である解放プールが空にされていることを自動的に次回、あろう。多くのプログラムでは、イベントループの反復処理が頻繁に行われ、自動解放プールからオブジェクトを再利用して、メモリ使用量が増加しないようにしています。しかし、大きいループforループの例のように、オブジェクトが作成されてからイベントループの1回の繰り返しで多く破棄されると、自動解放プールは空になる前に多くの必要なオブジェクトで終了する可能性があります。

A一般的な解決策は、

この問題に対する一般的な解決策は、できるだけ早くそれが終了するように空にされているローカル自動解放プールを使用することです。そのようなローカル自動解放プールを適切に配置することによって、メモリ使用を最小限に抑えることができます。彼らはゴミの多くを生成し、ループの本体をラップしているため一般的な場所は、あなたのコードでは、これは次のようになります。

void process() 
{ 
    for (int i=0; i<targetCount; i++) 
    { @autoreleasepool 
     { 
     calledFunction(i); 
     } 
    } 
} 
ここ

calledFunction()によって作成されたすべての自動解除され、破棄されたオブジェクトは、の反復ごとに再利用されますループ。

このアプローチの欠点は、@autoreleasepool構成の最適な配置を決定することです。確かにこれらの日に自動参照カウント(ARC)このプロセスを簡素化することができますか?

別の解決策は、もちろん...:あなたが直面している問題が長すぎるため、自動解放プールで終わるオブジェクトである

オートリリースプールを避け、これに対する簡単な解決策は絶対に入れないことです最初のプールのオブジェクト。

Objective-Cの第三オブジェクト作成パターンnew...を有し、それはautorelease無し<type>...しかしと同様です。手動のメモリ管理と重い自動リリースのプール使用の時代から生まれたほとんどのクラスは、(これはちょうどalloc/init)を実装していますが、他のメンバーはいませんが、簡単にカテゴリを追加できます。ここでnewWithFormat次のとおりです。

@interface NSString (ARC) 

+ (instancetype)newWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2); 

@end 

@implementation NSString (ARC) 

+ (instancetype)newWithFormat:(NSString *)format, ... 
{ 
    va_list args; 
    va_start(args, format); 
    id result = [[self alloc] initWithFormat:format arguments:args]; 
    va_end(args); 
    return result; 
} 

@end 

(原因変数の引数に、これが最もnew家族の方法よりも複雑であるだろう。)

がアプリケーションに上記を追加し、その後newWithFormatstringWithFormatへの呼び出しを置き換えます返された文字列は呼び出し元が所有し、ARCはそれらを管理し、自動解放プールをいっぱいにしません。 - 決してそれを入力せず、@autoreleasepool構造を配置する場所を特定する必要はありません。勝つには、あなたがより多くの `@ autoreleasepool`は、コード内のダウン深いスコープを使用する場合(つまり`プロセス() `の` for`ループ内で)何が起こる

HTH