2009-11-29 31 views
12

Chương trình của tôi hiển thị bề mặt cuộn ngang được lát bằng UIImageViews từ trái sang phải. Mã chạy trên chuỗi giao diện người dùng để đảm bảo rằng UIImageViews mới hiển thị có UIImage mới được gán cho chúng. Quá trình tải xảy ra trên một chuỗi nền.Tải xuống CGImage/UIImage tải lên trên UI gây ra tình trạng lộn xộn

Mọi thứ hoạt động gần như tốt, ngoại trừ có một đoạn nói lắp khi mỗi hình ảnh hiển thị. Lúc đầu, tôi nghĩ công nhân nền của tôi đã khóa một thứ gì đó trong chuỗi giao diện người dùng. Tôi đã dành rất nhiều thời gian nhìn vào nó và cuối cùng nhận ra rằng UIImage đang làm một số xử lý thêm lười biếng trên thread UI khi nó lần đầu tiên trở nên nhìn thấy được. Điều này giải đố tôi, vì chuỗi công nhân của tôi có mã rõ ràng để giải nén dữ liệu JPEG.

Dù sao, trên linh cảm, tôi đã viết một số mã để hiển thị thành ngữ cảnh đồ họa tạm thời trên chuỗi nền và - chắc chắn đủ, sự nói lắp đã biến mất. UIImage hiện đang được tải sẵn trên chuỗi công nhân của tôi. Càng xa càng tốt.

Vấn đề là phương pháp "tải trọng lười biếng của hình ảnh" của tôi không đáng tin cậy. Nó gây ra EXC_BAD_ACCESS liên tục. Tôi không biết UIImage thực sự đang làm gì đằng sau hậu trường. Có lẽ nó đang giải nén dữ liệu JPEG. Dù sao, phương pháp là:

+ (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(); 
} 

Và EXC_BAD_ACCESS xảy ra trên dòng CGContextDrawImage. CÂU HỎI 1: Tôi có được phép làm điều này trên một chủ đề khác với chuỗi giao diện người dùng không? CÂU HỎI 2: UIImage thực sự "tải trước" là gì? CÂU HỎI 3: Cách chính thức để giải quyết vấn đề này là gì?

Cảm ơn bạn đã đọc tất cả điều đó, mọi lời khuyên sẽ được đánh giá cao!

Trả lời

7

Phương thức UIGraphics* được thiết kế để chỉ được gọi từ chuỗi chính. Họ có lẽ là nguồn gốc của rắc rối của bạn.

Bạn có thể thay thế UIGraphicsBeginImageContext() bằng một cuộc gọi đến CGBitmapContextCreate(); nó có liên quan nhiều hơn một chút (bạn cần phải tạo một không gian màu, tìm ra bộ đệm có kích thước phù hợp để tạo và tự phân bổ nó). Các phương pháp CG* là tốt để chạy từ một chuỗi khác nhau.


Tôi không chắc chắn làm thế nào bạn đang khởi UIImage, nhưng nếu bạn đang làm việc đó với imageNamed: hoặc initWithFile: sau đó bạn có thể có thể buộc nó để nạp bằng cách tải dữ liệu cho mình và sau đó gọi initWithData:. Việc nói lắp có lẽ là do tệp I/O lười biếng, vì vậy việc khởi tạo nó với một đối tượng dữ liệu sẽ không cung cấp cho nó tùy chọn đọc từ một tệp.

+0

xin chào, cảm ơn vì đã dành thời gian trợ giúp việc này. Cuối cùng tôi đã nhận ra điều này. Tôi chỉ có thể tưởng tượng loại bộ nhớ đệm không an toàn không an toàn nào đang xảy ra bên trong các phương thức UI * đó. Giao diện của tôi bây giờ là nói lắp miễn phí ..... – JBx

+0

Tôi đang bối rối ... bạn đang nói imageNamed: gây ra tập tin lười biếng IO nhưng .. tạo một đối tượng dữ liệu sẽ không? Hay bạn đang nói rằng bạn nên có các đối tượng dữ liệu gộp vào một nơi nào đó để được rút ra khi thích hợp? –

+1

Jasconius: Nếu bạn tạo một đối tượng UIImage và cho nó đường dẫn đến một tập tin, nó có thể không tải toàn bộ hình ảnh vào bộ nhớ cho đến khi bạn vẽ nó. Nếu bạn tải tập tin vào bộ nhớ bằng cách sử dụng một đối tượng NSData, sau đó vượt qua THAT để UIImage, nó không có sự lựa chọn, nhưng để giữ nó trong bộ nhớ. Điều tốt nhất phụ thuộc vào tình hình cụ thể. – benzado

4

Tôi đã có cùng một vấn đề, mặc dù tôi đã khởi tạo hình ảnh bằng cách sử dụng dữ liệu. (Tôi đoán các dữ liệu được nạp một cách lười biếng, quá?) Tôi đã thành công để buộc giải mã sử dụng thể loại sau:

@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 
+0

Điều này dường như đã làm việc thực sự tốt cho tôi. Cảm ơn!!! : D – Marky

22

Tôi đã có vấn đề nói lắp cùng, với sự giúp đỡ tôi đã tìm ra những giải pháp thích hợp ở đây : Non-lazy image loading in iOS

Hai điều quan trọng cần đề cập đến:

  • không sử dụng phương pháp UIKit trong một công nhân sợi. Sử dụng CoreGraphics để thay thế.
  • Thậm chí nếu bạn có một chuỗi nền để tải và giải nén hình ảnh, bạn vẫn sẽ có một chút nói lắp nếu bạn sử dụng bitmask sai cho CGBitmapContext của bạn.Đây là những tùy chọn bạn có để lựa chọn (nó vẫn còn một chút không rõ ràng cho tôi tại sao):

-

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

tôi đã đăng một dự án mẫu ở đây: SwapTest, nó có khoảng performace cùng như ứng dụng Ảnh của Apples để tải/hiển thị hình ảnh.

+3

Tôi không thể hiểu tại sao bạn có 0 upvotes. Giải pháp của bạn là giải pháp duy nhất làm việc cho tôi. Tôi sẽ cung cấp cho bạn 10 upvotes nếu tôi có thể. CẢM ƠN BẠN!!! – Kalle

11

Tôi đã sử dụng danh mục SwapTest UIImage của @ jasamer để buộc tải UIImage lớn của tôi (khoảng 3000x2100 px) vào chuỗi công nhân (với NSOperationQueue). Điều này làm giảm thời gian nói lắp khi đặt hình ảnh vào UIImageView thành giá trị chấp nhận được (khoảng 0,5 giây trên iPad1).

Đây là SwapTest UIImage loại ... thanks again @jasamer :)

UIImage + ImmediateLoading.h nộp nộp

@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 

Và đây là cách tôi tạo NSOpeationQueue và đặt hình ảnh trên chủ đề chính ...

// 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]; 
} 
+0

Vẫn còn một cuộc gọi đến UIImage trong một chủ đề không phải là chủ đề chính của bạn, vì vậy giải pháp này không an toàn. –

+0

Bạn có thể sử dụng CGImageRef imageRef = CGImageCreateWithJPEGDataProvider (CGDataProviderCreateWithFilename ([đường dẫn UTF8String]), NULL, NO, kCGRenderingIntentDefault) thay vì UIImage – Tieme

+0

@JorisMans cách gọi UIImage bên ngoài chủ đề chính không an toàn? – Hlung

Các vấn đề liên quan