I'm looking at the crash log for a device that's testing an app and I see the following lines...
objc_exception_throw + 33
[__NSArrayM insertObject:atIndex:] +187
The code where this happens is below. appData is an NSDictionary, and I'm expecting imageUrl to be a URL to a png file on the internet.
for (int i = 1; i <= [self getNumberOfScreenshots]; i++) {
pathToUrl = #"screenshot_";
pathToUrl = [pathToUrl stringByAppendingString:[[NSNumber numberWithInt:i] stringValue]];
imageUrl = [self.appData valueForKey:pathToUrl];
imageData = [[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:imageUrl]];
[NSMutableArrayObj addObject:imageData];
}
What would cause this type of error? The error happens very rarely..could it be that imageData is sometimes nil because it fails to download the png image off the url, so that throws that exception when I try to add it to the NSMutableArrayObj?
Thanks!
there may two reasons as you have not described error more specically
1) Actually your not allocating the memory to array
2) inserting nil value to array. To stop inserting nil do this:
if(imageData)
{
[NSMutableArrayObj addObject:imageData];
}
Related
I am implementing a Cache in my iOS app, that would keep images downloaded in RAM.
I did some research and found some code but most of them were for caching images to permanent storage.
I tried NSCache but couldn't work it around for my need.
The requirements are:
Limit on saving images. e.g. 100.
As the Cache limit is reached, it should remove most older image inserted before adding a new one.
I'm not sure about the exact word but I think it should be called FIFO cache (First in first out).
After some research, I did the following implementation.
static NSMutableDictionary *thumbnailImagesCache = nil;
+ (UIImage *)imageWithURL:(NSString *)_imageURL
{
if (thumbnailImagesCache == nil) {
thumbnailImagesCache = [NSMutableDictionary dictionary];
}
UIImage *image = nil;
if ((image = [thumbnailImagesCache objectForKey:_imageURL])) {
DLog(#"image found in Cache")
return image;
}
/* the image was not found in cache - object sending request for image is responsible to download image and save it to cache */
DLog(#"image not found in cache")
return nil;
}
+ (void)saveImageForURL:(UIImage *)_image URLString:(NSString *)_urlString
{
if (thumbnailImagesCache == nil) {
thumbnailImagesCache = [NSMutableDictionary dictionary];
}
if (_image && _urlString) {
DLog(#"adding image to cache")
if (thumbnailImagesCache.count > 100) {
NSArray *keys = [thumbnailImagesCache allKeys];
NSString *key0 = [keys objectAtIndex:0];
[thumbnailImagesCache removeObjectForKey:key0];
}
[thumbnailImagesCache setObject:_image forKey:_urlString];
DLog(#"images count in cache = %d", thumbnailImagesCache.count)
}
}
Now the problem is that I'm not sure weather this is the correct/efficient solution. Any one have any better idea/solution?
Your assumption about the order of the keys is certainly incorrect. The order of the keys in an NSDictionary is unspecified, the key and value at index 0 need not be the oldest one. You shall store the creation date of each image in the method where you put them in the cache dictionary.
Apart from that, the rest of the code seems valid.
-(void)decode:(CVImageBufferRef)BufferRef
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
CGImageRef videoFrameImage = [ZXCGImageLuminanceSource createImageFromBuffer:BufferRef];
CGImageRef rotatedImage = [self rotateImage:videoFrameImage degrees:0.0f];
[NSMakeCollectable(videoFrameImage) autorelease];
//Decoding:
ZXLuminanceSource* source = [[[ZXCGImageLuminanceSource alloc] initWithCGImage:rotatedImage] autorelease];
[NSMakeCollectable(rotatedImage) autorelease];
ZXBinaryBitmap* bitmap = [ZXBinaryBitmap binaryBitmapWithBinarizer:[ZXHybridBinarizer binarizerWithSource:source]];
NSError* error = nil;
// There are a number of hints we can give to the reader, including
// possible formats, allowed lengths, and the string encoding.
ZXDecodeHints* hints = [ZXDecodeHints hints];
ZXMultiFormatReader* reader = [ZXMultiFormatReader reader];
ZXResult* result = [reader decode:bitmap
hints:hints
error:&error];
if (result)
{
// The coded result as a string. The raw data can be accessed with
// result.rawBytes and result.length.
NSString* contents = result.text;
// The barcode format, such as a QR code or UPC-A
ZXBarcodeFormat format = result.barcodeFormat;
}
else
{
// Use error to determine why we didn't get a result, such as a barcode
// not being found, an invalid checksum, or a format inconsistency.
}
[pool drain];
}
I ran into this problem which is easily resolved by down sampling the image that the camera generates. Apparently it's too high res for the library to process the bar code out of it. By reducing the size of my UIImage to 640x480 before call [image CGImage] everything worked perfectly.
You've commented that you should use the error to determine why you didn't get a result, but you don't actually do that. Look at what's in error.
I am in the process of writing an iPhone app, and am having a few problems with the memory. Here is the code below:
NSURL *url = [curItem valueForProperty: MPMediaItemPropertyAssetURL];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL: url options:nil];
NSError *error = nil;
AVAssetReader* reader = [[AVAssetReader alloc] initWithAsset:asset error:&error];
AVAssetTrack* track = [[asset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
NSMutableDictionary* audioReadSettings = [NSMutableDictionary dictionary];
[audioReadSettings setValue:[NSNumber numberWithInt:kAudioFormatLinearPCM]
forKey:AVFormatIDKey];
AVAssetReaderTrackOutput* readerOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:track outputSettings:audioReadSettings];
[reader addOutput:readerOutput];
[reader startReading];
CMSampleBufferRef sample = [readerOutput copyNextSampleBuffer];
while( sample != NULL)
{
sample = [readerOutput copyNextSampleBuffer];
}
CFRelease(sample);
I am reading songs from the user's iTunes library (curItem is the current song), and if I leave the last line: CFRelease(sample) in the code, the program will stop - no error is shown - it just crashes. If I comment out the line, I of course run into memory problems, and the code crashes on about the fourth song after getting "Received memory warning."
What am I doing wrong?
The naming convention copyNextSampleBuffer implies that you own the object returned so you are correct to release it, but you are calling the copyNextSampleBuffer method multiple times in a loop and overwriting the previous copy without releasing it.
When you do finally call CFRelease, you are calling it on a variable that you have just checked to be NULL. According to this StackOverflow answer, calling CFRelease on NULL is not safe, so that's why you're crashing:
What you need to do instead is call release inside your while loop before you overwrite the variable, like this:
CMSampleBufferRef sample = [readerOutput copyNextSampleBuffer];
while( sample != NULL)
{
CFRelease(sample);
sample = [readerOutput copyNextSampleBuffer];
}
If that doesn't fix your crash (and even if it does), try running the static analyser over your code (select Analyze in the product menu in Xcode) and see if it reports any potential leaks or over-releases. Remember, every yellow and blue warning you get is a potential crash so try to fix them all.
EDIT: It just occurs to me that your loop doesn't make much sense - why are you reading samples over and over and then just throwing them away? Did you perhaps get the NULL check wrong in your while loop and you actually meant to write this instead?
CMSampleBufferRef sample = [readerOutput copyNextSampleBuffer];
while( sample == NULL)
{
sample = [readerOutput copyNextSampleBuffer];
}
CFRelease(sample);
That should also be fine as in that case you are explicitly checking that the sample is not NULL before releasing it. Although you're still throwing away the sample before doing anything with it, and you also risk an infinite loop if the readerOutput contains no samples.
Use either autorelease or ARC to get rid off the "too early release syndrom". In both cases the release-task is managed by someone else. For a new project I'd suggest ARC.
i have an MJPEG stream over RTSP/UDP from which i want to generate JPEGs for a UIImageView with [UIImage initWithData:]. Most of the time this works good, but sometimes i get corrupt images and log messages like:
ImageIO: <ERROR> JPEGCorrupt JPEG data: premature end of data segment
My Question is: how can i see (during runtime), that such message occurs? Unfortunatly 'initWithData' has no error output, is there any other way?
Thank you.
Edit: in this case, the initWithData does return a valid UIImage object, not nil!
There is a similar thread to this one on stack overflow: Catching error: Corrupt JPEG data: premature end of data segment.
There solution is to check for the header bytes FF D8 and ending bytes FF D9. So, if you have image data in an NSData, you can check it like so:
- (BOOL)isJPEGValid:(NSData *)jpeg {
if ([jpeg length] < 4) return NO;
const char * bytes = (const char *)[jpeg bytes];
if (bytes[0] != 0xFF || bytes[1] != 0xD8) return NO;
if (bytes[[jpeg length] - 2] != 0xFF || bytes[[jpeg length] - 1] != 0xD9) return NO;
return YES;
}
Then, to check if JPEG data is invalid, just write:
if (![self isJPEGValid:myData]) {
NSLog(#"Do something here");
}
Hope this helps!
The initWithData: method should return nil in such cases.
Try :
UIImage *myImage = [[UIImage alloc] initWithData:imgData];
if(!myImage) {
// problem
}
I've encountered the same problem in this exact situation.
It turned out that I was passing an instance of NSMutableData to global queue for decoding. During decoding the data in NSMutableData was overwritten by next frame received from network.
I've fixed the errors by passing a copy of the data. It might be better to use a buffer pool to improve performance:
NSData *dataCopy = [_receivedData copy];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
ZAssert([self isJPEGValid:dataCopy], #"JPEG data is invalid"); // should never happen
UIImage *image = [UIImage imageWithData:dataCopy];
dispatch_async(dispatch_get_main_queue(), ^{
// show image
});
});
I'm trying to send files over a bluetooth connection.
I've got this to work thanks to my previous post, but the method wasn't memory efficient.
The whole file was loaded into memory before it was sent, and this created problems (and crashed the app) for files > ~20 MB. So I've come up with a new method of only reading parts of the file I need at a specific time, creating packets from the data, sending them and repeating the process for each 8KB chunk of the file.
So I made a class method that generates the packet, informs the controller that a packet is available (through a protocol) and repeats this process for each packet that's available.
Here's the code for the packet generator:
+ (void)makePacketsFromFile:(NSString *)path withDelegate:(id <filePacketDelegate>)aDelegate {
if (![[NSFileManager defaultManager] fileExistsAtPath:path] || aDelegate == nil) return;
id <filePacketDelegate> delegate;
delegate = aDelegate;
const NSUInteger quanta = 8192;
uint filesize;
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
filesize = [[fileAttributes objectForKey:NSFileSize] intValue];
int numOfPackets = (int)ceil(filesize/quanta);
if (numOfPackets == 0) numOfPackets = 1;
NSLog(#"filesize = %d, numOfPackets = %d or %.3f", filesize, numOfPackets, (float)ceil(filesize/quanta));
NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:path];
int offset = 0;
int counter = 0;
while (counter != (numOfPackets + 1)) {
uint len = (filesize < quanta) ? filesize : quanta;
if (counter == numOfPackets) {
len = filesize - offset;
}
[handle seekToFileOffset:offset];
NSData *fileData = [handle readDataOfLength:len];
file_packet *packet = [[file_packet alloc] initWithFileName:[path lastPathComponent] ofType:0 index:counter];
packet.packetContents = fileData;
[fileData release];
packet.checksum = #"<to be done>";
packet.numberOfPackets = [NSString stringWithFormat:#"%d", numOfPackets];
[delegate packetIsReadyForSending:packet];
[packet release];
offset += quanta;
counter++;
}
[handle closeFile];
}
And receiving and sending the file:
- (void)packetIsReadyForSending:(file_packet *)packet {
NSData *fileData = [packet dataForSending];
[self.connectionSession sendDataToAllPeers:fileData withDataMode:GKSendDataReliable error:nil];
}
- (void)sendFileViaBluetooth {
[file_packet makePacketsFromFile:selectedFilePath withDelegate:self];
}
However, the memory use is quite large. Not what I expected.
I'm a bit stuck on this, as I wouldn't like to restrict bluetooth sharing to files smaller than 20MB.
Any help appreciated.
Edit:
I've been thinking about this for a while and have come to the conclusion that it's not my code that's causing the memory allocation issue, it's GameKit's stack for sending the packets.
I think I'm generating too many packets too fast, and I don't think GameKit is sending them quick enough.
So I am now thinking about a way to see when GameKit has sent a packet, and only generating another one once GameKit confirms it was sent.
There’s one clear memory management issue in your code:
NSData *fileData = [handle readDataOfLength:len];
fileData wasn’t obtained via NARC (a method whose name contains new, alloc, retain, copy), so you don’t own it, hence you don’t release it.
[fileData release];
Oops.
As for the memory use, one thing to consider is that even though you release packet you can’t really tell what’s going on inside -[GKSession sendDataToAllPeers:withDataMode:error:]. It is possible that the method internally creates autoreleased objects that only end up being released after +makePacketsFromFile:withDelegate: has finished executing. I suggest you use a new autorelease pool in every loop iteration:
while (counter != (numOfPackets + 1)) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
uint len = (filesize < quanta) ? filesize : quanta;
…
counter++;
[pool drain];
}
By doing this, any autoreleased object inside that loop will be effectively released at the end of each loop iteration.
Checkout the NSInputStream. With it you can open a stream and only read out a buffer of bytes at a time.