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] という行にコメントを付けると、冗長なセルが表示されます。

4

2 に答える 2

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 が一時的に一貫性のない状態になる可能性があります。

また、少し改良するために、これら 3 つのデータ ソースを同時に取得することをお勧めします。これにより、パフォーマンスが明らかに向上する場合があります。

- (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;
            });
        });
    }
}

データ ソースが 3 つしかない場合は、おそらく GCD 同時実行キューで問題を解決できますが、それより多い場合は、同時要求の数を制限できるオペレーション キューを使用することをお勧めします。また、AFNetworkingの使用を検討することもできます。これにより、これらのネットワーク要求を、別の場所で同時に実行している可能性のある他のネットワーク要求とより適切に調整できます。

ただし、ここでの主な観察事項は次のとおりです。(a) 更新が完了し、UI を更新する準備が整うまで、モデルを更新しないでください。(b) 前の更新の進行中に新しい更新を開始しないようにします (または、本当に必要な場合は、キャンセル可能なNSOperationサブクラスを作成するオペレーション キュー モデルに移動し、理論的には前の更新をキャンセルできます)。要求がある場合は、別の更新要求を発行する前に)。


_imageForCalendarType当面の質問とはまったく関係ありませんが、最初のコード スニペットでは、 の設定をこのブロックから (常に同じものに設定しているため)に移動したことがわかりますviewDidLoad。この不要な行も削除しました。

_imageForCalendarType = [[NSDictionary alloc]init];

次の行で辞書リテラルのこのインスタンス化された辞書を破棄するため、上記の行は必要ありません。

率直に言って、UIImageとにかくオブジェクトの辞書を持っているべきではなく、画像名の辞書だけを持っていてcellForRowAtIndexPath、そこでインスタンス化するべきですUIImage。画像が 3 つしかない場合は問題にならないかもしれませんが、それ以上の画像がある場合は、既存のUIImageオブジェクト構造の配列がメモリ不足の状況で問題になる可能性があります。はい、適切な処理を挿入することもできますが、そもそもオブジェクトをdidReceiveMemoryWarning含む辞書を維持しない方がはるかに簡単です。UIImage

于 2013-10-30T14:32:08.950 に答える
0

sortedStartDates を使用してテーブルを公開しているため、dispatch_async GCD ブロックではなく、dispatch_sync GCD ブロックでこのオブジェクトを構築してください。

于 2013-10-30T15:44:38.457 に答える