문제

I am working on an app for a game server company and part of the app requires the user to see a list of his or her game servers and whether or not they are online, offline, how many players on them, the server name, etc. This data is all found in a PHP file hosted on the web updated from a MySQL database which when viewed, outputs JSON.

Using the code below, this doesn't seem to work. I load the view and right away get a "Thread 1: signal SIGABRT" error on the line with NSDictionary *myServer = [servers objectAtIndex:indexPath.row];. When removing indexPath.row and replacing it with either a 0 or 1, the data is displayed on the UITableView in my Storyboard, except it is displayed 4 times in a row and only for that entry in the JSON file (either 0 or 1). I can't keep it at a fixed number as the client might have 100 servers, or just 5 servers which is why I need something like indexPath.row. Below, I also attached exactly what the JSON looks like when given from the server and accessed directly from the app's code

I'd really appreciate it if someone could please let me know what the problem is and propose a solution unique to my situation to get rid of this SIGABRT error and once we do, make sure it doesn't show 4 times in the TableView like it is now.

My header file:

#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

My main file:

#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 code from server: {"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}]}}

도움이 되었습니까?

해결책

From your code, this looks like the source of error.(However, I didn't read the whole thing.)

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

and in your 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];

Try doing following to your code

- (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.
}

다른 팁

TL;DR
The reason for the crash, you're using the news.count as the number of rows in the table, yet referencing the indexPath.row in the servers array (which it isn't guaranteed to be).

There's a few things here:

Firstly, this is not a very proficient way of networking, since you support iOS5, I would suggest using the following method (or something similar) :

[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];  
}];

Secondly, I would strongly recommend the MVC model, managing data is not something for a controller - as someone has mentioned, the code at the moment isn't as easy reading as it could be (as well as maintaining it!).

Thirdly, I would recommend you employ more defensive coding, below are the points I've spotted while reading through:

  1. news, well rather [NSJSONSerialization JSONObjectWithData:data options:nil error:nil];,, isn't guaranteed to return a dictionary at all; although it is treated as such
  2. The reason for the crash, you're using the news.count as the number of rows in the table, yet referencing the indexPath.row in the servers array (which it isn't guaranteed to be).

If I were you, I'd probably start with simplifying the networking, followed by created a simple model (for example Server), have the model parse the JSON that's relevant to it. I'd go so far as to include a static method in your Server model, say 'retrieveServers', that returns an NSArray of Server objects.

That way, all your controller is doing is:

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

Rather than having a lot of irrelevant code in your controller - this will increase maintainability and readability.

If you were so inclined, you could take another step, and provide custom accessors, rather than referencing the members directly via the model, for example:

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

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

The code above is being safe with checking that the array has at least the same number of elements in it as we need, and secondly, when assigning the value to the table cell (which may be re-used, and so needs to be set to sane values for every possible branch of execution). The advantages of doing the above: readability, centralised default value, maintainability.

I apologise if this seems rather overkill (TL;DR added ;/), trying to provide some pointers, which would aid in debugging if you see no other reason to employ the tips above.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top