Read global variable that is continuously written
On the iPhone, we are taking images from the camera and saving them in a global UIImage
property. In another view controller class we are attempting to access this property in order to display the last one written (there is only ever one 开发者_JS百科since it is rewritten). We get a crash saying 'exe bad access' when we try to read the image. We are guessing this means that the image is not done being written when we try to access it.
How can we work around this? The nature of the project demands that this image be updated frequently and we will need to, after some user input, take that image and work with it. We need access to it.
The one possible confounding bit of evidence is that we had the same error even after using an NSTimer
to delay the image access by 10 seconds (just to diagnose the issue). This is after stopping the camera session, so there should be no new input after that point.
More question info:
This is the function that receives the image from the camera on each frame. Every 8 frames, we save a copy to the global var and process the image.
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
UIImage *image = [self imageFromSampleBuffer:sampleBuffer];
dispatch_async(dispatch_get_main_queue(), ^{
liveImageView.image = image;
if (mod%8==0) {
[self analyzeImage:image];
currentImage = image;
NSLog(@"retain count: %d", [currentImage retainCount]);
}
mod++;
});
}
The var is a global property because an entirely separate class needs to be able to access it on command when the most current image is needed.
The retaincount log returns 2 mostly but sometimes 3. :/
Maybe the code that writes the image can set an imageIsWritten
indicator (which is reset everytime a new write begins), and the other classes can simply wait until it's true
. Then, the other classes can set an imageIsBeingRead
indicator to prevent an overwrite (imagewriter must wait until !imageIsBeingRead
before it can write). And maybe wrap the whole thing in some sort of controller class...There's a name for this technique (I'm pretty sure), I just can't recall now...
(This assumes a simple single-threaded app. I don't know about what sort of threading libraries or other tools are available on the iPhone, so that's something you may want to research for a less generic solution that may work better).
you accomplish this by using a locked reads/writes to the variable. how you use the data will depend on how you access it. it is often acceptable to:
ENTER_LOCK
UIImage* img = [[theStaticImage retain] autorelease];
EXIT_LOCK
return img;
but you'll have to change that if you want to mutate the data. of course, your writes would look something like this:
ENTER_LOCK
if (theStaticImage != theNewImage) {
[theStaticImage autorelease];
theStaticImage = [theNewImage retain];
}
EXIT_LOCK
if you want to mutate the data, then you need to give clients access to the lock or implement every mutation beyond the client's visibility.
If you declare your UIImage global property as atomic
instead of nonatomic
the property will always be fully set before its getter returns the value to you. It's simpler than a coded lock and -- if the lack of being fully written is indeed the issue -- it should solve your problem.
Edited to add: Mind you, this is only true if you @synthesize
the property; if you write your own getter and setter then you'll need to do the manual lock.
(Note: leaving nonatomic
out of the the declaration makes the property atomic
by default, but if you purposefully add atomic
it will be clear upon later review that it was intentional.)
Note: This information has been moved into the question.
More question info:
This is the function that receives the image from the camera on each frame. Every 8 frames, we save a copy to the global var and process the image.
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
UIImage *image = [self imageFromSampleBuffer:sampleBuffer];
dispatch_async(dispatch_get_main_queue(), ^{
liveImageView.image = image;
if (mod%8==0) {
[self analyzeImage:image];
currentImage = image;
NSLog(@"retain count: %d", [currentImage retainCount]);
}
mod++;
});
}
The var is a global property because an entirely separate class needs to be able to access it on command when the most current image is needed.
The retaincount log returns 2 mostly but sometimes 3. :/
If an object is deallocated, the results of any messages sent to it are garbage. Since the block is added to the main queue, it will be invoked in a later iteration of the event loop, after the image object has been deallocated when the autorelease pool is drained. You can't trust retainCount
within the block.
In the current implementation, you must retain the object outside the GCD block, then release it within it.
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
UIImage *image = [[self imageFromSampleBuffer:sampleBuffer] retain];
dispatch_async(dispatch_get_main_queue(), ^{
liveImageView.image = image;
if (mod%8==0) {
[self analyzeImage:image];
currentImage = image;
}
mod++;
[image release];
});
}
A better approach would be to give it to an appropriate object, so that the ownership concept will prevent the image from being discarded early. For example:
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
self.recentCapture = [self imageFromSampleBuffer:sampleBuffer];
dispatch_async(dispatch_get_main_queue(), ^{
liveImageView.image = self.recentCapture;
if (mod%8==0) {
[self analyzeImage:self.recentCapture];
/* rather than the global 'currentImage' variable,
send a message to some other object informing
it the image is ready for processing. This will
introduce the image to the other object, no global
needed
*/
...
}
mod++;
});
}
精彩评论