16

内部に一連の画像が並んでロードされた UIScrollView があります。ここで私のアプリの例を見ることができます: http://www.42restaurants.com . 私の問題はメモリ使用量にあります。画面に表示されようとしている画像を遅延ロードし、画面に表示されていない画像をアンロードしたい。コードでわかるように、ロードする必要があるイメージを最小限に抑え、ロード部分を NSOperation に割り当てて、NSOperationQueue に配置します。ぎくしゃくしたスクロール体験を除けば、すべてがうまく機能します。

これをさらに最適化して、各画像の読み込み時間を最小限に抑えたり、スクロールがぎくしゃくしたりしないようにする方法について、誰かがアイデアを持っているかどうかはわかりません。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    [self manageThumbs];    
}

- (void) manageThumbs{
    int centerIndex = [self centerThumbIndex];
    if(lastCenterIndex == centerIndex){
        return;
    }

    if(centerIndex >= totalThumbs){
        return;
    }

    NSRange unloadRange;
    NSRange loadRange;

    int totalChange = lastCenterIndex - centerIndex;
    if(totalChange > 0){ //scrolling backwards
        loadRange.length = fabsf(totalChange);
        loadRange.location = centerIndex - 5;
        unloadRange.length = fabsf(totalChange);
        unloadRange.location = centerIndex + 6;
    }else if(totalChange < 0){ //scrolling forwards
        unloadRange.length = fabsf(totalChange);
        unloadRange.location = centerIndex - 6;
        loadRange.length = fabsf(totalChange);
        loadRange.location = centerIndex + 5;
    }
    [self unloadImages:unloadRange];
    [self loadImages:loadRange];
    lastCenterIndex = centerIndex;

    return;
}

- (void) unloadImages:(NSRange)range{
    UIScrollView *scrollView = (UIScrollView *)[[self.view subviews] objectAtIndex:0];
    for(int i = 0; i < range.length && range.location + i < [scrollView.subviews count]; i++){
        UIView *subview = [scrollView.subviews objectAtIndex:(range.location + i)];
        if(subview != nil && [subview isKindOfClass:[ThumbnailView class]]){
            ThumbnailView *thumbView = (ThumbnailView *)subview;
            if(thumbView.loaded){
                UnloadImageOperation *unloadOperation = [[UnloadImageOperation alloc] initWithOperableImage:thumbView];
                [queue addOperation:unloadOperation];
                [unloadOperation release];
            }
        }
    }
}

- (void) loadImages:(NSRange)range{
    UIScrollView *scrollView = (UIScrollView *)[[self.view subviews] objectAtIndex:0];
    for(int i = 0; i < range.length && range.location + i < [scrollView.subviews count]; i++){
        UIView *subview = [scrollView.subviews objectAtIndex:(range.location + i)];
        if(subview != nil && [subview isKindOfClass:[ThumbnailView class]]){
            ThumbnailView *thumbView = (ThumbnailView *)subview;
            if(!thumbView.loaded){
                LoadImageOperation *loadOperation = [[LoadImageOperation alloc] initWithOperableImage:thumbView];
                [queue addOperation:loadOperation];
                [loadOperation release];
            }
        }
    }
}

編集: 本当に素晴らしい回答をありがとう。これが私の NSOperation コードと ThumbnailView コードです。週末にいくつかのことを試しましたが、スクロール中に操作キューを一時停止し、スクロールが終了したら再開することによってのみ、パフォーマンスを向上させることができました。

ここに私のコードスニペットがあります:

//In the init method
queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:4];


//In the thumbnail view the loadImage and unloadImage methods
- (void) loadImage{
    if(!loaded){
        NSString *filename = [NSString stringWithFormat:@"%03d-cover-front", recipe.identifier, recipe.identifier]; 
        NSString *directory = [NSString stringWithFormat:@"RestaurantContent/%03d", recipe.identifier];     

        NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:@"png" inDirectory:directory];
        UIImage *image = [UIImage imageWithContentsOfFile:path];

        imageView = [[ImageView alloc] initWithImage:image andFrame:CGRectMake(0.0f, 0.0f, 176.0f, 262.0f)];
        [self addSubview:imageView];
        [self sendSubviewToBack:imageView];
        [imageView release];
        loaded = YES;       
    }
}

- (void) unloadImage{
    if(loaded){
        [imageView removeFromSuperview];
        imageView = nil;
        loaded = NO;
    }
}

次に、私のロードおよびアンロード操作:

- (id) initWithOperableImage:(id<OperableImage>) anOperableImage{

    self = [super init];
    if (self != nil) {
        self.image = anOperableImage;
    }
    return self;
}

//This is the main method in the load image operation
- (void)main {
    [image loadImage];
}


//This is the main method in the unload image operation
- (void)main {
    [image unloadImage];
}
4

5 に答える 5

15

「ぎくしゃくした」スクロールに少し困惑しています。NSOperationQueue は別のスレッドで操作を実行するため、最悪の場合、画面に空の UIImageView が表示されると予想していました。

