Накопление памяти при разборе XML в хранилище базовых данных с использованием NSXMLParser - PullRequest
3 голосов
/ 19 февраля 2011

У меня проблема с приложением, которое принимает XML-фид, анализирует его и сохраняет результаты в Core Data.

Проблема возникает только при самом первом запуске приложения, когда в магазине ничего нет, а весь фид анализируется и сохраняется. Проблема заключается в том, что объемы памяти увеличиваются и увеличиваются до тех пор, пока при 50% попыток не произойдет сбой приложения, обычно на уровне около 10 МБ. Выделенные объекты кажутся объектами CFData (store), и я не могу найти способ заставить их освободиться. Если вы можете запустить его один раз и успешно выполнить синтаксический анализ и сохранить данные ядра, то при каждом последующем запуске все в порядке, использование памяти никогда не превышает 2,5 МБ

.

Вот общий подход, который у меня есть, прежде чем мы перейдем к коду: Получить канал в объект NSData. Используйте NSFileManager, чтобы сохранить его в виде файла. Создайте URL из пути к файлу и передайте его методу parseXMLFile :. Делегат это сам. При достижении синтаксического анализатора: didStartElement: namespaceURI: qualName: attribute: я создаю словарь для сбора данных из нужных мне тегов. Parser: foundCharacters: метод сохраняет содержимое тега в строку Затем метод parser: didEndElement: ... определяет, нужен ли нам тег. Если это так, он добавляет его в словарь, если нет, то игнорирует его. Как только он достигает конца элемента, он вызывает метод для добавления его в основное хранилище данных.

Из приведенного здесь примера кода и постов других людей кажется, что в общем подходе нет ничего неправильного.

Код ниже, он взят из более широкого контекста контроллера представления, но я пропустил все, что не имеет прямого отношения.

С точки зрения вещей, которые я пробовал: Фид XML, нет возможности конвертировать в JSON, извините. Это не изображения, которые можно найти и сохранить в области общих документов.

Подсказки относительно того, что это может быть: Это запись, которую я получаю от Instruments о самых больших выделенных вещах (256 КБ за единицу):

Адрес объекта Категория Время создания Живой размер Ответственный Ответственный абонент библиотеки

1 0xd710000 CFData (хранилище) 16024774912 • 262144 CFNetwork URLConnectionClient :: clientDidReceiveData ( _CFData сопз *, 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, но пока не повезло, тот же я, или Ой, строил из одних и тех же предметов, всего около 256К каждый. Вот код:

- (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**
}

Ответы [ 3 ]

7 голосов
/ 31 марта 2011

Вы можете отключить кэширование:

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

или очистить его:

[[NSURLCache sharedURLCache] removeAllCachedResponses];

Это должно решить вашу проблему.

1 голос
/ 07 сентября 2011

Посмотрите на AQXMLParser.Существует пример приложения, в котором показано, как интегрировать его с базовыми данными в фоновом потоке.Это потоковый синтаксический анализатор, поэтому использование памяти минимально.

Установите второй контроллер MOC и извлеченный контроллер результатов для пользовательского интерфейса и выполните повторное получение после завершения анализа, чтобы получить новые данные.(одним из вариантов является регистрация уведомлений об изменениях, когда фоновые MOC сохраняются для слияния между контекстами)

Для изображений в UIImageVew есть ряд сторонних категорий, которые поддерживают асинхронную загрузку и изображения-заполнители.Таким образом, вы должны проанализировать URL-адрес изображения, сохранить его в Core Data, а затем задать URL-адрес в представлении изображения при просмотре этого элемента.Таким образом, пользователю не нужно ждать загрузки всех изображений.

0 голосов
/ 20 февраля 2011

Ваша проблема здесь:

[newManagedObject release];

Избавьтесь от этого, и сбои исчезнут (вы не выпускаете NSManagedObjects, Coredata справится с этим за вас).

Приветствия,

Rog

...