2012-02-04 47 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中提出了一個解決方案,但我確信會有更好的解決方案!