【问题标题】:SIGABRT accessing NSArray with indexPath.rowSIGABRT 使用 indexPath.row 访问 NSArray
【发布时间】:2013-07-31 18:39:13
【问题描述】:

我正在为一家游戏服务器公司开发一个应用程序,该应用程序的一部分要求用户查看他或她的游戏服务器的列表,以及它们是否在线、离线、其中有多少玩家、服务器名称等。这些数据都可以在 Web 上托管的 PHP 文件中找到,该文件从 MySQL 数据库更新,查看时会输出 JSON。

使用下面的代码,这似乎不起作用。我加载视图并立即在NSDictionary *myServer = [servers objectAtIndex:indexPath.row]; 的行上收到“线程 1:信号 SIGABRT”错误。删除 indexPath.row 并将其替换为 0 或 1 时,数据将显示在我的 Storyboard 中的 UITableView 上,除了它连续显示 4 次并且仅针对 JSON 文件中的该条目(0 或1)。我不能把它保持在一个固定的数字,因为客户端可能有 100 台服务器,或者只有 5 台服务器,这就是为什么我需要像 indexPath.row 这样的东西。下面,我还附上了从服务器提供并直接从应用程序代码访问的 JSON 的样子

如果有人能告诉我问题出在哪里,并提出一个针对我的情况的独特解决方案来消除这个 SIGABRT 错误,我将非常感激,一旦我们这样做了,请确保它不会显示 4 次像现在这样的 TableView。

我的头文件:

#import <UIKit/UIKit.h>
#import "ServerDetailViewController.h"

@interface SecondViewController : UITableViewController {
    IBOutlet UITableView *mainTableView;

    NSDictionary *news;
    NSMutableData *data;
}

@property (weak, nonatomic) IBOutlet UIBarButtonItem *refreshServersButton;

- (IBAction)refreshServers:(id)sender;

@end

我的主文件:

#import "SecondViewController.h"

@interface SecondViewController ()

@end

