EXC_BAD_ACCESS after executing an asynchronous URL request - objective-c

I am trying to execute an asynchronous URL request:
NSURLRequest* request=[NSURLRequest requestWithURL: [NSURL URLWithString: #"http://www.youtube.com/"]];
NSOperation* operation=[NSOperation new];
NSOperationQueue* queue=[NSOperationQueue new];
[operation setCompletionBlock: ^()
{
}];
[queue addOperation: operation];
[NSURLConnection sendAsynchronousRequest: request queue: queue completionHandler: ^(NSURLResponse* response, NSData* data, NSError* error)
{
NSLog(#"%#",[data bytes]);
}];
So I just need the data contained in the URL.But after few seconds (probably in the time that the newly created thread loads the data) the application crashes:
thread 6 : EXC_BAD_ACCESS (code=13, address=0x0)
The exact point is objc_msgSend_vtable5, in the NSLog line, when I try to print data bytes.
PS: I'm using ARC.

The problem is that the method -[NSData bytes] has return type void * (and is a pointer to a raw byte buffer), but you're treating the return value as an object by trying to log it using the %# format specifier. To fix this, just print just print data rather than [data bytes]; replace your line
NSLog(#"%#", [data bytes]);
with
NSLog(#"%#", data);

Related

may be NSURLSession or NSMutableURLRequest does not release memory calling through loop

My Problem :- NSURLSession does not release previous call API Memory of 5MB Chunk
I am calling APIs in do while loop to upload 500MB video. I have to send every 5MB chunk with different APIs not in one API.
For Example 500MB Video and create 100 chunks and send using NSURLSession so calls 100 times but NSURLSession does not release previous call API Memory of 5MB Chunk
(1) I have created 5MB Chunk.
(2) read File using NSFileHandle with 5MB Chunk using OffSet
(3) change URL for all chunk and call api (necessary to send all chunk at different URL)
I do not want convert video in NSData in (500MB) i want to send chunk through API
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
//dispatch_group_t group = dispatch_group_create();
__block NSUInteger counterFailure = 0; // PSM Anks calling blob by url fails 4 time, exit for funtion
arrBlobIds = [[NSMutableArray alloc]init];
__block NSInteger intBlockIdCount = 100000; // PSM Anks blobid to assign id to every blob
__block NSUInteger offset = 0; // PSM Anks offset to start posution to read data
NSUInteger length = [[[NSFileManager defaultManager] attributesOfItemAtPath:[urlOfGallaryVideo path] error:nil] fileSize]; // PSM anks total lenght of media
NSUInteger intChunkSize = (5000 * 1024); // PSM anks chunk size
while (offset < length){
//dispatch_group_enter(group);
NSLog(#"offset 1 : %lu",(unsigned long)offset);
// PSM Anks Creat Chunk from file according to length
NSUInteger intThisChunkSize = length - offset > intChunkSize ? intChunkSize : length - offset;
//NSData* chunk = [NSData dataWithBytesNoCopy:(char *)[myBlob bytes] + offset length:intThisChunkSize freeWhenDone:NO];
__block NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:[urlOfGallaryVideo path]];
[fileHandle seekToFileOffset:offset];
__block NSData *dataChunk = [fileHandle readDataOfLength:intThisChunkSize];
// PSM Anks Convert block id in Base 64 encode
NSData *dataBlockId = [[NSString stringWithFormat:#"%ld",intBlockIdCount] dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64BlockId = [dataBlockId base64EncodedStringWithOptions:0];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#&comp=block&blockid=%#",[dictAzureSAS objectForKey:#"uri"],base64BlockId]]];
[request setHTTPMethod:#"PUT"];
[request setHTTPBody:dataChunk];
//[request setValue:[NSString stringWithFormat:#"%lu",(unsigned long)dataChunk.length] forHTTPHeaderField:#"Content-Length"]; // Do not need
//[request setValue:strVideoMIMEType forHTTPHeaderField:#"Content-Type"]; // Do not need
[request addValue:#"BlockBlob" forHTTPHeaderField:#"x-ms-blob-type"];
//NSLog(#"request : %#",request);
//NSLog(#"dataChunk.length : %lu \n url for blob %# \n request %#",(unsigned long)dataChunk.length,[NSURL URLWithString:[NSString stringWithFormat:#"%#&comp=block&blockid=%#",[dictAzureSAS objectForKey:#"uri"],base64BlockId]],request);
NSLog(#"dataChunk.length : %lu",(unsigned long)dataChunk.length);
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.URLCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
config.timeoutIntervalForRequest = 20.0;
config.URLCredentialStorage = nil;
config.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
///NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
config = nil;
//NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *dataTaskForUpload = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"Finished with status code: %li", (long)[(NSHTTPURLResponse *)response statusCode]);
NSLog(#"response: %#", response);
NSLog(#"error: %# %#", error,error.description);
if(data != nil) // PSM anks Check Data is nil otherwise app crashed
{
NSMutableArray *jsonList = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSLog(#"jsonList: %#", jsonList);
}
dataChunk = nil;
fileHandle = nil;
if(error == nil)
{
/*
// PSM Anks First Add Then increment
[arrBlobIds addObject:base64BlockId];
intBlockIdCount++;
offset += intThisChunkSize;
counterFailure = 0;
*/
}
else
{
/*
counterFailure++;
offset = intThisChunkSize;
if(counterFailure >= 4)
{
NSLog(#"Enter counter Failure %lu",(unsigned long)counterFailure);
counterFailure = 0;
[self stopLoader];
[CommonAlertViewMsgs cannotConnectServer:self];
return ;
}
*/
}
//dispatch_group_leave(group);
dispatch_semaphore_signal(semaphore);
[session finishTasksAndInvalidate];
}];
[dataTaskForUpload resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(#"offset 2 : %lu",(unsigned long)offset);
}
Your problem is probably that your NSData objects are being put in an autorelease pool, which is never getting drained until after your main dispatch_async block completes. You can probably fix the immediate problem by adding an #autoreleasepool to your while loop; i.e.
while (offset < length) #autoreleasepool {
However, your dispatch_semaphore_wait at the end is blocking a dispatch queue, which is generally discouraged. What I would recommend would be, in addition to adding the #autoreleaspool to the while loop, to use a dispatch group instead of the semaphore, and to use dispatch_group_notify at the end instead of dispatch_group_wait. This will cause your main dispatch_async block to complete, which will release any autoreleased objects which have accumulated in it, and then the block you pass to dispatch_group_notify will be called once all your operations are complete.
EDIT: Knowing a little more about what you're trying to do, here is an alternative that will run the processes one at a time, while still not blocking the dispatch queue:
(pseudocode)
- (void)sendRequestWithOffset:length:otherParameters: {
send the url request {
do what you do
if newOffset < length {
[self sendRequestWithOffset:newOffset length:length otherParameters:whatever];
} else {
hooray, we're done
}
}
}
It's sort of like a recursive call (but not really, since we won't accumulate stack frames). Basically it's an asyncronous version of your while loop; your tasks occur one at a time, there's no blockage of dispatch queues, and since each dispatch queue has its own autorelease pool, you won't get buildup of autoreleased objects either and your memory usage should stay reasonable.
If you want to minimize your memory footprint while the uploads run, you should:
Try #autoreleasepool as advised by Charles;
Reuse one NSURLSession rather than recreating a new NSURLSession each time inside your loop; and
Use file-based upload tasks (e.g. uploadTaskWithRequest:fromFile:) rather than dataTask.
Note, with file-based upload tasks, you can still configure your NSURLRequest like you are now, but don't set httpBody, but instead supply that as the fromFile parameter of the above method.
Once you have this memory issue behind you, you can pursue other approaches to improve performance, too, but let's not get ahead of ourselves.

NSOperationQueue addOperationWithBlock with return in iOS

I have written method -(void) getStatus:(NSString*) url with return type,
-(NSString) getStatus:(NSString*) statusUrl
{
NSURL *urlObj = [NSURL URLWithString:[statusUrl
stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSError *err;
NSString *response = [NSString stringWithContentsOfURL: urlObj encoding:
NSUTF8StringEncoding error:&err];
return response;
}
But stringWithContentsOfURL is performing operation in main Thread, So while calling this method application struck for a second.
So I need to perform stringWithContentsOfURL function in background thread and after getting the response i want to return the response.
My current code:
-(NSString) getStatus:(NSString*) statusUrl
{
NSURL *urlObj = [NSURL URLWithString:[statusUrl
stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSError *err;
NSOperationQueue *rbQueue = [[[NSOperationQueue alloc] init] autorelease];
[rbQueue addOperationWithBlock:^{
NSString *response = [NSString stringWithContentsOfURL: urlObj
encoding: NSUTF8StringEncoding error:&err];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
return response;
}];
}];
return #"";
}
But now me receiving a empty string #"", I can not receive response i got from server. Is there any way to do the same task..
Have you noticed that there should be some changes with this approach to complete the task? You hardly can use getters this way because of the nature of asynchronous methods. Or the getters will block the main thread in their turn.
To avoid this I could recommend you to use NSNotification to update the UI after you complete the server request in the background thread;
or change your getter method definition to pass the result from the background thread to the main thread, but asynchronously:
- (void)getStatusAsychronously:(NSString *)statusUrl withCompletionBlock:(void(^)(NSString *result))completionBlock;
or even consider subclassing NSOperation object for server request. The NSOperation subclasses are really handy with NSOperationQueue instances and could provide some more useful features like the cancellation of operation.

Objective C - JSON data unavailable after being returned correctly

I'm trying to put in a tableview the result of a JSON that is returned correctly.
The problem is that I can only access the data within the block where the information is returned, even assigning the result to a instance variable.
My code is as follows:
NSURL *url = [NSURL URLWithString:#"http://www.my_url.com.br/app/?type=list&info=15,16&lat=some_data&long=some_data"];
ASIHTTPRequest *_request = [ASIHTTPRequest requestWithURL:url];
ASIHTTPRequest *request = _request;
request.requestMethod = #"POST";
[request addRequestHeader:#"Content-Type" value:#"application/json"];
[request setDelegate:self];
[request setCompletionBlock:^{
NSString *responseString = [request responseString];
NSLog(#"Response: %#", responseString);
NSDictionary *root = [NSJSONSerialization JSONObjectWithData:request.responseData options:0 error:nil];
self.data = [root objectForKey:#"listing"];
NSLog(#"Data returned: %#", data); //Everything works well. The data returned is printed correctly
}];
[request setFailedBlock:^{
NSError *error = [request error];
NSLog(#"Error: %#", error.localizedDescription);
}];
[request startAsynchronous];
NSLog(#"Data only: %#", data); //At this point, the "data" content is nil
This is my "ListDataController.h" file definitions :
#interface ListDataController : UITableViewController{
ApplicationCell *tmpCell;
NSArray *data;
UILabel *status ;
UINib *cellNib;
}
#property (nonatomic, retain) IBOutlet ApplicationCell *tmpCell;
#property (nonatomic, retain) NSArray *data;
The JSON returned :
Data self: (
{
Icon = "Baseball.png";
Name = Baseball;
NumRatings = 106;
Price = "$2.98";
Publisher = "Super Sportz, Inc.";
Rating = "3.5";
},
{
Icon = "Checkers.png";
Name = Checkers;
NumRatings = 87;
Price = Free;
Publisher = "Gameitoids, Inc.";
Rating = 4;
}
)
The question is : why I can not access the instance variable "data" outside the block, even assigning it the result of json?
You can access data outside the block, but it is set to nil. You are probably setting data properly, just not at the moment you thought.
By the time you execute the final log, the completion block has not run yet. The request is asynchronous, meaning the completion block will execute at some point in the future. This is a key concept to understand when working with blocks.
UPDATE:
To procress the data once it has been retreived, you can call the desired from the completion block:
[request setCompletionBlock:^{
NSString *responseString = [request responseString];
NSLog(#"Response: %#", responseString);
NSDictionary *root = [NSJSONSerialization JSONObjectWithData:request.responseData options:0 error:nil];
self.data = [root objectForKey:#"listing"];
NSLog(#"Data returned: %#", data); //Everything works well. The data returned is printed correctly
[self handleData:self.data]; // you would have to define this method your self
}];
One thing to consider, if you are updating the UI based on self.data, you would want to make sure you execute handleData on the main thread.
Some Links:
Objective C Blocks
Concurrency Programming Guide
timing (like the 2 answers above)
to write into variables living outside the block you have to use the __block directive before the variable declaration. I am not sure if instance variables are special to this, but to be sure you could use some temp variable for storing the result before assign it finally to the ivar.
NSLog(#"Data returned: %#", data); //Everything works well. The data returned is printed correctly
Fine. What is your problem? This is the completion block. It is executed when all data was received. Any access to data before that point in time will end up in nil in its best case or outated/wron/unpredictable data in its worst case.
What happens here is your log
NSLog(#"Data only: %#", data); //At this point, the "data" content is nil
will be executed well before getting the data.You are using asynchronous request using block that is the execution will happen on a seperate thread without affecting the main thread execution
and on completion of the seperate thread the completion block get executed .Hence response only loads into data at this point only
For better understandability
Put breakpoints in both log and completion block
You can see the execution on the log happens before the completion block
EDIT :
you can do the operations in the completion block after response assigned to the data
[request setCompletionBlock:^{
NSString *responseString = [request responseString];
NSLog(#"Response: %#", responseString);
NSDictionary *root = [NSJSONSerialization JSONObjectWithData:request.responseData options:0 error:nil];
self.data = [root objectForKey:#"listing"];
NSLog(#"Data returned: %#", data); //Everything works well. The data returned is printed correctly
//DO here WHATEVER you want to do after getting response
//eg:
[table reloadData]
}];
The request when get all the data will execute the completion block.So whatever you want to do after getting the response can be done in the completion block

ASIHttp Synchronous request is running delegate methods after returning

I am trying to download a file from a server. My code is following. In didFinishLaunchingWithOptions method, I create a new thread using detachNewThreadSelector which runs the following code.
NSString *destPath = [self.home_dir_path stringByAppendingPathComponent:[NSString stringWithFormat:#"_%#",content_data_file_name]];
[ContentBO downloadFile:destPath content_name:content_data_file_name];
if([self updatesAvailable]){
//update content
}else{
//launch app
}
My code for downloadFile is:
#try{
NSString *url = [NSString stringWithFormat:#"%#/%#",ServerURL,content_name];
NSLog(#"downloading URL is: %#",url);
self.request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:[url stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]]];
[self.request setRequestMethod:#"GET"];
[self.request setDownloadDestinationPath:destFilePath];
NSLog(#"destination path is: %#",destFilePath);
[self.request setTimeOutSeconds:30];
[self.request setDelegate:self];
[self.request startSynchronous];
NSError *error = [self.request error];
NSData *receivedData = nil;
if (!error) {
isSuccess = YES;
self.responseStr = [request responseString];
receivedData = [NSData dataWithData:[self.request responseData]];
}
else {
isSuccess = NO;
NSLog(#"The following error occurred: %#", error);
}
}#catch(NSException *e){
NSLog(#"exception occured.");
}
What my understanding about synchronous call is that this is a blocking call and control should not go below
[ContentBO downloadFile:destPath content_name:content_data_file_name];
until control is out of requestFinished method of ASIHTTPRequestDelegate. In my case what happening is that the control is simultaneously executing code in requestFinished and below
[ContentBO downloadFile:destPath content_name:content_data_file_name];
But I don't want the control to go below [ContentBO downloadFile...] before coming out of requestFinished method.
The requestFinished delegate call is run on the main thread asynchronously, and your code is not running on the main thread, so it is expected that both would run at the same time.
However, as you are using synchronous requests why not remove the contents of requestFinished and put the code after the 'startSyncronous' line? You are guaranteed the request has finished when startSynchronous returns.
In one of my projects the app had to do heavy server side data syncing. In that process one operation should had start after the successful execution of it's previous process and I was using ASIHttp synchronous requests in that. I was facing the same issue you mentioned, so to tackle it I used NSCondiiton. All it requires that you lock the thread after you call:
[self.request startSynchronous];. When the requests delegate method is called after the exection of the request, issue a signal command, and the next line of the code after the thread lock statement will be executed. Here is a rough example:
//declare a pointer to NSCondition in header file:
NSCondition *threadlock;
-(id) init
{
threadlock = [[NSCondition alloc] init]; //release it in dealloc
}
-(void)downLoadFile
{
[thread lock];
//your request code
[self.request setDidFinishSelector:#selector(downLoadFileRequestDone:)];
[self.request setDidFailSelector:#selector(downLoadFileRequestWentWrong:)];
[self.request startSynchronous];
[thread wait];
//the lines here will be executed after you issue a signal command in the request delegate method
[thread unlock];
}
-(void) downLoadFileRequestDone:(ASIHTTPRequest *)request
{
[thread lock];
//perform desire functionality
//when you are done call:
[thread signal];
[thread unlock];
}
It worked perfect for me... hope it will help.

HTTP server works in Cocoa application but not test case -- run loop issue?

I'm trying to add a GHUnit test case to this SimpleHTTPServer example. The example include a Cocoa application that works fine for me. But I can't duplicate the behavior in a test case.
Here is the test class:
#import <GHUnit/GHUnit.h>
#import "SimpleHTTPServer.h"
#interface ServerTest : GHTestCase
{
SimpleHTTPServer *server;
}
#end
#implementation ServerTest
-(void)setUpClass
{
[[NSRunLoop currentRunLoop] run];
}
- (NSString*)requestToURL:(NSString*)urlString error:(NSError**)error
{
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:1];
NSURLResponse *response = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:error];
NSString *page = nil;
if (error == nil)
{
NSStringEncoding responseEncoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)[response textEncodingName]));
page = [[NSString alloc] initWithData:data encoding:responseEncoding];
[page autorelease];
}
return page;
}
- (void)testPortReuse
{
unsigned int port = 50001;
NSError *error = nil;
NSString *path, *url;
server = [[SimpleHTTPServer alloc] initWithTCPPort:port delegate:self];
sleep(10);
path = #"/x/y/z";
url = [NSString stringWithFormat:#"http://localhost:%u%#", port, path];
[self requestToURL:url error:&error];
GHAssertNil(error, #"%# : %#", url, error);
[server release];
}
- (void)processURL:(NSURL *)path connection:(SimpleHTTPConnection *)connection
{
NSLog(#"processURL");
}
- (void)stopProcessing
{
NSLog(#"stopProcessing");
}
#end
I've tried sending requests via NSURLRequest and also (during the sleep) via a web browser. The delegate methods -processURL and -stopProcessing are never called. The problem seems to be that [fileHandle acceptConnectionInBackgroundAndNotify] in SimpleHTTPServer -initWithTCPPort:delegate: is not causing any NSFileHandleConnectionAcceptedNotifications to reach the NSNotificationCenter -- so I suspect a problem involving run loops.
The problem seems to be with the NSFileHandle, not the NSNotificationCenter, because when [nc postNotificationName:NSFileHandleConnectionAcceptedNotification object:nil] is added to the end of initWithTCPPort:delegate:, the NSNotificationCenter does get the notification.
if (error == nil)
That should be:
if (data != nil)
error here is the passed-in pointer to an NSError* - it will only be nil if the caller passed nil instead of a reference to an NSError* object, which isn't what your -testPortReuse method does.
It would also be incorrect to dereference it (as in if (*error == nil)), because error arguments are not guaranteed to be set to nil upon error. The return value indicates an error condition, and the value returned in the error argument is only meaningful or reliable if there is an error. Always check the return value to determine if an error happened, then check the error parameter for details only if something did in fact go wrong.
In other words, as it's written above, your -requestToURL:error: method is incapable of handling success. Much like Charlie Sheen. :-)