在 UISegment 之间快速切换时应用程序崩溃

Posted

技术标签:

【中文标题】在 UISegment 之间快速切换时应用程序崩溃【英文标题】:Application Crash on Fast switching between UISegment 【发布时间】:2013-06-20 04:12:46 【问题描述】:

我有一个段和一个表视图,我必须重新加载表视图中的数据,但当我快速切换段按钮时,应用程序崩溃了。

在段上,我有三个按钮,在它们的操作上,我触发了对 url 的请求并从该 url 获取 json(3 个不同的 url 分别返回 10、17 和 8 个结果为 json)。

我有一个自定义表格单元格,其中包含图像,我正在尝试延迟加载这些图像。

在 JSON 结果中,每个 json 对象都有不同的键,例如 name、id、imageurl 等。

id 在整个应用程序中是唯一的,我正在尝试加载图像并根据该唯一 id 将图像保存在本地内存中,这样我就可以显示已兑现的图像,而无需在分段更改时重新下载图像,因为所有三个部分的一些条目都是相同的。

这是我的完整代码

- (IBAction)segmentedControlIndexChanged 
NSString *offerRequestUrl = nil;
switch (self.mySegment.selectedSegmentIndex) 
    case 0:
        offerRequestUrl = @"url_one";
        self.feedRequestUrl = [NSURL URLWithString:offerRequestUrl];
        break;
    case 1:
        offerRequestUrl = @"url_two";;
        self.feedRequestUrl = [NSURL URLWithString:offerRequestUrl];
        break;
    case 2:
        offerRequestUrl = @"url_three";;
        self.feedRequestUrl = [NSURL URLWithString:offerRequestUrl];
        break;

    default:
        break;

  [self parseJSONWithURL:self.feedRequestUrl];

- (void) parseJSONWithURL:(NSURL *) jsonURL
 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
        NSData* data = [NSData dataWithContentsOfURL: jsonURL];
        [self performSelectorOnMainThread:@selector(fetchedData:) withObject:data waitUntilDone:YES];
    );


- (void)fetchedData:(NSData *)responseData 
//parse out the json data
NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseData
                                                     options:kNilOptions
                                                       error:&error];

self.feedsArray = [json objectForKey:@"result"];
[self.myTableView reloadData];
 

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
 
 int count = self.feedsArray.count;

// if there's no data yet, return enough rows to fill the screen
if (count == 0)

    return 1;

return count;

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

// customize the appearance of table view cells
//
static NSString *CellIdentifier = @"MyTableCell";
static NSString *PlaceholderCellIdentifier = @"PlaceholderCell";

// add a placeholder cell while waiting on table data
int nodeCount = [self.feedsArray count];

if (nodeCount == 0 && indexPath.row == 0)

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:PlaceholderCellIdentifier];

    if (cell == nil)
    
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
                                      reuseIdentifier:PlaceholderCellIdentifier];
        cell.detailTextLabel.textAlignment = UITextAlignmentCenter;
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
    

    cell.detailTextLabel.text = @"Loading . . .";

    [self performSelector:@selector(checkAndDisplayAlert) withObject:self afterDelay:10.0];

    return cell;


