2013-10-30 40 views
0

我有一个应用程序,它从3个共享的Google日历中获取事件并将其显示在表格视图中。拉动以刷新崩溃的应用程序

我想实现拉刷新,但应用程序不断崩溃,如果我放开拉,在数据加载之前。 (如果我憋了几秒钟拉一切都很好 - 如果我松手马上崩溃

代码:

-(void)viewDidLoad 
{ 
    [super viewDidLoad];  
    UIRefreshControl *refresh = [[UIRefreshControl alloc] init]; 
    refresh.attributedTitle = [[NSAttributedString alloc] initWithString:@"Pull to Refresh"]; 
    [refresh addTarget:self action:@selector(getEvents) forControlEvents:UIControlEventValueChanged]; 
    self.refreshControl = refresh; 
    startDates = [[NSMutableArray alloc] init]; 
    [self getEvents]; 
} 

- (void)stopRefresh 
{ 
    [self.refreshControl endRefreshing]; 
} 

-(void)getEvents 
{ 
    [startDates removeAllObjects]; 
    startDates = [NSMutableArray array]; 
    sectionEntries = [NSMutableArray array]; 
    entries = [NSMutableArray array]; 
    sortedStartDates = [[NSArray alloc]init]; 
    _imageForCalendarType = [[NSDictionary alloc]init]; 
    _imageForCalendarType = @{ 
           @"The Irish House Music Calendar" : [UIImage imageNamed:@"music.png"] 
           , @"FixedEvents-Student Night" : [UIImage imageNamed:@"student.png"] 
           , @"FixedEvents-Ladies Night"   : [UIImage imageNamed:@"cocktail.png"] 
           , @"AppTest"   : [UIImage imageNamed:@"football.png"] 
           }; 
    dispatch_async(kBgQueue, ^{ 

     NSData* data = [NSData dataWithContentsOfURL:sportsCalendarURL]; 
     [self performSelectorOnMainThread:@selector(fetchedData:) withObject:data waitUntilDone:YES]; 

     NSData* data2 = [NSData dataWithContentsOfURL:musicCalendarURL]; 
     [self performSelectorOnMainThread:@selector(fetchedData:) withObject:data2 waitUntilDone:YES]; 

     NSData* data3 = [NSData dataWithContentsOfURL:fixedCalendarURL]; 
     [self performSelectorOnMainThread:@selector(fetchedData:) withObject:data3 waitUntilDone:YES]; 

     // Reload table view - UI operation, so must be run on main thread 
     dispatch_async(dispatch_get_main_queue(), ^{ 

      sortedStartDates = [startDates sortedArrayUsingSelector:@selector(compare:)]; 
      [self.tableView reloadData]; 
      [self performSelector:@selector(stopRefresh) withObject:nil afterDelay:2.5]; 
     }); 
    }); 


} 

它给了我在这一行cellForRowAtIndexPath方法中一个SIGABRT错误。

NSInteger index = [self getRow:sortedStartDates[indexPath.section]]; // get correct index for sectionEntries 

错误:*终止应用程序由于未捕获的异常 'NSRangeException',原因: '* - [__ NSArrayI objectAtIndex:]:索引4超越界限为空数组'

这似乎是错误是因为我的startDates没有数据NSMutableArray,但如果我评论行[startDates removeAllObjects]我得到多余的单元格。

+0

无论发生什么问题:冗余或额外的单元格,tableview数据源配置不正确。首先检查'numberOfRowsInSection'和'numberOfSectionsInTableView'方法的结果。 –

+0

只有在实际更新数据之前放开“刷新”才会出现此问题。在我看来,应用程序试图在我有任何数据放入它们之前创建单元格 – catu

回答

3

至少,我建议检查以确保刷新尚未进行。您可能还需要改变你的getEvents采取刷新控制作为参数,并为它下相应地更新拉(这样用户就会知道,刷新正在进行):

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    _imageForCalendarType = @{ 
           @"The Irish House Music Calendar" : [UIImage imageNamed:@"music.png"] 
          , @"FixedEvents-Student Night"  : [UIImage imageNamed:@"student.png"] 
          , @"FixedEvents-Ladies Night"  : [UIImage imageNamed:@"cocktail.png"] 
          , @"AppTest"      : [UIImage imageNamed:@"football.png"] 
          }; 

    UIRefreshControl *refresh = [[UIRefreshControl alloc] init]; 
    refresh.attributedTitle = [[NSAttributedString alloc] initWithString:@"Pull to Refresh"]; 
    [refresh addTarget:self action:@selector(getEvents:) forControlEvents:UIControlEventValueChanged]; 
    self.refreshControl = refresh; 
    [self getEvents:refresh]; 
} 

