문제

내 프로그램에는 왼쪽에서 오른쪽으로 Uiimageviews가있는 수평 스크롤 표면이 표시됩니다. 코드는 UI 스레드에서 실행되어 새로 가시 가능한 UIIMageViews에 새로로드 된 UIIMAGE가 할당되도록합니다. 로딩은 배경 스레드에서 발생합니다.

각 이미지가 보이기 때문에 말더듬이 있다는 것을 제외하고는 거의 모든 것이 잘 작동합니다. 처음에 나는 내 배경 노동자가 UI 스레드에 무언가를 잠그고 있다고 생각했다. 나는 그것을 보는 데 많은 시간을 보냈고 결국 UIIMAGE가 UI 스레드가 처음 보이면 UI 스레드에서 약간의 게으른 처리를하고 있음을 깨달았습니다. 내 작업자 스레드에는 JPEG 데이터를 압축 해제하기위한 명시 적 코드가 있기 때문에이 문제는 저를 퍼즐로 만듭니다.

어쨌든, 직감에 나는 배경 스레드에서 임시 그래픽 컨텍스트로 렌더링하기 위해 코드를 썼습니다. UIIMAGE는 이제 내 작업자 스레드에서 사전로드되고 있습니다. 여태까지는 그런대로 잘됐다.

문제는 내 새로운 "Force Lazy Load of Image"방법이 신뢰할 수 없다는 것입니다. 간헐적 인 exc_bad_access를 유발합니다. 나는 UIIMAGE가 실제로 어떤 무대 뒤에서 무엇을하고 있는지 전혀 모른다. 아마도 JPEG 데이터를 압축 해제하고있을 것입니다. 어쨌든, 방법은 다음과 같습니다.

+ (void)forceLazyLoadOfImage: (UIImage*)image
{
 CGImageRef imgRef = image.CGImage;

 CGFloat currentWidth = CGImageGetWidth(imgRef);
 CGFloat currentHeight = CGImageGetHeight(imgRef);

    CGRect bounds = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);

 CGAffineTransform transform = CGAffineTransformIdentity;
 CGFloat scaleRatioX = bounds.size.width / currentWidth;
 CGFloat scaleRatioY = bounds.size.height / currentHeight;

 UIGraphicsBeginImageContext(bounds.size);

 CGContextRef context = UIGraphicsGetCurrentContext();
 CGContextScaleCTM(context, scaleRatioX, -scaleRatioY);
 CGContextTranslateCTM(context, 0, -currentHeight);
 CGContextConcatCTM(context, transform);
 CGContextDrawImage(context, CGRectMake(0, 0, currentWidth, currentHeight), imgRef);

 UIGraphicsEndImageContext();
}

exc_bad_access는 cgcontextDrawImage 라인에서 발생합니다. 질문 1 : UI 스레드 이외의 스레드 에서이 작업을 수행 할 수 있습니까? 질문 2 : UIIMAGE는 실제로 "사전로드"하는 것은 무엇입니까? 질문 3 :이 문제를 해결하는 공식적인 방법은 무엇입니까?

모든 것을 읽어 주셔서 감사합니다. 모든 조언은 대단히 감사하겠습니다!

도움이 되었습니까?

해결책

그만큼 UIGraphics* 메소드는 기본 스레드에서만 호출되도록 설계되었습니다. 그들은 아마도 당신의 문제의 원천 일 것입니다.

당신은 교체 할 수 있습니다 UIGraphicsBeginImageContext() 전화로 CGBitmapContextCreate(); 조금 더 관련이 있습니다 (색상 공간을 만들고 올바른 크기의 버퍼를 알아 내고 직접 할당해야합니다). 그만큼 CG* 방법은 다른 스레드에서 실행하는 것이 좋습니다.


당신이 어떻게 UIIMage를 초기화하는지 잘 모르겠지만, 당신이 그것을하고 있다면 imageNamed: 또는 initWithFile: 그러면 데이터를 직접로드 한 다음 initWithData:. 말더듬은 아마도 게으른 파일 I/O 때문일 수 있으므로 데이터 객체로 초기화해도 파일에서 읽을 수있는 옵션이 제공되지 않습니다.

다른 팁

나는 여기에 적절한 해결책을 알아내는 데 도움을 주면서 똑같은 말더듬 문제를 겪었습니다. iOS에서 게으른 이미지 로딩

언급해야 할 두 가지 중요한 사항 :

  • 작업자 스레드에서 Uikit 메소드를 사용하지 마십시오. 대신 CoreGraphics를 사용하십시오.
  • 이미지를로드하고 압축 해제하기위한 배경 스레드가 있더라도 CGBitMapContext에 잘못된 비트 마스크를 사용하는 경우 여전히 약간의 말더 핑이 있습니다. 이것은 당신이 선택 해야하는 옵션입니다 (여전히 나에게 조금 불분명합니다) :

-

