带有完成块的自定义XML解析器类

3
我正在使用TBXML创建一个XML解析器类。我希望这个类可以加载一个XML文档,遍历它,并返回一个字符串数组以填充表格。这应该在后台线程中进行,以避免阻塞UI。我想添加一个完成块,以便在XML解析完成时设置表格的数据源数组。
如何实现完成块?以下是我的一些代码:
Parser.m
- (NSMutableArray *)loadObjects
{
    // Create a success block to be called when the asyn request completes
    TBXMLSuccessBlock successBlock = ^(TBXML *tbxmlDocument) {
        NSLog(@"PROCESSING ASYNC CALLBACK");

        // If TBXML found a root node, process element and iterate all children
        if (tbxmlDocument.rootXMLElement)
            [self traverseElement:tbxmlDocument.rootXMLElement];
    };

    // Create a failure block that gets called if something goes wrong
    TBXMLFailureBlock failureBlock = ^(TBXML *tbxmlDocument, NSError * error) {
        NSLog(@"Error! %@ %@", [error localizedDescription], [error userInfo]);
    };

    // Initialize TBXML with the URL of an XML doc. TBXML asynchronously loads and parses the file.
    tbxml = [[TBXML alloc] initWithURL:[NSURL URLWithString:@"XML_DOC_URL"]
                               success:successBlock
                               failure:failureBlock];
    return self.array;
}


- (void)traverseElement:(TBXMLElement *)element
{    
    do {
        // if the element has child elements, process them
        if (element->firstChild) [self traverseElement:element->firstChild];

        if ([[TBXML elementName:element] isEqualToString:@"item"]) {
            TBXMLElement *title = [TBXML childElementNamed:@"title" parentElement:element];
            NSString *titleString = [TBXML textForElement:title];
            [self.array addObject:titleString];
        };

        // Obtain next sibling element
    } while ((element = element->nextSibling));
}

TableViewController.m

- (void)viewDidLoad
{
    [super viewDidLoad];

    Parser *parser = [[Parser alloc] init];
    self.products = [parser loadObjects]; 
}
1个回答

2

这样,你将得到一个空数组,对吧?那是因为loadObjects不会阻塞并在完成任务之前返回。

现在,您希望您的viewDidLoad立即返回,以便您的表可以显示。因此,在TableViewController中,您需要一个回调函数,在显示空表后进行更新管理。标准技术是使用委托。在Parser.h中定义

@protocol ParserProtocol <NSObject>
-(void)parserDidFinishLoading;
@end

在解析器接口中添加:
@property id<ParserProtocol> delegate;

请在您的successBlock中添加以下代码:

[self.delegate parserDidFinishLoading];

然后使TableViewController符合ParserProtocol并将解析器添加为属性:

@property Parser* parser;

在TableViewController.m中替换

Parser *parser = [[Parser alloc] init];
self.products = [parser loadObjects]; 

self.parser = [[Parser alloc] init];
self.products = [self.parser loadObjects]; 

并添加

-(void)parserDidFinishLoading{
    [self.tableView reloadData];
}
self.products指向了现在已经被修改的Parserarray属性。因此不需要进行额外的设置。
祝你好运,Peter。

谢谢,这很有帮助。只有两件事情:1)loadObjects仍然返回一个空数组,所以我直接设置了self.products = self.parser.array属性而不是返回的数组。2)在self.tableview reloadDatacellForRow...之间我得到了一些非常奇怪的延迟。这导致我的表格在parserDidFinishLoading被调用后15秒左右才更新。 - mnort9
我找出了延迟原因。在委托方法中,在后台线程中调用了reloadData。我不得不将reloadData替换为[[self tableView] performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO]; - mnort9
将一个属性设置为另一个类的属性self.products = self.parser.array是否可以? - mnort9
是的,像这样设置属性是完全可以的。self.parser.array 调用 array 的 getter 并返回一个指针。self.products = 使用该指针作为参数调用 products 的 setter。 - ilmiacs
是的,我没有注意到线程可能存在的问题,你说得对。但是我可能会在successBlock中调用performSelectorOnMainThread:,例如使用delegateparserDidFinishLoading。这样,切换线程的责任就只属于Parser对象本身。ViewController不应该知道其他线程的存在。 - ilmiacs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接