2012-02-04 42 views
0

这可能已经被问了很多,但我仍然输了。我需要解析从Google Reader的API中检索到的XML文件。基本上,它包含的对象,如下面:如何提高iOS代码的XML解析性能?

<object> 
    <string name="id">feed/http://developer.apple.com/news/rss/news.rss</string> 
    <string name="title">Apple Developer News</string> 
    <list name="categories"> 
     <object> 
      <string name="id">user/17999068807557229152/label/Apple</string> 
      <string name="label">Apple</string> 
     </object> 
    </list> 
    <string name="sortid">DB67AFC7</string> 
    <number name="firstitemmsec">1317836072018</number> 
    <string name="htmlUrl">http://developer.apple.com/news/</string> 
</object> 

我有尝试过的NSXMLParser和它的作品,但它实在是太慢了。也许我的代码不是最高效的,但仍然需要超过10秒才能解析并保存一个对象到Core Data中。我也看了其他几个库,但是对于这样一个小XML文件,它们的使用似乎有点复杂和沉重。

你认为我应该用什么?

谢谢。

编辑

这里解析器代码:

- (void) saveSubscription { 

    NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease]; 
    [fetchRequest setEntity: 
    [NSEntityDescription entityForName:@"Group" inManagedObjectContext:context]]; 
    [fetchRequest setPredicate: [NSPredicate predicateWithFormat: @"(id == %@)",self.currentCategoryId]]; 
    [fetchRequest setSortDescriptors: [NSArray arrayWithObject: 
             [[[NSSortDescriptor alloc] initWithKey: @"id" 
             ascending:YES] autorelease]]]; 

    NSError *error2 = nil; 
    NSArray *foundGroups = [context executeFetchRequest:fetchRequest error:&error2]; 

    if ([foundGroups count] > 0) { 
     self.currentGroupObject = [foundGroups objectAtIndex:0]; 
    } 
    else { 
     self.currentGroupObject = [NSEntityDescription insertNewObjectForEntityForName:@"Group" inManagedObjectContext:context]; 
     [self.currentGroupObject setId:self.currentCategoryId]; 
     [self.currentGroupObject setLabel:self.currentCategoryLabel]; 
    } 

    fetchRequest = [[[NSFetchRequest alloc] init] autorelease]; 
    [fetchRequest setEntity: 
    [NSEntityDescription entityForName:@"Subscription" inManagedObjectContext:context]]; 
    [fetchRequest setPredicate: [NSPredicate predicateWithFormat: @"(id == %@)", self.currentSubscriptionId]]; 
    [fetchRequest setSortDescriptors: [NSArray arrayWithObject: 
             [[[NSSortDescriptor alloc] initWithKey: @"id" 
                    ascending:YES] autorelease]]]; 

    error2 = nil; 
    NSArray *foundSubscriptions = [context executeFetchRequest:fetchRequest error:&error2]; 

    if ([foundSubscriptions count] > 0) { 
     self.currentSubscriptionObject = [foundSubscriptions objectAtIndex:0]; 
    } 
    else { 
     self.currentSubscriptionObject = [NSEntityDescription insertNewObjectForEntityForName:@"Subscription" inManagedObjectContext:context]; 
     [self.currentSubscriptionObject setId:self.currentSubscriptionId]; 
     [self.currentSubscriptionObject setTitle:self.currentSubscriptionTitle]; 
     [self.currentSubscriptionObject setHtmlURL:self.currentSubscriptionHtmlURL]; 
     NSString *faviconURL = [self favIconUrlStringFromURL:self.currentSubscriptionHtmlURL]; 
     NSString *faviconPath = [self saveFavicon:self.currentSubscriptionTitle url:faviconURL]; 
     [self.currentSubscriptionObject setFaviconPath:faviconPath]; 
     [self.currentSubscriptionObject setGroup:self.currentGroupObject]; 
     [self.currentGroupObject addSubscriptionObject:self.currentSubscriptionObject]; 
    } 

    NSError *error; 
    if (![context save:&error]) { 
     NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]); 
    } 
} 
+0

如果您要添加代码,我们可能会帮助您改进它。 – vikingosegundo 2012-02-04 10:50:28

+0

我重新命名了这个问题,因为“最好的图书馆”问题基本上只是意见,并且您在此场景中寻找特定的perf增强功能,无论它是否涉及新的库 – 2012-02-05 18:36:28

回答

7

你的分析逻辑是非常低效的 - 你说

if (string and x) do this 
if (string and y) do this 
if (string and z) do this 

而不是

if (string) 
    if (x) do this 
    if (y) do this 
    if (z) do this 
一遍又一遍地做同样的再次测试

所有这些不必要的字符串比较可能是为什么你的解析速度太慢。所有对象查找也是如此。如果你多次需要一个值,只需要一次,然后将其存储在一个变量中。