CGBitmapContextCreate(imageBuffer, width, height, 8, width*4, colourSpace,
                          kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);

여기에 샘플 프로젝트를 게시했습니다. swaptest, 이미지를로드/표시하기위한 사과의 사진 앱과 거의 동일한 공연이 있습니다.

@jasamer의 swaptest uiimage 카테고리를 사용하여 대형 UIIMAGE (약 3000x2100 px)를 작업자 스레드 (NSOperationQueue)에로드했습니다. 이로 인해 이미지를 UIIMageView로 설정할 때 촉촉한 시간을 줄입니다.

다음은 swaptest uiimage 카테고리입니다 ... 다시 감사합니다 @jasamer :)

UIIMAGE+impliceLoading.h 파일

@interface UIImage (UIImage_ImmediateLoading)

- (UIImage*)initImmediateLoadWithContentsOfFile:(NSString*)path;
+ (UIImage*)imageImmediateLoadWithContentsOfFile:(NSString*)path;

@end

UIIMAGE+impliceLoading.m 파일

#import "UIImage+ImmediateLoading.h"

@implementation UIImage (UIImage_ImmediateLoading)

+ (UIImage*)imageImmediateLoadWithContentsOfFile:(NSString*)path {
    return [[[UIImage alloc] initImmediateLoadWithContentsOfFile: path] autorelease];
}

- (UIImage*)initImmediateLoadWithContentsOfFile:(NSString*)path {
    UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
    CGImageRef imageRef = [image CGImage];
    CGRect rect = CGRectMake(0.f, 0.f, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef));
    CGContextRef bitmapContext = CGBitmapContextCreate(NULL,
                                                       rect.size.width,
                                                       rect.size.height,
                                                       CGImageGetBitsPerComponent(imageRef),
                                                       CGImageGetBytesPerRow(imageRef),
                                                       CGImageGetColorSpace(imageRef),
                                                       kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little
                                                       );
    //kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little are the bit flags required so that the main thread doesn't have any conversions to do.

    CGContextDrawImage(bitmapContext, rect, imageRef);
    CGImageRef decompressedImageRef = CGBitmapContextCreateImage(bitmapContext);
    UIImage* decompressedImage = [[UIImage alloc] initWithCGImage: decompressedImageRef];
    CGImageRelease(decompressedImageRef);
    CGContextRelease(bitmapContext);
    [image release];

    return decompressedImage;
}

@end

그리고 이것이 내가 nsopeationqueue를 만들고 메인 스레드에서 이미지를 설정하는 방법입니다 ...

// Loads low-res UIImage at a given index and start loading a hi-res one in background.
// After finish loading, set the hi-res image into UIImageView. Remember, we need to 
// update UI "on main thread" otherwise its result will be unpredictable.
-(void)loadPageAtIndex:(int)index {
    prevPage = index;

    //load low-res
    imageViewForZoom.image = [images objectAtIndex:index];

    //load hi-res on another thread
    [operationQueue cancelAllOperations];  
    NSInvocationOperation *operation = [NSInvocationOperation alloc];
    filePath = [imagesHD objectAtIndex:index];
    operation = [operation initWithTarget:self selector:@selector(loadHiResImage:) object:[imagesHD objectAtIndex:index]];
    [operationQueue addOperation:operation];
    [operation release];
    operation = nil;
}

// background thread
-(void)loadHiResImage:(NSString*)file {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSLog(@"loading");

    // This doesn't load the image.
    //UIImage *hiRes = [UIImage imageNamed:file];

    // Loads UIImage. There is no UI updating so it should be thread-safe.
    UIImage *hiRes = [[UIImage alloc] initImmediateLoadWithContentsOfFile:[[NSBundle mainBundle] pathForResource:file ofType: nil]];

    [imageViewForZoom performSelectorOnMainThread:@selector(setImage:) withObject:hiRes waitUntilDone:NO];

    [hiRes release];
    NSLog(@"loaded");
    [pool release];
}

데이터를 사용하여 이미지를 초기화했지만 같은 문제가있었습니다. (데이터도 게으르게로드 된 것 같아요?) 다음 범주를 사용하여 디코딩을 강제로 사용하는 데 성공했습니다.

@interface UIImage (Loading)
- (void) forceLoad;
@end

@implementation UIImage (Loading)

- (void) forceLoad
{
    const CGImageRef cgImage = [self CGImage];  

    const int width = CGImageGetWidth(cgImage);
    const int height = CGImageGetHeight(cgImage);

    const CGColorSpaceRef colorspace = CGImageGetColorSpace(cgImage);
    const CGContextRef context = CGBitmapContextCreate(
        NULL, /* Where to store the data. NULL = don’t care */
        width, height, /* width & height */
        8, width * 4, /* bits per component, bytes per row */
        colorspace, kCGImageAlphaNoneSkipFirst);

    CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
    CGContextRelease(context);
}

@end
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top