2011-02-19 6 views
2

XMLフィードを解析し、その結果をコアデータに格納するアプリケーションに問題があります。NSXMLParserを使用してXMLデータをコアデータストアに解析するときにメモリが蓄積する

この問題は、ストアに何もなく、フィード全体が解析されて保存されているときにアプリの初回実行時にのみ発生します。 問題は、メモリ割り当てが50%の試行でアプリケーションをクラッシュさせるまで、通常は約10Mbで構築されるまでです。 割り当てられたオブジェクトはCFData(ストア)オブジェクトのようですが、強制的に解放する方法はありません。私たちは、コードに入る前に、 は、あなたがそれを構文解析を完了し、コアデータに保存一度、正常に実行するために得ることができた場合、後続のすべての打ち上げは結構です、メモリ使用量がここ2.5MB

を超えたことがない私が持っている一般的なアプローチです: ゲットNSDataオブジェクトへのフィードNSFileManagerを使用してファイルとして保存します。 ファイルパスからURLを作成し、それをparseXMLFile:メソッドに渡します。代議員は自己です。 到達パーサー:didStartElement:namespaceURI:qualifiedName:attributes:必要なタグからデータを取得するための辞書を作成します。 パーサー:foundCharacters:メソッドは、タグの内容を文字列に保存します パーサー:didEndElement:...メソッドは、タグが必要なものかどうかを判断します。もしそうなら、辞書を無視しなければそれを辞書に追加します。アイテムの終わりに達すると、それをコアデータストアに追加するメソッドが呼び出されます。

ここでサンプルコードや他の人の投稿を見ると、一般的なアプローチには何も間違っているようです。

コードは以下のとおりですが、それはビューコントローラのより大きなコンテキストから来ていますが、直接関係のないものは省略しました。

私は試したことの点で: フィードはXMLであり、JSONに変換するオプションはありません。 共有文書領域に見つかって保存されている画像ではありません。それが何であるかについての

手がかり:責任

オブジェクトアドレスカテゴリの作成時間ライブサイズ : は、これは私が割り当てられた最大の、最も数多くのものに楽器から取得するエントリ(項目ごとに256K)であります図書館責任発信者

1 0xd710000 CFData (店舗)16024774912 262144 CFNetworkはURLConnectionClient :: clientDidReceiveData•( _CFData のconst *、 URLConnectionClient :: ClientConnectionEventQueue *)

そして、ここにコンテンツの匿名性のために編集されたコードは、です;)

-(void)parserDidStartDocument:(NSXMLParser *)feedParser { } 

-(void)parserDidEndDocument:(NSXMLParser *)feedParser { } 

-(void)parser:(NSXMLParser *)feedParser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict 
{ 
    eachElement = elementName; 
    if ([eachElement isEqualToString:@"item" ]) 
    { 
     //create a dictionary to store each item from the XML feed 
     self.currentItem = [[[NSMutableDictionary alloc] init] autorelease]; 
    } 
} 


-(void)parser:(NSXMLParser *)feedParser foundCharacters:(NSString *)feedString 
{ 
    //Make sure that tag content doesn't line break unnecessarily 
    if (self.currentString == nil) 
    { 
     self.currentString = [[[NSMutableString alloc] init]autorelease]; 
    } 
    [self.currentString appendString:feedString]; 

} 

