2

我有一个 UITableViewController。

我想多次(对于每个火车站)调用一个 URL( http://webservices.company.nl/api?station=ut ),其中“ut”总是不同的(它是车站的代码)。我想每次都将结果放在一个新的 tableview 行中。(URL 返回 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”等所有方法,它成功地读取了文件中的所有元素。

我的问题:

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

提前谢谢了。

4

2 回答 2

2

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

1) 创建一个像 TrainStation 这样的自定义对象,它应该有一个 NSString 站代码、一些函数的 BOOL 属性,告诉调用者它是从 Web 服务初始化的,以及一个提供块完成处理程序的 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;
}
于 2013-08-14T00:55:03.237 回答
1

有几个考虑:

  1. 您可能希望将这些请求中的每一个都设为自己的对象,以便让它们同时运行。正确的做法可能是一个自定义操作NSOperationQueue封装 XML 的下载和解析。这里有几个考虑:

    • 您应该进行此操作,以便它可以同时操作

    • 您应该使操作响应取消事件。

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

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

    • 至少,您希望将响应缓存到内存中(例如 a 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 请求、更新数据库、向我的表视图发布通知,让它知道何时插入了数据。但这与您所要求的内容有更根本的不同,所以我没有这样做。但可能值得退后一步考虑整体架构。

于 2013-08-14T02:55:03.440 回答