@implementation SecondViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

    NSURL *url = [NSURL URLWithString:@"REDACTED"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    data = [[NSMutableData alloc] init];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData
{
    [data appendData:theData];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

    news = [NSJSONSerialization JSONObjectWithData:data options:nil error:nil];
    [mainTableView reloadData];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    UIAlertView *errorView = [[UIAlertView alloc] initWithTitle:@"Error" message:@"Unable to load server list. Make sure you are connect to either 3G or Wi-Fi or try again later." delegate:nil cancelButtonTitle:@"Dismiss" otherButtonTitles:nil, nil];
    [errorView show];
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}

- (int)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [news count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

    UIColor *colorGreen = [UIColor colorWithRed:91.0f/255.0f green:170.0f/255.0f blue:101.0f/255.0f alpha:1.0f];
    UIColor *colorRed = [UIColor redColor];

    static NSString *CellIdentifier = @"MainCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }

    UILabel *serverName = (UILabel *)[cell viewWithTag:100];
    UILabel *serverPlayers = (UILabel *)[cell viewWithTag:101];
    UILabel *serverStatus = (UILabel *)[cell viewWithTag:102];
    UILabel *serverOfflineName = (UILabel *)[cell viewWithTag:103];

    serverPlayers.textColor = [UIColor grayColor];

    NSDictionary *resultDict = [news objectForKey:@"result"];
    NSArray *servers = [resultDict objectForKey:@"servers"];
    NSDictionary *myServer = [servers objectAtIndex:indexPath.row];

    NSString *titleOfServer = [myServer objectForKey:@"title"];
    NSNumber *statusOfServer = [NSNumber numberWithInt:[[myServer objectForKey:@"status"] intValue]];
    NSNumber *playersOnServer = [NSNumber numberWithInt:[[myServer objectForKey:@"players"] intValue]];

  if ([[statusOfServer stringValue] isEqualToString:@"0"]) {

      serverName.text = @"";
      serverOfflineName.text = titleOfServer;
      serverStatus.textColor = colorRed;
      serverStatus.text = @"OFFLINE";
      serverPlayers.text = @"";
      cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

  } else if ([[statusOfServer stringValue] isEqualToString:@"1"]) {

      serverName.text = titleOfServer;
      serverOfflineName.text = @"";
      serverStatus.textColor = colorGreen;
      serverStatus.text = @"ONLINE";
      serverPlayers.text = [playersOnServer stringValue];
      cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

  } else if ([[statusOfServer stringValue] isEqualToString:@"2"]) {

      serverName.text = @"";
      serverOfflineName.text = titleOfServer;
      serverStatus.textColor = [UIColor blueColor];
      serverStatus.text = @"BUSY";
      serverPlayers.text = @"";
      cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;


  } else if ([[statusOfServer stringValue] isEqualToString:@"3"]) {

      serverName.text = @"";
      serverOfflineName.text = titleOfServer;
      serverStatus.textColor = [UIColor grayColor];
      serverStatus.text = @"SUSPENDED";
      serverPlayers.text = @"";
      cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;


  } else if ([[statusOfServer stringValue] isEqualToString:@"-1"]) {

      serverName.text = @"";
      serverOfflineName.text = titleOfServer;
      serverStatus.textColor = [UIColor orangeColor];
      serverStatus.text = @"CRITICAL ERROR";
      serverPlayers.text = @"";
      cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;


  }

    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    ServerDetailViewController *detail = [self.storyboard instantiateViewControllerWithIdentifier:@"detail"];
    [self.navigationController pushViewController:detail animated:YES];
}

- (IBAction)refreshServers:(id)sender {
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

    NSURL *url = [NSURL URLWithString:@"REDACTED"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

@end

来自服务器的 JSON 代码:{"status":"OK","error":"","debug":"2 server(s)","result":{"servers":[{"id":1,"title":"Test","players":0,"slots":10,"status":3},{"id":2,"title":"Creative Spawn","players":0,"slots":5,"status":-1}]}}

【问题讨论】:

  • 如果我将 objectAtIndex 设置为固定数字(在本例中为 1),则屏幕显示显示每个服务器如何显示 4 次。 i.stack.imgur.com/K0cay.png
  • 首先,请阅读并包含错误消息的其余部分。其次,将来,请在此处的问题正文中包含您的代码,而不是在场外托管。第三,请花一些时间将您的代码精简为the minimum necessary to reproduce the problem。在这个过程中,您经常会发现自己解决了自己的问题。
  • 那么 indexPath.row 失败时的值是多少? (NSLog indexPath.row.)
  • @HotLicks 在 NS 日志输出中其值从 0 上升到 3。
  • 但是你的数组只有两个元素?

标签: ios objective-c cocoa-touch nsdictionary sigabrt


【解决方案1】:

从您的代码来看,这看起来像是错误的来源。(但是,我没有阅读全部内容。)

- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [news count]; //counted number of items in your whole json object.
}

在你的 cellForRowAtIndexPath:(NSIndexPath *)indexPath

NSDictionary *resultDict = [news objectForKey:@"result"];
NSArray *servers = [resultDict objectForKey:@"servers"];
// you used a different array(an item of the whole json array).
// Since news object has more items than servers, it caused an out of bound here. 
NSDictionary *myServer = [servers objectAtIndex:indexPath.row];

尝试对您的代码执行以下操作

- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSDictionary *resultDict = [news objectForKey:@"result"];
    NSArray *servers = [resultDict objectForKey:@"servers"];
    return [servers count]; //counted number of items in your whole json object.
}

【讨论】:

  • 非常感谢!我现在明白我哪里出错了,继续努力! -迈克尔
  • 请查看@user352891 的建议,它将使您免于维护噩梦。
【解决方案2】:

TL;DR
崩溃的原因是,您使用 news.count 作为表中的行数,但引用了服务器数组中的 indexPath.row(不能保证是)。

这里有几件事:

首先,这不是一种非常熟练的联网方式,因为你支持iOS5,我建议使用以下方法(或类似的方法):

[NSURLConnection sendAsynchronousRequest:theRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {  
    NSString *dataString = [[NSString alloc] initWithBytes:[data bytes] length:[[data bytes] length] encoding:NSUTF8StringEncoding];  
}];

其次,我强烈推荐MVC model,管理数据不是控制器的事情 - 正如有人提到的那样,目前的代码并不像它应该的那样易于阅读(以及维护它! )。

第三,我建议你使用更多的防御性编码,以下是我在阅读时发现的要点:

  1. news,以及[NSJSONSerialization JSONObjectWithData:data options:nil error:nil];,完全不能保证返回字典;虽然它被这样对待
  2. 崩溃的原因是,您使用 news.count 作为表中的行数,但引用了服务器数组中的 indexPath.row(不能保证是)。

如果我是你,我可能会先简化网络,然后创建一个简单的模型(例如服务器),让模型解析与其相关的 JSON。我什至会在你的服务器模型中包含一个静态方法,比如“retrieveServers”,它返回一个服务器对象的 NSArray。

这样,你的控制器所做的就是:

[self setNews:[Server retrieveServers]];
[_tableView reloadData];

而不是在控制器中包含大量不相关的代码 - 这将提高可维护性和可读性。

如果您愿意,您可以再采取一步,提供自定义访问器,而不是直接通过模型引用成员,例如:

Server *currentServer = nil;
if( self.news.count > indexPath.row ) {
   currentServer = [_news objectAtIndex:indexPath.row];
}

[serverPlayers setText:(currentServer ? [currentServer getPlayers] : [Server defaultPlayerValue]]

上面的代码可以安全地检查数组中的元素数量是否与我们需要的相同,其次,在将值分配给表格单元格时(可能会被重复使用,因此需要为每个可能的执行分支设置为合理的值)。这样做的好处:可读性、集中的默认值、可维护性。

如果这看起来有点矫枉过正(TL;DR 添加 ;/),我很抱歉,试图提供一些指针,如果您认为没有其他理由使用上述提示,这将有助于调试。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多