-(void)parser:(NSXMLParser *)feedParser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 
{ 
    eachElement = elementName; 
    NSString *tempString = [self.currentString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 

if ([eachElement isEqualToString:@"title"]) 
    { 
     //skip the intro title 
     if (![tempString isEqualToString:@"Andy Panda UK"]) 
     { 
      //add item to di citonary output to console 
      [self.currentItem setValue:tempString forKey:eachElement]; 
     } 
    } 
    if ([eachElement isEqualToString:@"link"]) 
    { 
     //skip the intro link 
     if (![tempString isEqualToString:@"http://andypanda.co.uk/comic"]) 
     { 
      //add item to dicitonary output to console 
      [self.currentItem setValue:tempString forKey:eachElement]; 
     } 
    } 
    if ([eachElement isEqualToString:@"pubDate"]) 
    { 
     //add item to dicitonary output to console 
     [self.currentItem setValue:tempString forKey:eachElement]; 
    } 
    if ([eachElement isEqualToString:@"description"]) 
    { 
     if ([tempString length] > 150) 
     { 
      //trims the string to just give us the link to the image file 
      NSRange firstPart = [tempString rangeOfString:@"src"]; 
      NSString *firstString = [tempString substringFromIndex:firstPart.location+5]; 
      NSString *secondString; 
      NSString *separatorString = @"\""; 
      NSScanner *aScanner = [NSScanner scannerWithString:firstString]; 
      [aScanner scanUpToString:separatorString intoString:&secondString]; 

      //trims the string further to give us just the credits 
      NSRange secondPart = [firstString rangeOfString:@"title="]; 
      NSString *thirdString = [firstString substringFromIndex:secondPart.location+7]; 
      thirdString = [thirdString substringToIndex:[thirdString length] - 12]; 
      NSString *fourthString= [secondString substringFromIndex:35]; 

      //add the items to the dictionary and output to console 
      [self.currentItem setValue:secondString forKey:@"imageURL"]; 
      [self.currentItem setValue:thirdString forKey:@"credits"]; 
      [self.currentItem setValue:fourthString forKey:@"imagePreviewURL"]; 
      //safety sake set unneeded objects to nil before scope rules kick in to release them 
      firstString = nil; 
      secondString = nil; 
      thirdString = nil; 
     } 
     tempString = nil; 
    } 


    //close the feed and release all the little objects! Fly be free! 
    if ([eachElement isEqualToString:@"item" ]) 
    { 
     //get the date sorted 
     NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 
     formatter.dateFormat = @"EEE, dd MMM yyyy HH:mm:ss ZZZZ"; 
     NSDate *pubDate = [formatter dateFromString:[currentItem valueForKey:@"pubDate"]]; 
     [formatter release]; 
     NSArray *fetchedArray = [[NSArray alloc] initWithArray:[fetchedResultsController fetchedObjects]]; 
     //build the string to make the image 
     NSString *previewURL = [@"http://andypanda.co.uk/comic/comics-rss/" stringByAppendingString:[self.currentItem valueForKey:@"imagePreviewURL"]]; 

     if ([fetchedArray count] == 0) 
     { 
      [self sendToCoreDataStoreWithDate:pubDate andPreview:previewURL]; 
     } 
     else 
     { 
      int i, matches = 0; 
      for (i = 0; i < [fetchedArray count]; i++) 
      { 
       if ([[self.currentItem valueForKey:@"title"] isEqualToString:[[fetchedArray objectAtIndex:i] valueForKey:@"stripTitle"]]) 
       { 
        matches++; 
       } 
      } 

      if (matches == 0) 
      { 
       [self sendToCoreDataStoreWithDate:pubDate andPreview:previewURL]; 
      } 
     } 
     previewURL = nil; 
     [previewURL release]; 
     [fetchedArray release]; 
    } 
    self.currentString = nil; 
} 

- (void)sendToCoreDataStoreWithDate:(NSDate*)pubDate andPreview:(NSString*)previewURL { 
    NSManagedObjectContext *context = [fetchedResultsController managedObjectContext]; 
    NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity]; 
    newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context]; 
    [newManagedObject setValue:[NSDate date] forKey:@"timeStamp"]; 
    [newManagedObject setValue:[[currentItem valueForKey:@"title"] description] forKey:@"stripTitle"]; 
    [newManagedObject setValue:[[currentItem valueForKey:@"credits"] description] forKey:@"stripCredits"]; 
    [newManagedObject setValue:[[currentItem valueForKey:@"imageURL"] description] forKey:@"stripImgURL"]; 
    [newManagedObject setValue:[[currentItem valueForKey:@"link"] description] forKey:@"stripURL"]; 
    [newManagedObject setValue:pubDate forKey:@"stripPubDate"]; 
    [newManagedObject setValue:@"NO" forKey:@"stripBookmark"]; 
    //**THE NEW SYSTEM** 
    NSString *destinationPath = [(AndyPadAppDelegate *)[[UIApplication sharedApplication] delegate] applicationDocumentsDirectory]; 
    NSString *guidPreview = [[NSProcessInfo processInfo] globallyUniqueString]; 
    NSString *guidImage = [[NSProcessInfo processInfo] globallyUniqueString]; 
    NSString *dpPreview = [destinationPath stringByAppendingPathComponent:guidPreview]; 
    NSString *dpImage = [destinationPath stringByAppendingPathComponent:guidImage]; 
    NSData *previewD = [NSData dataWithContentsOfURL:[NSURL URLWithString:previewURL]]; 
    NSData *imageD = [NSData dataWithContentsOfURL:[NSURL URLWithString:[currentItem valueForKey:@"imageURL"]]]; 
    //NSError *error = nil; 
    [[NSFileManager defaultManager] createFileAtPath:dpPreview contents:previewD attributes:nil]; 
    [[NSFileManager defaultManager] createFileAtPath:dpImage contents:imageD attributes:nil]; 
    //TODO: BETTER ERROR HANDLING WHEN COMPLETED APP 
    [newManagedObject setValue:dpPreview forKey:@"stripLocalPreviewPath"]; 
    [newManagedObject setValue:dpImage forKey:@"stripLocalImagePath"]; 
    [newManagedObject release]; 
    before++; 
    [self.currentItem removeAllObjects]; 
    self.currentItem = nil; 
    [self.currentItem release]; 
    [xmlParserPool drain]; 
    self.xmlParserPool = [[NSAutoreleasePool alloc] init]; 

}  

