3

XCode 4.5、iPad 開発、iOS6

こんにちは、初心者の開発者を助けていただければ幸いです。これがすでに回答されている場合は事前にお詫びしますが、検索中に見つけることができませんでした!

Core Data に大量のデータをインポートする必要があるアプリを開発しています。インポート ルーチンは正常に動作します (ルーチンがバックグラウンドで動作している間、アラートはアクティビティ モニターで「お待ちください」と表示されます) が、ユーザーにインポートの進行状況に関するより詳細なフィードバック (「XX% インポート済み」など) を提供したいと考えています。次のコードはプロセスを開始し、-

- (IBAction)import:(id)sender{

[self showWaiting];

[self performSelectorInBackground:(@selector(callGrouper)) withObject:nil];


}

-(void)showWaiting{

alertMsg = @"Please Wait....";
waitAlert = [[UIAlertView alloc] initWithTitle:alertMsg message:nil delegate:self  cancelButtonTitle:nil otherButtonTitles: nil];


[waitAlert show];     

UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];

indicator.center = CGPointMake(waitAlert.bounds.size.width / 2, waitAlert.bounds.size.height - 50); 

[indicator startAnimating];
[waitAlert addSubview:indicator];    

}


-(void)callGrouper{

ImportRoutine *firstTest = [[ImportRoutine alloc] init];

[firstTest runImport:managedObjectContext];


 [waitAlert dismissWithClickedButtonIndex:0 animated:TRUE];

UIAlertView *alert = [[UIAlertView alloc]initWithTitle: @"iPad Application"
                                               message: @"Import complete!"
                                              delegate: self
                                     cancelButtonTitle:@"Ok"
                                     otherButtonTitles:nil];

[alert show];

}

ImportRoutine (別のクラス) 内に、インポートされたパーセンテージに関するデータを収集するコードがありますが、このメッセージをメイン スレッドに戻して「alertMsg」を更新し、UIAlertView を更新するにはどうすればよいですか?

4

2 に答える 2

2

GCD(グランドセントラルディスパッチ)を使用して、コードのブロックをメインスレッドにディスパッチできます。

dispatch_async(dispatch_get_main_queue(), ^{

    // code here to update UI
});

ディスパッチ呼び出しを含むメソッドのスコープ内のオブジェクトはすべて保持されるため、データを処理する前に、バックグラウンドスレッドがオブジェクトとともに割り当て解除されることを心配することなく、オブジェクトをメインスレッドに簡単に戻すことができます。 。ローカルスコープのプリミティブ値(別名int、float、doubleなど)がコピーされるため、intを5に設定すると、intの値を出力するブロックをディスパッチし、intを10に設定した直後にディスパッチします。 intを10に設定した後にブロックが実行されても、5が出力されます。2つのスレッドで同じ可変オブジェクト( `NSMutableArrayやNSMutableDictionaryなど)を同時に変更したり、1つで変更したりすることはできません。クラッシュせずに別の整数を列挙するので、

dispatch_async()とは異なりdispatch_sync()、ディスパッチされたコードが完了するのを待たずに実行を続行します。これは、バックグラウンドスレッドがUIの処理が終了したかどうかを気にする必要がないため便利です。

ImportRoutineUIAlertViewがViewControllerクラスの外部でアドレス可能である限り、進行状況を計算するクラスのメソッド内でディスパッチ呼び出しを固定できます。または、model-view-controllerの設計原則をより厳密に追跡したい場合は、ViewControllerで次のようなメソッドを作成できます。

- (void)updateProgressToPercentComplete:(double)percent {
    if ([NSThread currentThread] != [NSThread mainThread]) {
        dispatch_async(dispatch_get_main_queue(), ^{
            // update code or call to method that is guaranteed to be on the main thread.
        }
    }
    else {
        // update code or call to method that is guaranteed to be on the main thread.
    }
}

ドキュメントを読んで、「ああ、Objective-Cブロックはこれまでで最もクールなものだ」と思ったら、上記のメソッドを変更して、同じ更新コードを2回記述する必要がないようにすることができます。

- (void)updateProgressToPercentComplete:(double)percent {
    void (^updateProgressBlock)(void) = ^{
        // update code
    };
    if ([NSThread currentThread] != [NSThread mainThread]) {
        dispatch_async(dispatch_get_main_queue(), updateProgressBlock());
    }
    else {
        updateProgressBlock();
    }
}

ちなみに、-callGrouperコードで、バックグラウンドスレッドのメインスレッドで作成したと想定している既存のmanagedObjectContextを使用していることに気付きました...コアデータのほとんどはスレッドセーフではないため、細心の注意を払う必要があります。あちこちでクラッシュします。バックグラウンドスレッドでセカンダリ管理対象オブジェクトコンテキストを作成してから、変更をメインスレッドのコンテキストにマージする(またはバックグラウンドスレッドで保存してメインスレッドで再フェッチする)方がよい場合があります。

編集:
基本フロー:View Controllerからバックグラウンドプロセスを開始し、進行状況ブロックを渡します。->バックグラウンドスレッドのインポートクラスは、プログレスブロックを定期的に実行します->プログレスブロック内で、メインスレッドにディスパッチしてUIを更新します。

ImportRoutineクラスに、次のようなプロパティ宣言を追加します。

@property (nonatomic, strong) void (^progressBlock)(NSUInteger);

progressBlockこれは、符号なし整数(0-100)を取り、何も返さない(void)という呼び出されたプロパティを意味します。クラス拡張を使用して、このプロパティをプライベートにする必要があります。

次に、次のようにインポートクラスにメソッドを作成します。

- (void)callGrouper:(void (^)(NSUInteger))progress {
    [self setProgressBlock:progress];
    // Your import code
}

進行状況の更新を受け取るメソッドで、progressBlockを呼び出し、進行状況を0〜100の数値として渡します。

if ([self progressBlock] != nil) {
    [self progressBlock](progressValue);
}

進行状況ブロックがゼロでないことを確認するためにチェックすることに注意してください。NULLブロックを実行しようとすると、クラッシュして書き込みます。

次に、View Controllerにすでにあるインポートルーチン呼び出しのオブジェクトとしてブロックを渡し、ブロック内でメインキューにディスパッチして、進行状況を更新できます。

于 2012-10-18T14:40:27.723 に答える
1

以下を使用できます。

[self performSelectorOnMainThread:@selector(yourSelector) withObject:anObjectIfYouNeedToSendOne waitUntilDone:YES/NO];

UI はメイン スレッドで実行されるため、UIAlertView または他の UI オブジェクトに再度アクセスできます。

于 2012-10-18T14:48:48.070 に答える