У меня проблема с приложением, которое принимает 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**
}