[EDIT] @Rog 私は一緒に遊んでみましたNSOperationしかし、これまでの運は、同じ私、oryは、すべてのすべての約256k、同じアイテムを構築します。ここでは、コードです:あなたの問題はここにある

- (void)sendToCoreDataStoreWithDate:(NSDate*)pubDate andPreview:(NSString*)previewURL 
{ 
    NSManagedObjectContext *context = [fetchedResultsController managedObjectContext]; 
    NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity]; 

    newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context]; 


    [newManagedObject setValue:[NSDate date] forKey:@"timeStamp"]; 
    [newManagedObject setValue:[[currentItem valueForKey:@"title"] description] forKey:@"stripTitle"]; 
    [newManagedObject setValue:[[currentItem valueForKey:@"credits"] description] forKey:@"stripCredits"]; 
    [newManagedObject setValue:[[currentItem valueForKey:@"imageURL"] description] forKey:@"stripImgURL"]; 
    [newManagedObject setValue:[[currentItem valueForKey:@"link"] description] forKey:@"stripURL"]; 
    [newManagedObject setValue:pubDate forKey:@"stripPubDate"]; 
    [newManagedObject setValue:@"NO" forKey:@"stripBookmark"]; 

NSString *destinationPath = [(AndyPadAppDelegate *)[[UIApplication sharedApplication] delegate] applicationDocumentsDirectory]; 
NSString *guidPreview = [[NSProcessInfo processInfo] globallyUniqueString]; 
NSString *guidImage = [[NSProcessInfo processInfo] globallyUniqueString]; 
NSString *dpPreview = [destinationPath stringByAppendingPathComponent:guidPreview]; 
NSString *dpImage = [destinationPath stringByAppendingPathComponent:guidImage]; 

//Create an array and send the contents off to be dispatched to an operation queue 
NSArray *previewArray = [NSArray arrayWithObjects:dpPreview, previewURL, nil]; 
NSOperation *prevOp = [self taskWithData:previewArray]; 
[prevOp start]; 

//Create an array and send the contents off to be dispatched to an operation queue 
NSArray *imageArray = [NSArray arrayWithObjects:dpImage, [currentItem valueForKey:@"imageURL"], nil]; 
NSOperation *imagOp = [self taskWithData:imageArray]; 
[imagOp start]; 