何よりもまず、プロセッサに大きな影響を与えるものを探します。NSOperation だけではメイン スレッドに干渉してはならないからです。次に、メイン スレッドを中断してスクロールに影響を与える可能性のあるロックと同期の問題を引き起こしている可能性のある NSOperation のセットアップと実行に関する詳細を探しています。

考慮すべきいくつかの項目:

  1. ThumbnailView を最初に 1 つの画像でロードし、NSOperation のキューイングを無効にしてみてください (「読み込まれた場合」チェックに続くすべてをスキップしてください。これにより、NSOperation コードがパフォーマンスに影響を与えているかどうかがすぐにわかります。

  2. 1 回のスクロール操作中に-scrollViewDidScroll:度も発生する可能性があることに注意してください。スクロールの移動方法と実装方法によっては、同じアクションを複数回キューに入れようとする場合があります。-initWithOperableImage または -loaded でこれを考慮した場合、ここでのコードが同期/ロックの問題を引き起こしている可能性があります (以下の 3 を参照)。ThumbnailView インスタンスの「アトミック」プロパティを使用して、NSOperation が開始されたかどうかを追跡する必要があります。そのプロパティが設定されている場合は別の操作をキューに入れないようにし、NSOperation プロセスの最後にそのプロパティを (読み込まれたものと共に) のみ設定解除します。-centerThumbIndex

  3. NSOperationQueue は独自のスレッドで動作するため、NSOperation 内で実行されているコードがメイン スレッドと同期またはロックしていないことを確認してください。これにより、NSOperationQueue を使用する利点がすべて失われます。

  4. 「アンロード」操作の優先度が「ロード」操作よりも低いことを確認してください。これは、ユーザー エクスペリエンスが優先され、メモリの節約が 2 番目であるためです。

  5. NSOperationQueue が遅れた場合に空白のサムネイルが表示される前にエラーのマージンが高くなるように、少なくとも 1 ページまたは 2 ページ前後に十分なサムネイルを保持してください。

  6. ロード操作が「事前にスケーリングされた」サムネイルのみをロードし、フルサイズの画像をロードして再スケーリングまたは処理していないことを確認してください。これは、スクロール アクションの途中で余分なオーバーヘッドが大きくなります。さらに進んで、アルファチャンネルなしで PNG16 に変換したことを確認してください。これにより、サイズが少なくとも (4:1) 縮小され、視覚的なイメージに変化が見られないことが期待されます。また、サイズをさらに小さくする PVRTC 形式の画像を使用することも検討してください (8:1 の縮小)。これにより、「ディスク」からイメージを読み取るのにかかる時間が大幅に短縮されます。

これが意味をなさない場合は申し訳ありません。投稿したコードに問題は見られず、NSOperation または ThumbnailView クラスの実装で問題が発生する可能性が高くなります。そのコードを確認しないと、条件を効果的に説明できない可能性があります。

ロードとアンロード用の NSOperation コードと、少なくとも NSOperation インスタンスとのやり取りを理解するのに十分な ThumbnailView を投稿することをお勧めします。

これがある程度役に立てば幸いです。

バーニー

于 2009-07-09T11:05:09.067 に答える
3

見栄えは良くありませんが、1 つのオプションは、スクロールが停止したときにのみ画像を読み込むことです。

画像の読み込みを無効にするフラグを設定します。

-scrollViewWillBeginDragging:

以下を使用して、スクロールが停止したときに画像の読み込みを再度有効にします。

-scrollViewDidEndDragging:willDecelerate:

UIScrollViewDelegate方法。willDecelerate:パラメータがの場合NO、移動は停止しています。

于 2009-07-08T14:49:32.437 に答える
2

問題はここにあります:

 UIImage *image = [UIImage imageWithContentsOfFile:path];

ディスクからファイルをロードすると、スレッド化されているかどうかに関係なく、メインスレッドで発生する可能性がありますが、すべてが停止します。他の状況では、このような大きな領域がまったく移動していないため、通常、これは表示されません。

于 2010-04-08T18:01:58.473 に答える
1

この問題を調査しているときに、興味深いと思われるリソースがさらに2つ見つかりました。

iPhoneサンプルプロジェクト「PageControl」をチェックしてください:http://developer.apple.com/iphone/library/samplecode/PageControl/index.html

UIScrollViewにビューコントローラを遅延ロードします。

  • と -

cocoa touch libをチェックしてください:http: //github.com/facebook/three20 には、ウェブ/ディスクから写真/サムネイルを遅延ロードする「TTPhotoViewController」クラスがあります。

于 2010-04-08T21:40:12.717 に答える
0

スクロールビューに追加するサブコンテンツビューに対して shouldRasterize = YES を設定します。チャームのように、カスタム作成されたスクロール ビューのぎくしゃくした動作を削除することが見られます。:)

また、Xcode のインストゥルメントを使用してプロファイリングを行います。Ray Wenderlichによって作成されたプロファイリング用のチュートリアルに目を通し、大いに役立ちました。

于 2013-09-15T05:32:31.870 に答える