私はARCを使用していて、ループ内の文字列を変更するときに奇妙な動作が見られます。
私の状況では、NSXMLParserデリゲートコールバックを使用してループしていますが、デモプロジェクトといくつかのNSString
オブジェクトを変更するだけのサンプルコードを使用すると、まったく同じ動作と症状が見られます。
デモプロジェクトはGitHubからダウンロードできます。メインビューコントローラーのviewDidLoad
メソッドで、4つのメソッド呼び出しの1つをコメント解除するだけで、さまざまな動作をテストできます。
簡単にするために、これは私が空のシングルビューアプリケーションに固執した単純なループです。viewDidLoad
このコードをメソッドに直接貼り付けました。ビューが表示される前に実行されるため、ループが終了するまで画面は黒くなります。
NSString *text;
for (NSInteger i = 0; i < 600000000; i++) {
NSString *newText = [text stringByAppendingString:@" Hello"];
if (text) {
text = newText;
}else{
text = @"";
}
}
次のコードも、ループが完了するまでメモリを消費し続けます。
NSString *text;
for (NSInteger i = 0; i < 600000000; i++) {
if (text) {
text = [text stringByAppendingString:@" Hello"];
}else{
text = @"";
}
}
割り当てツールを実行した状態で、これら2つのループがInstrumentsでどのようにループするかを次に示します。
見る?大量のメモリ警告が表示されてからアプリが自然に停止するまで、徐々に安定したメモリ使用量。
次に、少し違うことを試してみました。私は次のようにのインスタンスを使用しましたNSMutableString
:
NSMutableString *text;
for (NSInteger i = 0; i < 600000000; i++) {
if (text) {
[text appendString:@" Hello"];
}else{
text = [@"" mutableCopy];
}
}
このコードのパフォーマンスははるかに優れているようですが、それでもクラッシュします。これは次のようになります。
次に、これを小さなデータセットで試し、どちらかのループがビルドアップを完了するのに十分な時間生き残ることができるかどうかを確認しました。NSString
バージョンは次のとおりです。
NSString *text;
for (NSInteger i = 0; i < 1000000; i++) {
if (text) {
text = [text stringByAppendingString:@" Hello"];
}else{
text = @"";
}
}
同様にクラッシュし、結果のメモリグラフは、次のコードを使用して生成された最初のグラフのようになります。
を使用するNSMutableString
と、同じ百万回の反復ループが成功するだけでなく、はるかに短い時間で実行されます。コードは次のとおりです。
NSMutableString *text;
for (NSInteger i = 0; i < 1000000; i++) {
if (text) {
[text appendString:@" Hello"];
}else{
text = [@"" mutableCopy];
}
}
そして、メモリ使用量のグラフを見てください。
最初の短いスパイクは、ループによって発生したメモリ使用量です。viewDidLoadで実行しているため、ループの処理中に画面が黒くなるという一見無関係な事実に気付いたときのことを覚えていますか?そのスパイクの直後に、ビューが表示されます。したがって、このシナリオではNSMutableStringsがメモリをより効率的に処理するだけでなく、はるかに高速であるように見えます。魅力的な。
ここで、実際のシナリオに戻ります... NSXMLParser
API呼び出しの結果を解析するために使用しています。XML応答構造に一致するObjective-Cオブジェクトを作成しました。したがって、たとえば、次のようなXML応答について考えてみます。
<person>
<firstname>John</firstname>
<lastname>Doe</lastname>
</person>
私のオブジェクトは次のようになります。
@interface Person : NSObject
@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, strong) NSString *lastName;
@end
これで、NSXMLParserデリゲートで、先に進んでXMLをループし、現在の要素を追跡します(データはかなりフラットなので、完全な階層表現は必要ありません。これは、 XMLとしてのMSSQLデータベース)そして、foundCharacters
メソッドで、次のようなものを実行します。
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
if((currentProperty is EqualToString:@"firstname"]){
self.workingPerson.firstname = [self.workingPerson.firstname stringByAppendingString:string];
}
}
このコードは最初のコードによく似ています。を使用してXMLを効果的にループしているNSXMLParser
ので、すべてのメソッド呼び出しをログに記録すると、次のようになります。
parserDidStartDocument:parser:didStartElement:namespaceURI:qualifiedName:attributes:parser:foundCharacters:parser:didStartElement:namespaceURI:qualifiedName:parser:didStartElement:namespaceURI:qualifiedName:attributes:parser:foundCharacters:parser:didStartElement:namespaceURI:qualifiedName:parser:did namespaceURI:qualifiedName:attributes:parser:foundCharacters:parser:didStartElement:namespaceURI:qualifiedName:parserDidEndDocument:
パターンを見ますか?それはループです。を複数回連続して呼び出すことも可能であることに注意してください。そのためparser:foundCharacters:
、プロパティを前の値に追加します。
まとめると、ここには2つの問題があります。まず第一に、あらゆる種類のループでメモリが蓄積すると、アプリがクラッシュするようです。第二に、NSMutableString
プロパティでの使用はそれほどエレガントではなく、意図したとおりに機能するかどうかさえわかりません。
一般に、ARCを使用して文字列をループしているときに、このメモリの蓄積を克服する方法はありますか?NSXMLParserに固有のことができることはありますか?
編集:
最初のテストでは、1秒を使用しても@autoreleasepool{...}
問題は解決しないようです。
オブジェクトは、thwyが存在する間、メモリ内のどこかに移動する必要があり、自動解放プールが空になる可能性があるrunloopの終わりまでそこにあります。
これは、NSXMLParserに関する限り、文字列の状況では何も修正されません。ループがメソッド呼び出し全体に分散しているため、修正される可能性があります。さらにテストする必要があります。
(理論的には、ARCはメモリがピークアウトするまでではなく、ある時点でメモリをクリーンアップするため、これをメモリピークと呼んでいることに注意してください。実際には何もリークしていませんが、同じ効果があります。)
編集2:
自動解放プールをループ内に固定すると、いくつかの興味深い効果があります。NSString
オブジェクトに追加するときの蓄積をほぼ軽減するようです。
NSString *text;
for (NSInteger i = 0; i < 600000000; i++) {
@autoreleasepool {
if (text) {
text = [text stringByAppendingString:@" Hello"];
}else{
text = [@"" mutableCopy];
}
}
}
割り当てトレースは次のようになります。
時間の経過とともにメモリが徐々に蓄積されることに気づきましたが、それは約150キロバイトの調整であり、以前に見られた350メガバイトではありません。ただし、このコードを使用NSMutableString
すると、自動解放プールを使用しない場合と同じように動作します。
NSMutableString *text;
for (NSInteger i = 0; i < 600000000; i++) {
@autoreleasepool {
if (text) {
[text appendString:@" Hello"];
}else{
text = [@"" mutableCopy];
}
}
}
そして、割り当てトレース:
NSMutableStringは明らかに自動解放プールの影響を受けないように見えます。理由はわかりませんが、最初は、これを以前に見たものと結び付けます。これはNSMutableString
、それ自体で約100万回の反復を処理できますが、処理NSString
できません。
それで、これを解決する正しい方法は何ですか?