[newManagedObject setValue:dpPreview forKey:@"stripLocalPreviewPath"]; 
[newManagedObject setValue:dpImage forKey:@"stripLocalImagePath"]; 
//[newManagedObject release]; **recommended by stackoverflow answer 
before++; 
[self.currentItem removeAllObjects]; 
self.currentItem = nil; 
[self.currentItem release]; 
[xmlParserPool drain]; 
self.xmlParserPool = [[NSAutoreleasePool alloc] init]; 


} 

- (NSOperation*)taskWithData:(id)data 
{ 
    NSInvocationOperation* theOp = [[[NSInvocationOperation alloc] initWithTarget:self 
                     selector:@selector(threadedImageDownload:) 
                      object:data] autorelease]; 
    return theOp; 
} 

- (void)threadedImageDownload:(id)data 
{ 
    NSURL *urlFromArray1 = [NSURL URLWithString:[data objectAtIndex:1]]; 
    NSString *stringFromArray0 = [NSString stringWithString:[data objectAtIndex:0]]; 
    NSData *imageFile = [NSData dataWithContentsOfURL:urlFromArray1]; 
    [[NSFileManager defaultManager] createFileAtPath:stringFromArray0 
              contents:imageFile 
              attributes:nil]; 
    //-=====0jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjmn **OLEG** 
} 
+0

ええ、コードの挿入機能はちょうど私のコードでうまく再生されませんので、スニペット内にスニペットがあります。ごめんなさい。私はHTMLを責めます;) – Cocoadelica

答えて

0

AQXMLParserをご覧ください。バックグラウンドスレッドのコアデータとの統合方法を示すサンプルアプリケーションがあります。ストリーミングパーサーなので、メモリ使用量は最小限に抑えられます。

UIの2番目のMOCおよびフェッチされた結果コントローラを設定し、新しいデータを取得するために解析が完了したら再フェッチします。 (背景MOCが保存してコンテキスト間でマージするときに変更通知を登録するオプションが1つあります)

イメージの場合、非同期ダウンロードおよびプレースホルダイメージをサポートするUIImageVewには、多くのサードパーティカテゴリがあります。そのため、画像URLを解析してコアデータに格納し、その項目を表示するときに画像ビューでURLを設定します。このようにして、ユーザは全ての画像がダウンロードされるのを待つ必要はない。

+0

素晴らしいヒント、ありがとう! – Cocoadelica

+0

MOCとは何ですか? -sc – scord

+0

NSManagedObjectContextの設定されたインスタンス –

0

[newManagedObject release];

はそれを取り除くと、クラッシュが(あなたがNSManagedObjectsを放出しない消えてしまいます、Coredataはあなたのためにそれを処理します)。

乾杯、

Rogメール

+0

こんにちは、あなたの応答に感謝します。 私はこれを試してみましたが、大容量メモリの割り当てを少し減らしましたが、それでも9Mbを上回り、強制的に終了して再起動するまで多くのCFData(store)オブジェクトをハングアップさせています。 私が扱っていないものが何かにぶら下がっている可能性のある他のアイデアはありますか? – Cocoadelica

+0

私は、解析中にイメージ用のNSDataをロードしていることに気付きました。別の作業やニーズに基づいてそれを実行できる可能性はありますか?あなたは、imageURLリファレンスを使って解析を終了してから、NSOperationQueueを起動してイメージのダウンロードを開始し、それらをオブジェクトに割り当てることができます。これは、解析中のピークmem消費を下げるのに役立ちます。 – Rog

+0

または、ユーザーが要求したときにNSURLConnectionでイメージを取得するだけです。また、これらのイメージの大きさを見てください。> 1MB以上の場合は、それぞれに個別のイメージエンティティを作成してメインモデルにリレーションシップを追加することをお勧めします。サムネイルは、メインエンティティモデルにとどまるためには問題ありません。 – Rog

6

あなたはキャッシュを無効にすることができます

NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil]; 
[NSURLCache setSharedURLCache:sharedCache]; 
[sharedCache release]; 

それともクリア:

[[NSURLCache sharedURLCache] removeAllCachedResponses]; 

これはあなたの問題を解決する必要があります。

+0

これは正解でなければなりません。同じ問題で苦しんでいるほとんどの人がキャッシングそれはシーンの裏で行われます – Matt

関連する問題