CustomCell *cell = (CustomCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (nodeCount > 0)
        
    // Configure the cell...
    cell.carImageView.image = [UIImage imageNamed:@"Placeholder"]; //acts as default image
    if([self.imageDict  valueForKey:[NSString stringWithFormat:@"%@", self.feedsArray[indexPath.row][@"id_str"]]]==nil)
    
        [NSThread detachNewThreadSelector:@selector(displayingSmallImage:) toTarget:self withObject:indexPath];
     else 
        cell.carImageView.image = [self.imageDict valueForKey:[NSString stringWithFormat:@"%@", self.feedsArray[indexPath.row][@"id_str"]]];
    

    cell.imageView.clipsToBounds = YES;
    cell.titleLabel.text = self.feedsArray[indexPath.row][@"title_key"];


return cell;
 

  - (void) displayingSmallImage:(NSIndexPath *)indexPath

NSString *imageUrl = self.feedsArray[indexPath.row][@"icon_url"];

NSURL *url = [NSURL URLWithString:imageUrl];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
if(image == nil)

    image = [UIImage imageWithContentsOfFile:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Placeholder"]];

[self.imageDict setObject:image forKey:[NSString stringWithFormat:@"%@", self.feedsArray[indexPath.row][@"id_str"]]];
[self performSelectorOnMainThread:@selector(imageReceived:) withObject:indexPath waitUntilDone:NO];
  

   - (void) imageReceived:(NSIndexPath *)indexPath
 
UIImage *image = (UIImage *)[self.imageDict objectForKey: [NSString stringWithFormat:@"%@", self.feedsArray[indexPath.row][@"id_str"]]];
UIImageView *imgs = (UIImageView *)[[self.myTableView cellForRowAtIndexPath:indexPath] viewWithTag:IMG_TAG];
[imgs setImage:image];
[self.myTableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
  

  - (void) checkAndDisplayAlert

if (!self.feedsArray && self.isAlertPresent == NO) 
    UIAlertView *nilAlert = [[UIAlertView alloc] initWithTitle:@"Sorry!"
                                                       message:@"No result returned from server."
                                                      delegate:self
                                             cancelButtonTitle:@"OK"
                                             otherButtonTitles:nil];
    [nilAlert setTag:kNilAlertTag];
    self.isAlertPresent = YES;
    [nilAlert show];

 

我要做的主要事情是

    懒惰下载图片

    根据唯一ID缓存图片到本地内存

    检查已下载的图片字典并显示它

    如果可能,将这些下载的图像保存在本地内存中(nsuserdefaults 等),这样如果用户重新启动应用程序,图像也会在那里并且可以根据其唯一 ID 显示,无需再次下载。

任何帮助将不胜感激。

【问题讨论】:

也许我没有完全遵循……这四项“您必须做的主要事情”不正是您实现目标所需的吗?您是否尝试过实现其中的任何一个? 是的,我正在尝试前三名,但应用程序崩溃了。 愿意分享崩溃位置的转储吗?在旁注中,一种超级懒惰的方法是禁用 UITabBar 按钮,直到初始加载完成。这样,当您弄清楚#4时,您就可以开始了。再次......那是纯粹的创可贴。 进入主程序,现在它抛出错误 *** 由于未捕获的异常“NSRangeException”而终止应用程序,原因:“-[__NSCFArray objectAtIndex:]: index (3) beyond bounds (3)” *** 第一次抛出调用堆栈:(0x1615012 0x143ae7e 0x1614deb 0x16097e0 0x168e3a8 0xadc6 0xe85805 0xe85764 0x95b065b7 0x95af0d4e)libc++abi.dylib:终止调用抛出异常 【参考方案1】:

请贴出错误日志,如果是数组越界异常我怀疑这两种方法

- (void)fetchedData:(NSData *)responseData & your tableViewDatasource

要查找导致此崩溃的代码行,请执行此操作。转到左侧的断点选项卡,然后单击底部的 +。

然后你会得到这样的东西:

刚刚完成,再次运行您的代码并告诉我们是哪一行导致了异常。

我明白你的问题了,为什么要在这个函数中从数组中取URL

  - (void) displayingSmallImage:(NSIndexPath *)indexPath

您可以像这样从 cellForRowAtIndex 传递要加载到该函数的 Url:

    NSArray *argsToPass=[NSArray arrayWithObjects:indexPath,self.feedsArray[indexPath.row][@"icon_url"]];

    [NSThread detachNewThreadSelector:@selector(displayingSmallImage:) toTarget:self withObject:argsToPass];

你的函数被修改为:

 - (void) displayingSmallImage:(NSArray *)args


  if([args count] == 0)
 
NSString *imageUrl = [args objectAtIndex:1];

NSIndexPath *indexPath=[args objectAtIndex:0]; 

NSURL *url = [NSURL URLWithString:imageUrl];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
if(image == nil)

    image = [UIImage imageWithContentsOfFile:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Placeholder"]];

[self.imageDict setObject:image forKey:[NSString stringWithFormat:@"%@", self.feedsArray[indexPath.row][@"id_str"]]];
[self performSelectorOnMainThread:@selector(imageReceived:) withObject:indexPath waitUntilDone:NO];
  
 

【讨论】:

谢谢,我会按照这个方法,一会儿分享结果 控件在-(void)displaySmallImage:(NSIndexPath *)indexPath方法并上线[self.imageDict setObject:image forKey:[NSString stringWithFormat:@"%@", self.feedsArray[ indexPath.row][@"id_str"]]];显示 sigabert 错误 根据您的代码所说,您正在尝试从字典数组中访问字典,对吗?我想问题出在你的 self.feedsArray 上,它在传递的索引处不包含对象。 我看到您尝试延迟加载图像并且还想在本地缓存它们,请参阅我之前的回答 ***.com/questions/15865809/… 关于异步加载图像。 每次触发 Web 服务请求时,Feeds 数组都会更新,并且在所有三个 JSON 响应中很少有值/对象相同,但 id 是唯一的,并且对于所有三个来说都是相同的,并且所有文本都会正确更改但是在图像下载或显示期间它崩溃了,它只在分段之间快速切换时崩溃,平均切换速度通过分段它工作正常,但仅在快速切换时崩溃。【参考方案2】:

您可以在向服务器发送请求时设置 self.view.userInteractionEnabled=NO。 当你获取数据时,只需启用用户交互。这样就可以避免多个请求。

【讨论】:

以上是关于在 UISegment 之间快速切换时应用程序崩溃的主要内容,如果未能解决你的问题,请参考以下文章

UITableViewCellAccessibilityElement 发布在选项卡切换时崩溃

在页面之间切换会导致 Windows 10 移动版崩溃

切换输入法导致的程序崩溃问题

C# Winform多窗口间切换

应用程序在飞行模式下启动时崩溃

ActionScript 3.0 通过按键切换视频