CGIMAGE/UIIMAGE UI 스레드에 게으르게 로딩하면 말더 러가 발생합니다.
-
06-07-2019 - |
문제
내 프로그램에는 왼쪽에서 오른쪽으로 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