2012-08-13 2 views
5

나는 Grand Central Dispatch을 사용하여 UITableViewCell의 이미지를 비동기 적으로로드합니다. 이것은 셀이 재사용되고 이전 블록이 잘못된 이미지를로드하는 경우를 제외하고는 잘 작동합니다.GCD로 UITableViewCell 재사용

나의 현재 코드는 다음과 같습니다

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 
    static NSString *CellIdentifier = @"Cell"; 

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 
    if (!cell) { 
     cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; 
    } 

    NSString *imagePath = [self imagePathForIndexPath:indexPath];   
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 

    dispatch_async(queue, ^{ 
     UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; 

     dispatch_sync(dispatch_get_main_queue(), ^{ 
      cell.imageView.image = image; 
      [cell setNeedsLayout]; 
     }); 
    }); 

    return cell; 
} 

지금까지 내가 GCD 큐는 중지 할 수 없습니다 알고있다. 이 국경 사건은 어떻게 방지 될 수 있습니까? 또는이 문제를 해결하기 위해 GCD 대신 다른 것을 사용해야합니까?

+0

GMGridView를 사용하여 엄청난 양의 이미지 축소판을 표시하고 정확하게 동일한 문제가 발생합니다. 또한 GCD를 사용하여 웹 서버의 미리보기 이미지를 즉시로드합니다. – Humayun

답변

3

내가 수행 한 작업은 셀에 NSOperation ivar가 추가되었습니다. 작업은 이미지 가져 오기,로드 및 작성을 담당합니다. 완료되면 셀을 핑 (ping)합니다. 셀이 대기열에서 제외되면 작업이 취소되고 완료되지 않은 경우 삭제됩니다. -main에있을 때 취소 작업 테스트. 이미지가 셀로 전달 된 후 셀은 작업을 삭제하고 이미지를 사용합니다.

+1

NSOperation은 확실히 GCD보다 좋으며 일반적으로 사용하며 셀 내부에 배치하는 것이 가장 좋습니다 (가장 우아한 방법) ... +1 – meronix

+1

WWDC 2012 세션 211에도이 연습이 있습니다 – jrturton

1

비동기 요청을 시작하기 전에 이미지를 강제로 재설정하려고 할 수 있습니다.

cell.imageView.image = nil; // or a placeHolder image 
dispatch_async(queue, ^{ 
    UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; 

    dispatch_sync(dispatch_get_main_queue(), ^{ 
     cell.imageView.image = image; 
     [cell setNeedsLayout]; 
    }); 
}); 

편집 :

사용자가 테이블을 스크롤 할 당신의 비동기 방법은 권리를 변경하기 전에

, 그는 한

그냥이 줄을 추가 기존의 이미지를 볼 수 있습니다 @hgpc :

이 문제가 해결되지 않는다고 생각합니다. 이전 블록은 여전히 ​​새로운 블록 앞에 잘못된 이미지를 설정할 수 있습니다. - 9 분 전 hgpc

당신 맞아요, 사용자 스크롤 빨리이 문제가 될 수 있다면 ...

