【问题标题】:iOS - Parse multiple XML files for each tableView rowiOS - 为每个 tableView 行解析多个 XML 文件
【发布时间】:2013-08-13 22:13:17
【问题描述】:

我有一个 UITableViewController。

我想多次调用一个 URL (http://webservices.company.nl/api?station=ut)(对于每个火车站),其中“ut”总是不同的(它是车站的代码)。而且我想每次都将结果放在一个新的 tableview 行中。 (网址返回 XML)。

要调用 URL,我使用这个:

                // Create connection
                NSURLConnection *urlConnection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat: @"http://webservices.company.nl/api?station=%@", station.stationCode]]] delegate:self];
                [urlConnection start];

然后在“connectionDidFinishLoading”中,我使用 NSXMLParser 解析 URL 内容:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:receivedDataFromURL];
[parser setDelegate:self];
[parser parse];
}

我已经实现了“didStartElement”、“didEndElement”等所有方法,并且成功读取了文件中的所有元素。

我的问题:

对表格视图中的每一行最好的方法是什么?如何将结果放在每一行中? 我不知道最好的结构是什么,因为我想异步执行。

非常感谢。

【问题讨论】:

    标签: iphone ios objective-c nsxmlparser


    【解决方案1】:

    这里的模式就像延迟加载图片。

    1) 创建一个像 TrainStation 这样的自定义对象,它应该有一个 NSString 站代码、一些告诉调用者它已从 Web 服务初始化的函数的 BOOL 属性,以及一个提供块完成处理程序的 init 方法。

    // TrainStation.h
    @interface TrainStation : NSObject
    
    @property (strong, nonatomic) NSString *stationCode;  // your two character codes
    @property (strong, nonatomic) id stationInfo;  // stuff you get from the web service
    @property (strong, nonatomic) BOOL hasBeenUpdated;
    @property (copy, nonatomic) void (^completion)(BOOL);
    
    - (void)updateWithCompletion:(void (^)(BOOL))completion;
    @end
    

    2) 完成处理程序启动一个 NSURLConnection,保存完成块以供稍后解析完成...

    // TrainStation.m
    - (void)updateWithCompletion:(void (^)(BOOL))completion {
    
        self.completion = completion;
        NSURL *url = // form this url using self.stationCode
        NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
    
        [NSURLConnection sendAsynchronousRequest:self queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
    
        }];
    }
    
    // TrainStation does it's own parsing, then
    - (void)parserDidEndDocument:(NSXMLParser *)parser
    
        self.hasBeenUpdated = YES;
        self.completion(YES);
        // when you hold a block, nil it when you're through with it
        self.completion = nil;
    }
    

    3) 包含表格的视图控制器需要知道表格视图单元格随心所欲地来去去去,这取决于滚动,因此 Web 结果的唯一安全位置是模型(TrainStations 数组)

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
        // normal stuff, dequeue cell, etc.
    
        // the interesting part
    
        TrainStation *trainStation = self.array[indexPath.row];
        if ([trainStation hasBeenUpdated]) {
            cell.detailTextLabel.text = [trainStation.stationInfo description];
            // that's just a shortcut.  teach your train station how to produce text about itself
        } else { // we don't have station info yet, but we need to return from this method right away
            cell.detailTextLabel.text = @"";
            [trainStation updateWithCompletion:^(id parse, NSError *) {
                // this runs later, after the update is finished.  the block retains the indexPath from the original call
                if ([[tableView indexPathsForVisibleRows] containsObject:indexPath]) {
                    // this method will run again, but now trigger the hasBeenUpdated branch of the conditional
                    [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation: UITableViewRowAnimationAutomatic];
                }
            }];
        }
        return cell;
    }
    

    【讨论】:

    • 谢谢,我明白了。但我必须解析 XML 数据而不是 JSON。所以我有 didStartElement、didEndElement(几个方法)。我必须把这些方法放在哪里?在 TrainStation 对象中?我如何才能“等待”这些方法完成?
    • 啊,好点,我应该注意到解析器改变了一些东西。 xml 解析器协议略有不同,但您可以在概念上使用相同的想法。由于解析器不提供块接口,因此您需要将调用者的“完成”块复制到属性中,然后在您发现 - 作为解析器的委托 - 完成时调用它。
    • +1 但请确保取消对已滚动到屏幕外的单元格的请求。如果用户快速滚动表格视图,您不希望可见单元格等待大量请求已滚动完成的单元格。确保活动的网络请求是针对可见单元的请求,否则您最终会得到一个非常缓慢的 UI。如果您查看大多数 async UIImageView 类别(您是对的,这是正确的类比),它们就是这样做的:每个单元格执行一次操作,取消之前对出列单元格的任何请求。
    • 谢谢,我去试试!
    【解决方案2】:

    有几个注意事项:

    1. 您可能希望将这些请求中的每一个都设为自己的对象,以便让它们同时运行。正确的做法大概是custom operation 换成NSOperationQueue 来封装XML 的下载和解析。这里有几个注意事项:

      • 您应该进行此操作,以便它可以operate concurrently

      • 您应该进行操作respond to cancellation 事件。

      • 注意,如果您使用自己的 NSURLConnection 和自己的 NSURLConnectionDataDelegate 方法来执行自己的 NSOperation,那么您必须在适当的运行循环中安排它来做一些愚蠢的事情。我通常用自己的 runloop 创建一个单独的线程,但我看到很多人只是在做:

        NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
        [connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
        [connection start];
        
    2. 你可能想要实现一个缓存机制:

      • 至少,您希望将响应缓存到内存中(例如NSCache),这样如果您向下滚动然后向上滚动,它就不需要重新发出它刚刚发送的请求;

      • 根据您的应用程序的需要,您可能还需要一个持久存储缓存。在这种特殊情况下,您可能不会,但在这类情况下,这是一个常见的考虑因素。

    3. 鉴于您的问题的网络密集型性质,我会确保您在网络现实、真实世界(和最坏情况)场景中测试您的应用程序。在模拟器上,您可以使用“Hardware IO Tools”中的“Network Link Conditioner”来实现这一点(可从“Xcode”菜单中选择“Open Developer Tool”-“More Developer Tools”)。如果您安装了“Network Link Conditioner”,您就可以让您的模拟器模拟各种网络体验(例如良好的 3G 连接、较差的边缘连接等)。

    无论如何,把这些放在一起,这是一个对每一行都执行 XML 请求的示例(在这种情况下,是在 Yahoo 的天气服务上查找一个城市的温度)。

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        static NSString *cellIdentifier = @"Cell";
        CityCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    
        // try to retrieve the cell from the cache
    
        NSString *key = self.objects[indexPath.row];
        City *cityFromCache = [self.cache objectForKey:key];
        if (cityFromCache)
        {
            // if successful, use the data from the cache
    
            cell.textLabel.text = cityFromCache.temperature;
            cell.detailTextLabel.text = cityFromCache.name;
        }
        else
        {
            // if we have a prior operation going for this cell (i.e. for a row that has
            // since scrolled off the screen), cancel it so the display of the current row
            // is not delayed waiting for data for rows that are no longer visible;
            // obviously, for this to work, you need a `weak` property for the operation
            // in your `UITableViewCell` subclass
    
            [cell.operation cancel];
    
            // re-initialize the cell (so we don't see old data from dequeued cell while retrieving new data)
    
            cell.textLabel.text = nil;
            cell.detailTextLabel.text = nil;
    
            // initiate a network request for the new data; when it comes in, update the cell
    
            CityOperation *operation = [[CityOperation alloc] initWithWoeid:key successBlock:^(City *city) {
    
                // see if the cell is still visible
    
                UITableViewCell *updateCell = [tableView cellForRowAtIndexPath:indexPath];
    
                // if the cell for this row is still visible, update it
    
                if (updateCell)
                {
                    updateCell.textLabel.text = city.temperature;
                    updateCell.detailTextLabel.text = city.name;
                    [updateCell setNeedsLayout];
                }
    
                // let's save the data in our cache, too
    
                [self.cache setObject:city forKey:key];
            }];
    
            // in our custom cell subclass, I'll keep a weak reference to this operation so
            // we can cancel it if I need to
    
            cell.operation = operation;
    
            // initiate the request
    
            [self.queue addOperation:operation];
        }
    
        return cell;
    }
    

    在实践中可能会将一些逻辑转移到我的单元子类中,但希望这能说明这个想法。

    在概述了您的问题的答案后,我必须承认,当您描述您想要做的事情时,我立即被完全不同的设计所吸引。例如。我可能会启动一个异步进程,它会执行一堆 XML 请求、更新数据库、向我的表视图发布通知,让它知道何时插入了数据。但这与您所要求的内容有更根本的不同,所以我没有这样做。但可能值得退后一步考虑整体架构。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-02
    • 2012-07-08
    • 1970-01-01
    相关资源
    最近更新 更多