I am trying to play video that is coming from an IP camera in iOS, but currently I tried 2 methods and they both seem to be filling up the memory of my iOS device really fast. I am using ARC for this project.
My IP camera uses Videostream.cgi (Foscam), which is a well-known way for IP cameras to stream 'video' through the browser.
So, I tried 3 ways, which all end up in crashing my iOS app, with an out-of-memory exception.
1. Putting an UIWebView on my UIViewController and call the CGI directly using a NSURLRequest.
NSString* url = [NSString stringWithFormat:#"http://%#:%#/videostream.cgi?user=%#&pwd=%#&rate=0&resolution=%ld", camera.ip, camera.port, camera.username, camera.password, (long)_resolution];
NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
webView = [[UIWebView alloc] init];
[webView loadRequest:request];
2. Putting an UIWebView on my UIViewController and creating a piece of HTML (in code) which includes a <img> tag which has a source to the CGI mentioned before. (see: IP camera stream with UIWebview works on IOS 5 but not on IOS 6)
NSString* imgHtml = [NSString stringWithFormat:#"<img src='%#'>", url];
webView = [[UIWebView alloc] init];
[webView loadHTMLString:imgHtml];
3. Using a custom control, based on a UIImageView, which fetches data continuously. https://github.com/mateagar/Motion-JPEG-Image-View-for-iOS
All of these things burn through memory and even when I try to remove them and re-add them after a certain period of time, but this does not seem to fix the problem. Memory won't be released and the iPad crashes.
UPDATE:
I am currently modifying option 3 of the solutions I tried. It is based on a NSURLConnection and the data it retrieves.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
if (!_receivedData) {
_receivedData = [NSMutableData new];
}
[_receivedData appendData:data];
NSRange endRange = [_receivedData rangeOfData:_endMarkerData
options:0
range:NSMakeRange(0, _receivedData.length)];
NSUInteger endLocation = endRange.location + endRange.length;
if (_receivedData.length >= endLocation) {
NSData *imageData = [_receivedData subdataWithRange:NSMakeRange(0, endLocation)];
UIImage *receivedImage = [UIImage imageWithData:imageData];
if (receivedImage) {
NSLog(#"_receivedData length: %d", [_receivedData length]);
self.image = receivedImage;
_receivedData = nil;
_receivedData = [NSMutableData new];
}
}
if (_shouldStop) {
[connection cancel];
}
}
_receivedData is a NSMutableData object. which I try to "empty" once an image is retrieved from the stream. The part in if (receivedImage) is called when it is supposed to be called. The length of the _receivedData object is also not increasing, it stays around the same size (~ 14k), so that seems to work.
But somehow, with every didReceiveData the memory my app is using increases, even when I disable the line self.image = receivedImage.
UPDATE
As iosengineer suggested, I have been playing with autorelease pools, but this does not solve the problem.
Using Instruments I found out that most of the allocations are done by CFNetwork, in the method HTTPBodyData::appendBytes(unsigned char const*, long). (This allocates 64KB at a time and keeps them alive).
The next step I'd take would be to analyse the request/response patterns using Charlie, step through the source using Xcode and probably write my own solution using NSURLSession and NSURLRequest.
Streams don't just create themselves - something is pulling in data from the responses and not getting rid of it fast enough.
Here's my guess on what is possibly happening:
When you download something using NSURLRequest, you create an instance of NSMutableData to collect the responses in chunks until you are ready to save it to disk. In this case, the stream never ends and so the store grows massive and then bails.
A custom solution to this would have to know when it's safe to ditch the store based on the end of a frame (for example). Good Luck! Instruments is your friend.
P.S. Beware of autoreleased memory - use autoreleasepools wisely
In your revised question, the code sample shows a few objects that are created using autoreleased memory. The appropriate use of autorelease pools should fix this. It should be fairly straight-forward to see which object is causing the most problems and if your problems have been solved by profiling the app using Instruments (allocation tool).
Of particular interest, the UIImage imageWithData: call should definitely be wrapped, as that is creating a new image object every time.
Also subdataWithRange creates a new object which is only released once the pool is flushed.
I don't use the "new" syntax for creation ever so I can't recall how that one actually works. I always use alloc init.
Wrap MOST of this whole routine with this:
#autoreleasepool
{
ROUTINE
}
That will make it so that at each chunk of data received, the pool will be drained and you will mop up any autoreleased memory objects.
I rewrote the MotionJpegImageView thing, which was causing all my problems:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
if (!_receivedData) {
_receivedData = [NSMutableData new];
}
[_receivedData appendData:data];
NSRange endRange = [_receivedData rangeOfData:_endMarkerData
options:0
range:NSMakeRange(0, _receivedData.length)];
if (endRange.location == NSNotFound) {
return;
}
#autoreleasepool {
UIImage *receivedImage = [UIImage imageWithData:_receivedData];
if (receivedImage) {
self.image = receivedImage;
}
else {
DDLogVerbose(#"Invalid image data");
}
}
[_receivedData setLength:0];
if (_shouldStop) {
[connection cancel];
DDLogVerbose(#"Should stop connection");
}
}
Also, my connections were opened multiple times in the end by not correctly canceling the old ones. Pretty stupid mistake, but for the people wanting to know how it works. Code is mentioned above.
Related
I'm currently building an app that pulls large JSON file via an API request.
During the download-decoding-storing the data I get memory warnings (over 500MB).I found a solution to avoid to overload the memory and keep it at most at 300MB by adding #autoreleasepool { } function manually.
#autoreleasepool {
NSString * result = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&err];//150
decodeData = [[NSData alloc] initWithBase64EncodedString:result options:0];//100
}
#autoreleasepool {
NSString * decodeString = [[NSString alloc] initWithData:decodeData encoding:NSUTF8StringEncoding];//100
NSError * jsonError;
NSData * objectData = [decodeString dataUsingEncoding:NSUTF8StringEncoding];//100
json = [NSJSONSerialization JSONObjectWithData:objectData options:NSJSONReadingMutableContainers error:&jsonError];//50
if(!jsonError){
[defults setObject:json forKey:#"data_object"];//50
}
}
Is there a better way of doing this for memory management?
Placing an #autorelease block around code that generates lots of throw-away (autoreleased) objects is not only valid, but recommended. This obviously also applies to few, large objects :)
Code which is running on the main thread has an autorelease pool available, but it may not be enough. The pool is drained at the bottom of the runloop, and if many autoreleased objects are created in a single runloop cycle, you may need a pool specifically to cleanup these objects to avoid running out of memory. This happens often with loops, and it's recommended that loop bodies be #autorelease blocks in such situations.
In terms of your specific issue, 300MB for one JSON structure is pushing it. If at all possible, you should try and break that up into smaller objects and parse them separately.
Hi, I'm trying to fetch the user's friends profile pic from facebook and load it in my table and it works fine, but however it takes a long time based on number of friends you have I have noticed that in my fetchPeopleimages function the part of [NSData dataWithContentsOfURL:imageURL] is making the delay. I've searched through stackoverflow and it seems that I may have to implement the NSURLConnection sendAsynchronousRequest
method or cache. But there is no proper example. Can anyone please provide a solution to this? If I have to implement those methods please do give a example on how should I implement it in my code.
-(void) fetchPeopleimages
{
if ([listType isEqualToString:#"Facebook"])
{
int count=0;
NSMutableArray *imageArray=[[NSMutableArray alloc]initWithCapacity:[_peopleImageList count]];
for(NSString *imageUrl in _peopleImageList)
{
NSData *imageData = nil;
NSString *imageURLString = imageUrl;
count++;
NSLog(#"URL->%#,count->%d", imageURLString,count);
if (imageURLString)
{ //This block takes time to complete
NSURL *imageURL = [NSURL URLWithString:imageURLString];
imageData = [NSData dataWithContentsOfURL:imageURL];
}
if (imageData)
{
[imageArray addObject:imageData];
}
else
{
[imageArray addObject:[NSNull null]];
}
}
_peopleImageList=imageArray;
NSLog(#"%#",_peopleImageList);
}
}
Never ever use __withContentsOfURL in an iOS app. As you can see, it blocks the thread it runs on, making your app appear jerky or unresponsive. If you're really lucky, the iOS watchdog timer will kill your app if one of those operations takes too long.
You must use an asynchronous network operation for something like this.
Apple has a sample app that does exactly this:
https://developer.apple.com/library/ios/samplecode/LazyTableImages/Introduction/Intro.html
I'm having an issue where relatively large images never seem to get released from memory (1MB~5MB in size). This following block of code gets called while a user scrolls through a set of images. After about 15 images the application will crash. Sometimes "didReceiveMemoryWarning" gets called, and sometimes it doesn't--the application will just crash, stop, quit debugging, and not halt on any line of code--nothing. I assume this is what happens when the device runs out of memory? Another concern is that 'dealloc' never seems to get called for the subclassed 'DownloadImageOperation'. Any ideas?
Getting and setting the image:
//Calling this block of code multiple times will eventually cause the
// application to crash
//Memory monitor shows real memory jumping 5MB to 20MB increments in instruments.
//Allocations tool shows #living creeping up after this method is called.
//Leaks indicate something is leaking, but in the 1 to 5 kb increments. Nothing huge.
DownloadImageOperation * imageOp = [[DownloadImageOperation alloc] initWithURL:imageURL localPath:imageFilePath];
[imageOp setCompletionBlock:^(void){
//Set the image in a UIImageView in the open UIViewController.
[self.ivScrollView setImage:imageOp.image];
}];
//Add operation to ivar NSOperationQueue
[mainImageQueue addOperation:imageOp];
[imageOp release];
DownloadImageOperation Definition:
.h file
#import <Foundation/Foundation.h>
#interface DownloadImageOperation : NSOperation {
UIImage * image;
NSString * downloadURL;
NSString * downloadFilename;
}
#property (retain) UIImage * image;
#property (copy) NSString * downloadURL;
#property (copy) NSString * downloadFilename;
- (id) initWithURL:(NSString *)url localPath:(NSString *)filename;
#end
.m file
#import "DownloadImageOperation.h"
#import "GetImage.h"
#implementation DownloadImageOperation
#synthesize image;
#synthesize downloadURL;
#synthesize downloadFilename;
- (id) initWithURL:(NSString *)url localPath:(NSString *)filename {
self = [super init];
if (self!= nil) {
[self setDownloadURL:url];
[self setDownloadFilename:filename];
[self setQueuePriority:NSOperationQueuePriorityHigh];
}
return self;
}
- (void)dealloc { //This never seems to get called?
[downloadURL release], downloadURL = nil;
[downloadFilename release], downloadFilename = nil;
[image release], image = nil;
[super dealloc];
}
-(void)main{
if (self.isCancelled) {
return;
}
UIImage * imageProperty = [[GetImage imageWithContentsOfFile:downloadFilename andURL:downloadURL] retain];
[self setImage:imageProperty];
[imageProperty release];
imageProperty = nil;
}
#end
Get Image Class
.m file
+ (UIImage *)imageWithContentsOfFile:(NSString *)path andURL:(NSString*)urlString foundFile:(BOOL*)fileFound {
BOOL boolRef;
UIImage *image = nil;
NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
if (image==nil) {
boolRef = YES;
image = [UIImage imageWithContentsOfFile:[[AppDelegate applicationImagesDirectory] stringByAppendingPathComponent:[path lastPathComponent]]];
}
if (image==nil) {
boolRef = YES;
image = [super imageWithContentsOfFile:path];
}
if (image==nil) {
//Download image from the Internet
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
NSURL *url = [NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setTimeOutSeconds:120];
[request startSynchronous];
NSData *responseData = [[request responseData] retain];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
NSData *rdat = [[NSData alloc] initWithData:responseData];
[responseData release];
NSError *imageDirError = nil;
NSArray *existing_images = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[path stringByDeletingLastPathComponent] error:&imageDirError];
if (existing_images == nil || [existing_images count] == 0) {
// create the image directory
[[NSFileManager defaultManager] createDirectoryAtPath:[path stringByDeletingLastPathComponent] withIntermediateDirectories:NO attributes:nil error:nil];
}
BOOL write_success = NO;
write_success = [rdat writeToFile:path atomically:YES];
if (write_success==NO) {
NSLog(#"Error writing file: %#",[path lastPathComponent]);
}
image = [UIImage imageWithData:rdat];
[rdat release];
}
return image;
}
Apologies for this huge block of code. I really have no idea where the problem might be here so I tried to be as inclusive as possible. Thanks for reading.
The main issue in the operation not getting deallocated is that you have a retain cycle, caused by the reference of imageOp in the completion block. Consider your code that says:
DownloadImageOperation * imageOp = [[DownloadImageOperation alloc] initWithURL:imageURL localPath:imageFilePath];
[imageOp setCompletionBlock:^(void){
//Set the image in a UIImageView in the open UIViewController.
[self.ivScrollView setImage:imageOp.image];
}];
In ARC, you would add a __weak qualifier to the operation and use that rather than imageOp within the completionBlock, to avoid the strong reference cycle. In manual reference counting, you can avoid the retain cycle through the use the __block qualifier to achieve the same thing, namely to keep the block from retaining imageOp:
DownloadImageOperation * imageOp = [[DownloadImageOperation alloc] initWithURL:imageURL localPath:filename];
__block DownloadImageOperation *blockImageOp = imageOp;
[imageOp setCompletionBlock:^(void){
//Set the image in a UIImageView in the open UIViewController.
[self.imageView setImage:blockImageOp.image];
}];
I think if you do that, you'll see your operation getting released correctly. (See the "Use Lifetime Qualifiers to Avoid Strong Reference Cycles" section of Transitioning to ARC Release Notes. I know you're not using ARC, but this section describes both the ARC and manual reference counting solutions.)
If you don't mind, I had other observations regarding your code:
You shouldn't be updating the UI from the completionBlock without dispatching it to the main queue ... all UI updates should happen on the main queue:
DownloadImageOperation * imageOp = [[DownloadImageOperation alloc] initWithURL:imageURL localPath:filename];
__block DownloadImageOperation *blockImageOp = imageOp;
[imageOp setCompletionBlock:^(void){
//Set the image in a UIImageView in the open UIViewController.
UIImage *image = blockImageOp.image;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.imageView setImage:image];
}];
}];
You're using accessor methods in your init method. As a matter of good practice, you really shouldn't. See Don’t Use Accessor Methods in Initializer Methods and dealloc in the Advanced Memory Management Programming Guide.
While we may have fixed the problem of the operation not getting released, I would suspect that unless you've already coded your UIScrollViewDelegate calls to release images that have scrolled off the visible screen, that you'll continue to have memory issues. Having said that, you may have already tackled this issue, and if so, I apologize for even mentioning it. I only raise the issue as it would just be easy to fix this NSOperation problem, but then neglect to have the scroll view release images as they scroll off the screen.
I'm not sure your subclassed NSOperation will support concurrency as you're missing some of the key methods discussed in Defining a Custom Operation in the Concurrency Programming Guide. Perhaps you have done this already, but just omitted it for brevity. Alternatively, I think it's easier if you use one of the existing NSOperation classes (e.g. NSBlockOperation) which take care of this stuff for you. Your call, but if you pursue concurrency, you'll want to make sure you set the queue's maxConcurrentOperationCount to something reasonable, like 4.
You code has some redundant retain statements. Having said that, you have the necessary release statements, too, so you've ensured that you won't have problems, but it's just a little curious. Clearly, ARC gets you out of the weeds on that sort of stuff, but I appreciate that this is a big step. But when you get a chance, take a look at ARC as it saves you from having to worry about a lot of this.
You should probably run your code through the static analyzer ("Analyze" on the "Product" menu), as you have some dead stores and the like.
Here's a leak problem that I'm having trouble with. Most of this code is just here for context so you can see that the "response" NSData object is not what's leaking.
If I drill down into the touchJSON code, following the stack trace as given to me by the LEAKS tool, the leak apparently begins life at the line
*outStringConstant ....
But since this is such a commonly used library, I doubt it's the problem.
One note. This doesn't leak the first time it's executed, only every subsequent time. But it leaks a lot, so the response data is probably the actual data that's leaking.
Also, if anyone is familiar with touchJSON and this code, can you explain to me what this outStringConstant variable is and what it does? It doesn't appear to play any role, other than to be assigned a copy of theString, though if I remove that line the code crashes.
MY CODE is
dataRequestURL = [NSString stringWithFormat:#"http://www....", ...];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:dataRequestURL] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:&ts_response error:&ts_error];
NSArray *array = [[CJSONDeserializer deserializer] deserialize:response error:nil]; <- LEAKS HERE
TOUCHJSON CODE is
-(BOOL)scanJSONStringConstant:(NSString **)outStringConstant error:(NSError **)outError {
NSMutableString *theString = [[NSMutableString alloc] init];
if (outStringConstant != NULL) { *outStringConstant = [[theString copy] autorelease]; }
[theString release];
}
Is it possible to use the:
[NSMutableArray writeToURL:(NSString *)path atomically:(BOOL)AuxSomething];
In order to send a file (NSMutableArray) XML file to a url, and update the url to contain that file?
for example:
I have an array and I want to upload it to a specific URL and the next time the app launches I want to download that array.
NSMutableArray *arrayToWrite = [[NSMutableArray alloc] initWithObjects:#"One",#"Two",nil];
[arrayToWrite writeToURL:
[NSURL urlWithString:#"mywebsite.atwebpages.com/myArray.plist"] atomically:YES];
And at runtime:
NSMutableArray *arrayToRead =
[[NSMutableArray alloc] initWithContentsOfURL:[NSURL urlWithString:#"mywebsite.atwebpages.com/myArray.plist"]];
Meaning, I want to write an NSMutableArray to a URL, which is on a web hosting service (e.g. batcave.net, the URL receives the information and updates server sided files accordingly.
A highscore like setup, user sends his scores, the server updates it's files, other users download the highscores at runtime.
As for part one of your question,
I'll assume you want to use the contents of a NSMutableArray to form some sort of a URL request (like POST) that you will send to your web service and expect back some information...
There is no prebuilt way of sending the contents of a NSMutableArray to an URL but there are simple ways of doing this yourself. For example, you can loop through the data of your array and make use of NSURLRequest to create a URL request that complies with the interface of your web service. Once you've constructed your request you can send it by passing it a NSURLConnection object.
Consider this very simple and incomplete example of what the client-side code might look like using an Obj-C array to provide data...
NSMutableData *dataReceived; // Assume exists and is initialized
NSURLConnection *myConnection;
- (void)startRequest{
NSLog(#"Start");
NSString *baseURLAddress = #"http://en.wikipedia.org/wiki/";
// This is the array we'll use to help make the URL request
NSArray *names = [NSArray arrayWithObjects: #"Jonny_Appleseed",nil];
NSString *completeURLAsString = [baseURLAddress stringByAppendingString: [names objectAtIndex:0]];
//NSURLRequest needs a NSURL Object
NSURL *completeURL = [NSURL URLWithString: completeURLAsString];
NSURLRequest *myURLRequest = [NSURLRequest requestWithURL: completeURL];
// self is the delegate, this means that this object will hanlde
// call-backs as the data transmission from the web server progresses
myConnection = [[NSURLConnection alloc] initWithRequest:myURLRequest delegate: self startImmediately:YES];
}
// This is called automatically when there is new data from the web server,
// we collect the server response and save it
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"Got some");
[dataReceived appendData: data];
}
// This is called automatically when transmission of data is complete
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// You now have whatever the server sent...
}
To tackle part 2 of your question, the receiver of a web request will likely require some scripting or infrastructure to make a useful response.
Here, Answer in this question:
Creating a highscore like system, iPhone side
I couldn't edit my post because I posted from my iPhone as an anonymous user, sorry.