내가 볼 수있는 유일한 방법은 어디에 저장하는 카운터 속성을 사용자 정의 셀을 만드는 것입니다 각 셀의 큐 operationn 수 (동기 방식) 다음 카운터 이미지 변경하기 전에 == 1이라는 비동기 방식 체크인 :

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 
     static NSString *CellIdentifier = @"Cell"; 

     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 
     if (!cell) { 
    // MyCustomUITableViewCell has a property counter 
      cell = [[[MyCustomUITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; 
      cell.counter = 0; 
     } 

     NSString *imagePath = [self imagePathForIndexPath:indexPath];   
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 

     cell.imageView.image = nil; // or a placeHolder image 
     cell.counter = cell.counter + 1; 
     dispatch_async(queue, ^{ 
      UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; 

      dispatch_sync(dispatch_get_main_queue(), ^{ 
       if (cell.counter == 1){ 
        cell.imageView.image = image; 
        [cell setNeedsLayout]; 
       } 
       cell.counter = cell.counter - 1; 

      }); 
     }); 

    return cell; 
} 
+0

이것이 문제를 해결할 것이라고 생각하지 않습니다. 이전 블록은 여전히 ​​새로운 블록 이전에 잘못된 이미지를 설정할 수 있습니다. – hpique

+0

맞아 ... 내 대답에 새 편집 – meronix

+0

이제 카운터는 하나의 이미지 만 설정되지만 반드시 올바른 이미지는 설정하지 않도록합니다. @ willy의 제안에 따라 indexPath를 사용하여 뷰 태그를 설정하고 이미지를 변경하기 전에 뷰 태그가 동일한 지 확인하십시오. – hpique

0

당신은 셀 식별자 전에 설정할 수는 비동기 작업을 시작하고 UI를 업데이트하기 전에 동일한 작업인지 확인하십시오.

+0

왜 downvote? 이것은 좋은 생각 인 것 같습니다. – hpique

0

여기에는 두 가지 가능성이 있습니다.

a) 직접 GDC 대신 NSOperationQueue을 사용하십시오.
NSOperation은 GDC를 기반으로하며 스레드 중지와 같은 많은 관리를 가능하게합니다.
사과에서 concurrency guide을보십시오.

b) 준비된 비동기 이미지 로더를 사용하십시오.
internetz에는 무료 및 오픈 소스 프로젝트가 있습니다.
저는 개인적으로 AFNetworking (특히 AFNetworking+UIImageView 카테고리)을 권장합니다.

독자적으로 수행하려는 경우 (연구, 지식 등)) a)으로 머물러야하지만 더 편리한 방법은 b)입니다.

업데이트
난 그냥 당신이 네트워크에있는 파일에서 이미지를로드하고 있지 않은지,났습니다. 이 경우
, 당신은 그것을 다르게 처리하고 다음 사항에주의를 기울여야한다 :

  • imageWithContentsOfFile:을 오히려 imageNamed:를 사용합니다. 이는 imageNamed:이로드되고 imageWithContentsOfFile:이 아닌 이미지를 캐시하기 때문입니다. imageWithContentsOfFile:을 사용하려면 NSCache의 모든 이미지를 미리로드하고이 캐시의 이미지로 테이블을 채 웁니다.

  • TableView 이미지의 크기가 크지 않아야합니다 (차원 파일 크기). 심지어 100x100 px 망막 이미지는 비동기 적재가 필요할 정도로 오래 걸리지 않는 몇 킬로미터 이상을 가지고 있지 않습니다.

+0

파일에서 이미지를로드하는 중임을 알 수 있습니다. 이는 비동기로 만들려는 지연입니다. 관련된 다운로드가 없습니다. – hpique

+0

아하 그렇습니다. 그러나이 경우에는 다른 문제가 있습니다. 나는 곧 내 대답을하겠습니다. – yinkou

+0

그래서 답변을 업데이트했습니다. – yinkou

0

테이블이 스크롤되지 않을 때 이미지를로드하는 방법은 무엇입니까? cellForRowWithIndexPath:의 문제점은 과거에 스크롤 된 셀에 대해 많은 작업을 수행하고 있다는 것입니다.

셀이 대기열에서 제거 될 때마다 이미지를로드하는 대신 viewDidLoad (또는 데이터 모델이 초기화 될 때마다)에서 수행하고 scrollViewDidEndDecelerating:에서 수행 할 수 있습니다.

-(void)viewDidLoad { 
    [super viewDidLoad]; 
    [self initializeData]; 
    [self loadVisibleImages]; 
} 

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { 
    if (scrollView == self.tableView) { 
     [self loadVisibleImages]; 
    } 
} 

-(void)loadVisibleImages { 
    for (NSIndexPath *indexPath in [self.tableview indexPathsForVisibleRows]) { 
     dispatch_async(queue, ^{ 
      NSString *imagePath = [self imagePathForIndexPath:indexPath]; 
            UIImage *image = [UIImage imageWithContentsOfFile:imagePath];   
            dispatch_sync(dispatch_get_main_queue(), ^{ 
       UITableViewCell *cell = [self tableView:self.tableView cellForRowAtIndexPath:indexPath]; 
                cell.imageView.image = image; 
                [cell setNeedsLayout]; 
            }); 
        }); 
    } 
}