يؤدي تحميل CGImage/UIImage بتكاسل على مؤشر ترابط واجهة المستخدم إلى التلعثم

StackOverflow https://stackoverflow.com/questions/1815476

سؤال

يعرض برنامجي سطح تمرير أفقيًا مبلطًا بـ UIImageViews من اليسار إلى اليمين.يتم تشغيل التعليمات البرمجية على مؤشر ترابط واجهة المستخدم للتأكد من أن UIImageViews المرئية حديثًا تحتوي على UIImage تم تحميلها حديثًا وتم تعيينها لها.يحدث التحميل على موضوع الخلفية.

كل شيء يعمل بشكل جيد تقريبًا، باستثناء وجود تلعثم عندما تصبح كل صورة مرئية.في البداية اعتقدت أن عامل الخلفية الخاص بي كان يقفل شيئًا ما في سلسلة واجهة المستخدم.لقد أمضيت الكثير من الوقت في النظر إليها وأدركت في النهاية أن UIImage تقوم ببعض المعالجة البطيئة الإضافية على مؤشر ترابط واجهة المستخدم عندما تصبح مرئية لأول مرة.هذا يحيرني، نظرًا لأن مؤشر الترابط العامل الخاص بي يحتوي على رمز واضح لفك ضغط بيانات JPEG.

على أي حال، بناءً على حدسي، قمت بكتابة بعض التعليمات البرمجية لعرضها في سياق رسومي مؤقت على مؤشر ترابط الخلفية - ومن المؤكد أن التأتأة اختفت.يتم الآن تحميل UIImage مسبقًا على مؤشر ترابط العامل الخاص بي.حتى الان جيدة جدا.

تكمن المشكلة في أن أسلوبي الجديد "فرض التحميل البطيء للصورة" غير موثوق به.فإنه يسبب 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:هل يُسمح لي بالقيام بذلك في موضوع آخر غير موضوع واجهة المستخدم؟السؤال 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, ، فهو يتمتع بنفس أداء تطبيق الصور من Apple لتحميل/عرض الصور.

واعتدت فئة SwapTest UIImage @ jasamer لإجبار تحميل بلدي UIImage كبير (حوالي 3000x2100 بكسل) في مؤشر ترابط عامل (مع NSOperationQueue). هذا يقلل من الوقت تلعثم عند وضع الصورة في UIImageView إلى قيمة مقبولة (حوالي 0.5 ثانية على IPAD1).

وهنا هي فئة SwapTest UIImage ... شكرا مرة أخرىjasamer:)

وUIImage + ImmediateLoading.h ملف

@interface UIImage (UIImage_ImmediateLoading)

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

@end

وUIImage + ImmediateLoading.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