- (void)getEvents:(UIRefreshControl *)refresh 
{ 
    static BOOL refreshInProgress = NO; 

    if (!refreshInProgress) 
    { 
     refreshInProgress = YES; 

     refresh.attributedTitle = [[NSAttributedString alloc] initWithString:@"Refreshing"]; // let the user know refresh is in progress 

     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 

      // get the data here 

      dispatch_async(dispatch_get_main_queue(), ^{ 

       // when done, update the model and the UI here 

       refresh.attributedTitle = [[NSAttributedString alloc] initWithString:@"Pull to Refresh"]; // reset the message 
       [refresh endRefreshing]; 

       refreshInProgress = NO; 
      }); 
     }); 
    } 
} 

但,您应该非常小心地异步更新模型数据(因为您的主队列可能会在更新过程中尝试从模型中检索信息)。你真的应该推迟模型的更新,直到最后一次发送到主队列。但是不要在异步过程中更新模型,否则您的模型和UI可能会暂时处于不一致的状态。另外,作为一种改进,您可能想要同时检索这三个数据源,并且您可能会观察到明显的性能改进。

- (void)getEvents:(UIRefreshControl *)refresh 
{ 
    static BOOL refreshInProgress = NO; 

    if (!refreshInProgress) 
    { 
     refreshInProgress = YES; 

     refresh.attributedTitle = [[NSAttributedString alloc] initWithString:@"Refreshing"]; // let the user know refresh is in progress 

     // get the data here 

     __block NSData *data1 = nil; 
     __block NSData *data2 = nil; 
     __block NSData *data3 = nil; 

     dispatch_queue_t queue = dispatch_queue_create([[[[NSBundle mainBundle] bundleIdentifier] stringByAppendingString:@".network"] UTF8String], DISPATCH_QUEUE_CONCURRENT); 

     dispatch_async(queue, ^{ 
      data1 = [NSData dataWithContentsOfURL:sportsCalendarURL]; 
     }); 

     dispatch_async(queue, ^{ 
      data2 = [NSData dataWithContentsOfURL:musicCalendarURL]; 
     }); 

     dispatch_async(queue, ^{ 
      data3 = [NSData dataWithContentsOfURL:fixedCalendarURL]; 
     }); 

     // use dispatch barrier here, which will only fire when the previous three requests are done 

     dispatch_barrier_async(queue, ^{ 

      // update the UI here 

      dispatch_async(dispatch_get_main_queue(), ^{ 

       startDates  = [NSMutableArray array]; 
       sectionEntries = [NSMutableArray array]; 
       entries  = [NSMutableArray array]; 

       [self fetchedData:data1]; 
       [self fetchedData:data2]; 
       [self fetchedData:data3]; 

       refresh.attributedTitle = [[NSAttributedString alloc] initWithString:@"Pull to Refresh"]; // reset the message 
       [refresh endRefreshing]; 

       sortedStartDates = [startDates sortedArrayUsingSelector:@selector(compare:)]; 
       [self.tableView reloadData]; 

       refreshInProgress = NO; 
      }); 
     }); 
    } 
} 

你也许可以逃脱一个GCD并发队列如果你只有三个数据源,但如果你可能有不止这些,你可能想使用一个操作队列中,你可以限制数量并发请求。此外,您可能会考虑使用AFNetworking,它可以更好地协调这些网络请求与其他网络请求,您可能在其他地方同时进行其他网络请求。

但是,这里的主要观察结果是(a)在刷新完成并且您准备更新UI之前不更新您的模型;和(b)确保在前一个进行中的时候不会启动新的刷新(或者,如果您真的需要它,请转到操作队列模式,您可以取消NSOperation的子类,然后理论上可以取消在发出另一个更新请求之前,如果有的话)。


完全无关手头上的问题,但在我的第一个代码段,你会看到,我提出的_imageForCalendarType出这个块的设置(如你总是将其设置为同一件事)和viewDidLoad。我也消除这种不必要的行:

_imageForCalendarType = [[NSDictionary alloc]init]; 

你放弃这个实例字典词典中的下一行文字,所以不需要上面的线。

坦率地说,你甚至有可能不应该UIImage对象的字典,无论如何,而是只是一个形象的名字的字典,并有cellForRowAtIndexPath实例化UIImage那里。当你只有三幅图像时,这可能并不重要,但如果你有更多的图像,现有的UIImage对象构造阵列在内存压力情况下可能会有问题。是的,您可以插入适当的didReceiveMemoryWarning处理方式,但首先不要维护带有UIImage对象的字典会简单得多。

+0

非常感谢您提供非常详细的解释。帮助了解它。 – catu

0

由于您使用sortedStartDates发布表,因此请在dispatch_sync GCD块中构造此对象,而不是dispatch_async GCD块。

+0

嗯。我不确定你指的是哪一个,但是如果catu使用'dispatch_sync'完成外部调度,那么问题就会消失,但整个过程会同步操作,阻塞主队列,导致可怕的用户体验和风险被看门狗流程所杀。如果你同步发送,根本不会有任何利益使用GCD。当使用串行队列来同步来自多个线程的共享资源的更新时,'dispatch_sync'非常有用,但在这种情况下,它并不是非常有用。 – Rob