2012-01-13 6 views
1

注釈が付いたマップを表示するカスタムビューコントローラクラスを作成しました。アノテーションが押されると、コールアウトが表示され、サムネイル画像がアノテーションコールアウトの左側に表示されます。このクラスは、デリゲートに表示される画像を提供するように要求します。GCDを使用して別のスレッドで取得された値を返す

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view 
{ 
    [(UIImageView *)view.leftCalloutAccessoryView setImage:[self.delegate mapViewController:self imageForAnnotation:view.annotation]]; 
} 

委任クラスは、ネットワークからイメージを取得します。 UIが応答しなくなるのを防ぐために、GCDを使用してイメージをダウンロードするための新しいスレッドが作成されます。

- (UIImage *)mapViewController:(MapViewController *)sender imageForAnnotation:(id<MKAnnotation>)annotation 
{ 
    NSURL *someURL = [[NSURL alloc] initWithString:@"a URL to data on a network"]; 
    __block UIImage *image = [[UIImage alloc] init]; 

    dispatch_queue_t downloader = dispatch_queue_create("image downloader", NULL); 
    dispatch_async(downloader, ^{ 
     NSData *imageData = [NSData dataWithContentsOfURL:someURL]; // This call can block the main UI! 
     image = [UIImage imageWithData:imageData]; 
    }); 

    return image; 
} 

なぜ画像は表示されませんか?私は、デリゲートメソッドが、将来、サムネイルが最終的にそれ自身を更新する有効な画像データに設定されている画像へのポインタを返すので、それを前提としていました。これは明らかにそうではありません...

答えて

3

非同期に実行されているブロックを作成しています。つまり、コードで実行するブロックが作成された後、すぐに変数が初期化されたときに作成した新しい画像を指していた '画像'が返されます。__block UIImage *image = [[UIImage alloc] init]; メソッドからオブジェクトを返すと、そのオブジェクトへのポインタを返します。

いつかこの新しいイメージへのポインタが返されました。ブロックが実行され、取得したイメージへのポインタがローカル変数 'image'に割り当てられますが、これはメソッドの範囲外です(ブロックにはまだあります)。だから今ブロックはそれが持っているイメージへのこの参照を持っていますが、ブロックが終了するとその参照はなくなります。

これを修正する1つの方法は、ブロックを同期して実行することですが、それはイメージ取得プロセスのディスパッチのポイントを打ち負かします。あなたがする必要があるのは、画像が取得された後に呼び出すことができる機能、つまり必要な場所に画像を割り当てる機能にブロックを提供することです。これは、次のようになります。

- (void)mapViewController:(MapViewController *)sender imageForAnnotation:(id<MKAnnotation>)annotation withImageBlock:(void (^)(UIImage *))block{ 
{ 
    NSURL *someURL = [[NSURL alloc] initWithString:@"a URL to data on a network"]; 
    __block UIImage *image = [[UIImage alloc] init]; 

    dispatch__object_t currentContext = dispatch_get_current_queue(); 
    dispatch_queue_t downloader = dispatch_queue_create("image downloader", NULL); 
    dispatch_async(downloader, ^{ 
     NSData *imageData = [NSData dataWithContentsOfURL:someURL]; // This call can block the main UI! 
     image = [UIImage imageWithData:imageData]; 
     dispatch_async(currentContext, ^{ 
      block(image); 
     }); 
    }); 
} 

私はそれが私に与えられたのと同じスレッド上で私に与えられたブロックを実行できるように、私は、現在のキュー・コンテキストをつかむことに注意してください。これは本当に重要です。私に渡されたブロックにはUIKitメソッドが含まれている可能性があります。これはメインスレッドでのみ実行できます。

+0

ご協力いただきありがとうございます。この解決法は残念ながら私のアプローチに対する単純な修正ではありませんが、問題ではありません。私は、画像が取得されるまで、デリゲートがメインスレッドをブロックするかもしれないと仮定して、GCDスレッドをmapviewcontrollerが処理することをやっていると思います。とにかく、返信ありがとう! –

1

残念ながら、ここでやろうとしていることはまったく簡単ではありません。 "将来の"(http://en.wikipedia.org/wiki/Futures_and_promises)を作成し、ObjC/Cocoaに実装されていないものを返す必要があります。

ここで可能なことは、a)ダウンロードの完了時に実行され、UIを更新するブロックに発信者を置くこと、またはb)プレースホルダイメージを返すことです。およびは、イメージがダウンロードを終了した後。どちらもコードをいくらか再構築する必要があります。後者はまた、あなたのUIを更新する方法を知るためにコードをダウンロードする必要があります。これはカップリングの増加という点では不幸です。

+0

あなたとAaronは、アノテーションイメージを設定するイメージのダウンロードの完了時にブロックを実行することによって、同様のソリューションを提供していると思います。 - 私は "未来"のものについて読むでしょう。返信してくれてありがとう。 –

関連する問題