Objective C的方法调用相对缓慢,不能由编译器被优化掉,因此,如果该值不改变,你应该再调用方法,然后存储起来。

因此,举例来说,这样的:

if([elementName isEqualToString:@"string"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"id"]){ 
    if(categoryFound){ 
     categoryIdFound = YES; 
    } 
    else{ 
     subscriptionIdFound = YES; 
    } 
} 
if([elementName isEqualToString:@"string"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"title"]){ 
    subscriptionTitleFound = YES; 
} 
if([elementName isEqualToString:@"string"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"label"]){ 
    categoryLabelFound = YES; 
} 
if([elementName isEqualToString:@"string"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"htmlUrl"]){ 
    subscriptionHtmlURLFound = YES; 
} 

可以改写为这样的:

NSString *name = [attributeDict objectForKey:@"name"]; 
if([elementName isEqualToString:@"string"]) 
{ 
    if ([name isEqualToString:@"id"]) 
    { 
     if(categoryFound){ 
      categoryIdFound = YES; 
     } 
     else{ 
      subscriptionIdFound = YES; 
     } 
    } 
    else if ([name isEqualToString:@"title"]) 
    { 
     subscriptionTitleFound = YES; 
    } 
    else if ([name isEqualToString:@"label"]) 
    { 
     categoryLabelFound = YES; 
    } 
    else if ([name isEqualToString:@"htmlUrl"]) 
    { 
     subscriptionHtmlURLFound = YES; 
    } 
} 

哪个方式更有效。

+0

感谢您的建议,但是您对于查找对象的含义究竟是什么?你在说什么代码的一部分? – 2012-02-04 11:40:53

+0

[attributeDict objectForKey:@“name”] < - 这 – 2012-02-04 11:44:40

+2

我不知道这种方式更有效率。感谢您的解释。 – 2012-02-04 12:40:38

0

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { 

    if([elementName isEqualToString:@"list"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"subscriptions"]){ 
     subscriptionListFound = YES; 
    } 

    if(subscriptionListFound){ 
     if([elementName isEqualToString:@"list"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"categories"]){ 
      categoryFound = YES; 
      currentCategoryId = [[[NSMutableString alloc] init] autorelease]; 
      currentCategoryLabel = [[[NSMutableString alloc] init] autorelease]; 
     } 
     if([elementName isEqualToString:@"object"] && !subscriptionFound && !categoryFound){ 
      subscriptionFound = YES; 
      currentSubscriptionTitle = [[[NSMutableString alloc] init] autorelease]; 
      currentSubscriptionId = [[[NSMutableString alloc] init] autorelease]; 
      currentSubscriptionHtmlURL = [[[NSMutableString alloc] init] autorelease]; 
     } 
     if([elementName isEqualToString:@"string"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"id"]){ 
      if(categoryFound){ 
       categoryIdFound = YES; 
      } 
      else{ 
       subscriptionIdFound = YES; 
      } 
     } 
     if([elementName isEqualToString:@"string"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"title"]){ 
      subscriptionTitleFound = YES; 
     } 
     if([elementName isEqualToString:@"string"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"label"]){ 
      categoryLabelFound = YES; 
     } 
     if([elementName isEqualToString:@"string"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"htmlUrl"]){ 
      subscriptionHtmlURLFound = YES; 
     } 
    } 
} 

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { 

    if([elementName isEqualToString:@"list"] && !categoryFound){ 
     subscriptionListFound = NO; 
    } 

    if([elementName isEqualToString:@"list"] && categoryFound){ 
     categoryFound = NO; 
    } 

    if([elementName isEqualToString:@"object"] && !categoryFound && subscriptionFound){   
     [self saveSubscription]; 
     [[NSNotificationCenter defaultCenter] postNotificationName:@"currentSubscriptionNotification" object:currentSubscriptionTitle]; 
     subscriptionFound = NO; 
    } 

    if([elementName isEqualToString:@"string"]){ 
     if(subscriptionIdFound == YES) { 
      [currentSubscriptionId appendString:self.currentParsedCharacterData]; 
      subscriptionIdFound = NO; 
     } 
     if(subscriptionTitleFound == YES) { 
      [currentSubscriptionTitle appendString:self.currentParsedCharacterData]; 
      subscriptionTitleFound = NO; 
     } 
     if(subscriptionHtmlURLFound == YES) { 
      [currentSubscriptionHtmlURL appendString:self.currentParsedCharacterData]; 
      subscriptionHtmlURLFound = NO; 
     } 
     if(categoryIdFound == YES) { 
      [currentCategoryId appendString:self.currentParsedCharacterData]; 
      categoryIdFound = NO; 
     } 
     if(categoryLabelFound == YES) { 
      [currentCategoryLabel appendString:self.currentParsedCharacterData]; 
      categoryLabelFound = NO; 
     } 
    } 

    [self.currentParsedCharacterData setString:@""]; 
} 

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { 
    [self.currentParsedCharacterData appendString:string]; 
} 

