views:

848

answers:

4

This appears to be the the classic method for scanning images from the iPhone. I have a thread that is dispatched from the main thread to go and scan for Codes. It essentially creates a new UIImage each time then removes it.

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    { 
     while (![thread isCancelled]) {
#ifdef DEBUG
      NSLog(@"Decoding Loop");
#endif
     // [self performSelectorOnMainThread:@selector(updateImageBuffer) withObject:nil waitUntilDone:YES];     
      CGImageRef cgScreen = UIGetScreenImage();
      UIImage *uiimage = [UIImage imageWithCGImage:cgScreen];

      if (uiimage){
       CGSize size = [uiimage size];
       CGRect cropRect = CGRectMake(0.0, 80.0, size.width, 360); // Crop to centre of the screen - makes it more robust
#ifdef DEBUG
       NSLog(@"picked image size = (%f, %f)", size.width, size.height);
#endif
       [decoder decodeImage:uiimage cropRect:cropRect];
      }
      [uiimage release];
      CGImageRelease(cgScreen);
     }
    }
    [pool release];

the problem is that the [pool release] causes an ERROR_BAD_EXC (that old classic) and the program bombs. I'm told that there is no need to call [uiimage release] as I havent explicitly allocated a UIImage but this doesn't seem to be the case. If I take that line out, Memory usage goes through the roof and the program quits dues to lack of memory. It appears I can't have this work the way I'd like.

Is there a way to create a UIImage "in-place"? I.e, have a buffer that is written to again and again as a UIImage? I suspect that would work?

Update!

Tried executing the UIKit related calls on the main thread as follows:

-(void)performDecode:(id)arg{

    // Perform the decoding in a seperate thread. This should, in theory, bounce back with a 
    // decoded or not decoded message. We can quit at the end of this thread.
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    { 
     while (![thread isCancelled]) {

#ifdef DEBUG
      NSLog(@"Decoding Loop");
#endif
      [self performSelectorOnMainThread:@selector(updateImageBuffer) withObject:nil waitUntilDone:YES];     

      if (uiimage){
       CGSize size = [uiimage size];
       CGRect cropRect = CGRectMake(0.0, 80.0, 320, 360); // Crop to centre of the screen - makes it more robust
#ifdef DEBUG
       NSLog(@"picked image size = (%f, %f)", size.width, size.height);
#endif
       [decoder decodeImage:uiimage cropRect:cropRect];
      }
     }
    }
    [pool drain];


#ifdef DEBUG
    NSLog(@"finished decoding.");
#endif


}

-(void) updateImageBuffer {
    CGImageRef cgScreen = UIGetScreenImage();
    uiimage = [UIImage imageWithCGImage:cgScreen];
    //[uiimage release];
    CGImageRelease(cgScreen);
}

No joy however as EXC_BAD_ACCESS rears its ugly head when one wishes to grab the "Size" of the UIImage

A: 

[uiimage release] is definitely wrong in this context. Also, Apple stresses that all UIKit methods must be executed on the main thread. That includes UIGetScreenImage() and +[UIImage imageWithCGImage:].

Edit: So you get an exception when calling -[UIImage size] on the wrong thread. This probably shouldn't surprise you because it is not permitted.

Ole Begemann
Yes, that was what I thought. I cut that out but the internal library I'm using is doing such things to UIImage as well so... no joy with that one.
Oni
+1  A: 

UIGetScreenImage() is private and undocumented so you flat-out cannot use it. Saying that nothing about it suggests that you now own CGImageRef cgScreen so why do you release it? You also have no way of knowing if it is thread safe and so should assume it isn't. You then go on to release the IImage *uiimage which you did not init, retain or copy, so again - you don't own it. Review the docs.

private or un-private is irrelevant to me at this point. The release method on cgScreen was taken from another piece of code. I have reviewed the docs and I am in agreement that uiimage should not be released HOWEVER as I stated, the memory simply keeps climbing if this line is not put in place
Oni
A: 
UIImage *uiimage = [[UIImage alloc] initWithCGImage: cgScreen];

Explicitly stating that I know best when to release the object seemed to work. Virtual Memory still increases but physical now stays constant. Thanks for pointing out the UIKit Thread Safe issues though. That is a point I'd missed but seems not affect the running at this point.

Also, I should point out, Red Laser and Quickmark both use this method of scanning camera information ;)

Oni
+1  A: 

As has been stated by others, you should not release the UIImage returned from imageWithCGImage: . It is autoreleased. When your pool drains, it tries sending a release message to your already-released image objects, leading to your crash.

The reason why your memory usage keeps climbing is that you only drain the autorelease pool outside of the loop. Your autoreleased objects keep accumulating inside of the loop. (By the way, you need to release your autorelease pool at the end of that method, because it is currently being leaked.) To prevent this accumulation, you could drain the pool at regular intervals within the loop.

However, I'd suggest switching to doing [[UIImage alloc] initWithCGImage:cgScreen] and then releasing the image when done. I try to avoid using autoreleased objects whereever I can within iPhone applications in order to have tighter control over memory usage and overall better performance.

Brad Larson
Cheers! I mentioned this in the previous comment but thanks for the tip with Drain. The key was figuring out that convenience methods stick with auto-release and that releasing early, therefore, does not work out right.
Oni