下面的代码通过CoreData的方式保存HAV e你试过KissXML。我之前使用过它。

1

我建议你使用GDataXML。使用非常简单,而且速度非常快。欲了解更多信息,请登录how-to-read-and-write-xml-documents-with-gdataxml

我已经回答了关于如何在此堆栈溢出主题中使用GDataXML读取属性的类似问题:get-xml-response-value-with-gdataxml

+1

我同意FlexDataAdded对GDataXML,但也想添加一个链接到这个令人难以置信的有用的职位上选择XML解析器上的ios http://www.raywenderlich.com/553/how-to-chose-the-best-xml-parser-for-your-iphone-project – shawnwall 2012-02-04 14:57:44

0

如果您正在寻找最好的XML解析库,我建议你对TBXML看看(http://www.tbxml.co.uk/)..最快,最简单的..

0

我我意见,在iOS上解析XML的最佳库是TouchXML。它允许您使用xPaths来解析XML,并具有高级元素解析选项。你也可以用这个解析XHTML文档。

解析很简单:

NSData *xmlData = read your xml file 
CXMLDocument *doc = [[CXMLDocument alloc] initWithData:xmlData options:0 error:nil] 
NSArray *objects = [doc nodesForXPath:@"//object" error:nil]; 

for (CXMLElement *object in objects) { 
    NSArray *children = [object children]; 
    for(CXMLElement *child in children) { 
     if([[child name] isEqualToString:@"string"]) { 
      // you are parsing <string> element. 
      // you can obtain element attribute by: 
      NSString *name = [[child attributeForName:@"name"] stringValue]; 
      // you can obtain string between <></> tags via: 
      NSString *value = [child stringValue]; 
     } else if([[child name] isEqualToString:@"list"]) { 
      // you are parsing <list> element. 
     } else if ... 
    } 
} 
0

已经开发了类似的需求为你的一些应用程序后,我会全力推荐用于解析XML或多或少这样的AQToolkit

我通常设置:

  • 创建一个单独的队列,使用任一GCD OG NSOperationsQueue
  • 设置使用HTTPMessage AQGZipInputStream

防爆一个输入流和充足代码:

HTTPMessage *message = [HTTPMessage requestMessageWithMethod:@"GET" url:url version:HTTPVersion1_1]; 
[message setUseGzipEncoding:YES];  
AQGzipInputStream *inputstream = [[AQGzipInputStream alloc] initWithCompressedStream:   [message inputStream]]; 
  • 手流到一个单独的解析器代表,它创建了一个独立的NSManagedObjectContext,并合并的变更保存到主要的NSManagedObjectContext(NSManagedObject不是线程安全的!)

例用于初始化上下文的代码以及添加用于合并的通知:

-(void)parserDidStartDocument:(AQXMLParser *)parser 
{ 
    self.ctx=[[NSManagedObjectContext alloc] init]; 
    [self.ctx setMergePolicy: NSMergeByPropertyObjectTrumpMergePolicy]; 
    [self.ctx setPersistentStoreCoordinator: [Database db].persistentStoreCoordinator]; 
    NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter]; 
    [dnc addObserver:self selector:@selector(mergeContextChanges:) name:NSManagedObjectContextDidSaveNotification object:self.ctx]; 
    parsedElements = 0; 
} 

- (void)mergeContextChanges:(NSNotification *)notification{ 
    SEL selector = @selector(mergeHelper:); 
    [self performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES]; 
} 

- (void)mergeHelper:(NSNotification*)saveNotification 
{ 
// Fault in all updated objects 
NSArray* updates = [[saveNotification.userInfo objectForKey:@"updated"] allObjects]; 
for (NSInteger i = [updates count]-1; i >= 0; i--) 
{ 
    [[[Database db].managedObjectContext objectWithID:[[updates objectAtIndex:i] objectID]] willAccessValueForKey:nil]; 
} 

// Merge 
[[Database db].managedObjectContext mergeChangesFromContextDidSaveNotification:saveNotification]; 
} 

在我看来,选择正确的解析器是mo对于庞大的数据集是至关重要的如果你的数据集是可管理的,那么你可以从体面的实现中获得很多收益。使用任何基于libxml的解析器,并在接收数据时解析数据块,将使您在下载数据后解析数据,从而显着提高性能。

根据你的数据源,libz可能会抛出Z_BUF_ERROR(至少在模拟器中)。我已经在AQToolkit的pull-request中提出了一个解决方案,但我确信会有更好的解决方案!