Logic Issue - NSObject class - Beginner - iphone

I have 2 questions.
1.) I am creating a NSObject class, and i am having the following code in it. (ASIHTTPRequest POST).
The name of the NSObject class is called, SendToServer. I call the class as follows;
SendToServer *sv = [[SendToServer alloc]];
sv.grabURLInTheBackground ;
NSLog(#"This line is executed ");
The following is the code that is in the SendToServer NSObject class.
- (void)grabURLInTheBackground
{
if (![self queue]) {
[self setQueue:[[[NSOperationQueue alloc] init] autorelease]];
}
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setDidFinishSelector:#selector(requestDone:)];
[request setDidFailSelector:#selector(requestWentWrong:)];
[[self queue] addOperation:request]; //queue is an NSOperationQueue
}
- (void)requestDone:(ASIHTTPRequest *)request
{
NSString *response = [request responseString];
}
- (void)requestWentWrong:(ASIHTTPRequest *)request
{
NSError *error = [request error];
}
The problem is that, the code executes the line sv.grabURLInTheBackground ; and before it executes the requestDone or requestWentWrong methods, it executes the NSLog (NSLog(#"This line is executed "); )
What i want my program to do is to complete all the operations in the SendToServer NSObject class and then Execute the NSLog (In a sequence).
First execute sv.grabURLInTheBackground ; once all the activities in that method/class is over, then return to the code and execute the other line which is NSLog(#"This line is executed "); .
2.) I need to return a String when the requestDone method is executed. How do i modify the code to do so;
- (NSString * )requestDone:(ASIHTTPRequest *)request {
}
but how do i edit [request setDidFinishSelector:#selector(requestDone:)];, for the above code ?
------------------------------------------------------------------------------------------------------------------------------------------
EDIT
I am doing this for user login. Upon button click i will be calling the grabURLInTheBackground method from the NSObject class. And the viewcontroller needs to know if the user login was successful or failed.
SendToServer *sv = [[SendToServer alloc]];
[sv grabURLInTheBackground] ;
NSLog(#"User login SUcess or failed %#", [sv userloginSucessOrFail]);
For example say [sv userloginSucessOrFail] returns if the user login was success or failed.
What hapence here, is that after [sv grabURLInTheBackground] is called, it directly goes and executes the NSLog(#"User login SUcess or failed %#", [sv userloginSucessOrFail]); line of code.
What i want is, i need to find a way to let my ViewCOntroller know if the user login was a Success or failure.

First: call init on your object.
Second: grabURLInTheBackground is a method not a property. It should by called with square brackets
So you code becomes:
SendToServer *sv = [[SendToServer alloc] init];
[sv grabURLInTheBackground];
NSLog(#"This line is executed ");
To accomplish point 1) you need to make a synchronous request
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
NSString *response = [request responseString];
}
The problem is that if this code is executed on the main thread is blocking (not good)...
For the second point. You can't.
EDIT:
What you have to do is something like the following steps:
Before calling grabURLInTheBackground you have to notify the user that a request is pending.. like putting an UIActivityIndicator, or disabling the UI,...
when you receive the callback then update the UI: hide the activity indicator, re-enable the UI... or if the request failed, notify the user.

Related

iOS: Passing objects into a ASIHTTPRequest Block?

I am trying to create a function which uses ASIHTTPRequest, and return an instance of a MyPhotoSource class as follows..
- (MyPhotoSource *) photoSourceForURL:(NSURL *)url {
MyPhotoSource *source;
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
.....
source = [[MyPhotoSource alloc] initWithPhotos:photoArray];
}];
[request startAsynchronous];
return source;
}
When I build the project, it gives me Assignment of a read-only variable 'source' error message.
I tried the following modification, however the project crashes at run-time.
__block MyPhotoSource *source;

Implementation of Async Request causing many leaks

I've inherited a project that uses of ASIHttpRequest for all network communication. I am unclear as to which specific version we're using. All I can tell is that, from the .h files, the oldest creation date on a particular file is 17/08/10 (ASIDataDecompressor).
We're using completion and failure blocks. For some reason, the failure block is often triggered, which should only really happen if the server fails to respond. Our logs look sane, and we haven't received any notifications (Airbrake) that there were server problems around the time the errors occur, so for now I'm moving forward with the assumption that our server is fine and it's the app that is the culprit.
I decided to run the app through Instruments (Leaks) and was astonished to see that when I force a request to fail, ~27 leaks are created immediately. I'm don't know how to get around Instruments all that well, so I'm not really sure what to do with the information now that I have it.
I figured I'd post my code to see if there's anything glaring.
In viewDidLoad, this code is executed
[[MyAPI sharedAPI] getAllHighlights:pageNumber:perPage onSuccess:^(NSString *receivedString,NSString *responseCode) {
[self getResults:receivedString];
if(![responseCode isEqualToString:#"Success"]) {
[self hideProgressView];
appDelegate.isDiscover_RefreshTime=YES;
[[MyAPI sharedAPI] showAlert:responseCode];
} else {
NSString *strLogEvent=#"Discover_Highlights_Loaded Page_";
strLogEvent=[strLogEvent stringByAppendingFormat:#"%i",intPageNumber];
[FlurryAnalytics logEvent:strLogEvent timed:YES];
}
} onFail:^(ASIFormDataRequest *request) {
NSDictionary *parameters = [[MyAPI sharedAPI] prepareFailedRequestData:request file:#"Discover" method:_cmd];
[FlurryAnalytics logEvent:#"Unable_to_Connect_to_Server" withParameters:parameters timed:true];
[self hideProgressView];
appDelegate.isDiscover_RefreshTime=YES;
[[AfarAPI sharedAPI] showAlert:#"Unable to Connect to Server."];
[tblHighlightsGrid reloadData];
[tblListHighlights reloadData];
}];
These typedefs have been defined at the top of API Singleton:
typedef void (^ASIBasicBlockWrapper)(NSString *responseString,NSString *responseCode);
typedef void (^ASIBasicBlockWrapperFail)(ASIFormDataRequest *request);
MyAPISingleton#getAllHighlights...
- (void)getAllHighlights:(NSString *)pageNumber:(NSString *)perPage onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2{
NSString *access_token= [[NSUserDefaults standardUserDefaults] objectForKey:#"access_token"];
NSString *url = [baseURL stringByAppendingFormat:AFAR_GET_ALL_HIGHLIGHTS_ENDPOINT, pageNumber,perPage];
if (access_token) { url = [url stringByAppendingFormat:ACCESS_TOKEN, access_token]; }
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:url]];
[request setRequestMethod:#"GET"];
[request setDelegate:self];
[self executeAsynchronousRequest:request onSuccess:cb1 onFail:cb2];
}
And finally, MyAPI#executeAsynchronousRequest:
- (void) executeAsynchronousRequest:(ASIFormDataRequest *)request onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2
{
[request setCompletionBlock:^{
int statusCode = [request responseStatusCode];
NSString *statusMessage = [self statusErrorMessage:statusCode];
cb1([request responseString],statusMessage);
}];
[request setFailedBlock:^{
cb2(request);
}];
[request startAsynchronous];
}
Does anything stand out as to why 27 leaks are created?
I figured this out.
The ASIHttpRequest Documentation is very clear about the fact that you need to designate your request object with the __block storage mechanism:
Note the use of the __block qualifier when we declare the request, this is important! It tells the block not to retain the request, which is important in preventing a retain-cycle, since the request will always retain the block.
In getAllHighlights(), I'm doing that, but then I'm sending my request object as an argument to another method (executeAsyncRequest). The __block storage type can only be declared on local variables, so in the method signature, request is just typed to a normal ASIFormDataRequest, and so it seems as though it loses its __block status.
The trick is to cast (I'm not sure if that's technically accurate) the argument before using it in a block.
Here's my leak free implementation of executeAsyncRequest:
- (void) executeAsyncRequest:(ASIFormDataRequest *)request onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2
{
// this is the important part. now we just need to make sure
// to use blockSafeRequest _inside_ our blocks
__block ASIFormDataRequest *blockSafeRequest = request;
[request setCompletionBlock: ^{
int statusCode = [blockSafeRequest responseStatusCode];
NSString *statusMessage = [self statusErrorMessage:statusCode];
cb1([blockSafeRequest responseString],statusMessage);
}];
[request setFailedBlock: ^{
cb2(blockSafeRequest);
}];
[request startAsynchronous];
}

Converting from ASIHTTPRequest to AFNetworking

I am converting my app routines from ASIHTTP to AFNetworking due to the unfortunate discontinuation of work on that project ... and what I found out later to be the much better and smaller codebase of AFNetworking.
I am finding several issues. My code for ASIHTTPRequest is built as a method. This method takes a few parameters and posts the parameters to a url ... returning the resulting data. This data is always text, but in the interests of making a generic method, may sometimes be json, sometimes XML or sometimes HTML. Thus I built this method as a standalone generic URL downloader.
My issue is that when the routine is called I have to wait for a response. I know all the "synchronous is bad" arguments out there...and I don't do it a lot... but for some methods I want synchronous.
So, here is my question. My simplified ASIHTTP code is below, followed by the only way i could think of coding this in AFNetworking. The issue I have is that the AFNetworking sometimes does not for the response before returning from the method. The hint that #mattt gave of [operation waitUntilFinished] totally fails to hold the thread until the completion block is called... and my other method of [queue waitUntilAllOperationsAreFinished] does not necessarily always work either (and does NOT result in triggering the error portion of the [operation hasAcceptableStatusCode] clause). So, if anyone can help, WITHOUT The ever-present 'design it asynchronously', please do.
ASIHTTP version:
- (NSString *) queryChatSystem:(NSMutableDictionary *) theDict
{
NSString *response = [NSString stringWithString:#""];
NSString *theUrlString = [NSString stringWithFormat:#"%#%#",kDataDomain,kPathToChatScript];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:theUrlString]];
for (id key in theDict)
{
[request setPostValue:[theDict objectForKey:key] forKey:key];
}
[request setNumberOfTimesToRetryOnTimeout:3];
[request setAllowCompressedResponse:YES];
[request startSynchronous];
NSError *error = [request error];
if (! error)
{
response = [request responseString];
}
return response;
}
AFNetworking version
- (NSString *) af_queryChatSystem:(NSMutableDictionary *) theDict
{
NSMutableDictionary *theParams = [NSMutableDictionary dictionaryWithCapacity:1];
for (id key in theDict)
{
[theParams setObject:[theDict objectForKey:key] forKey:key];
}
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:kDataDomain]];
NSMutableURLRequest *theRequest = [httpClient requestWithMethod:#"POST" path:[NSString stringWithFormat:#"/%#",kPathToChatScript] parameters:theParams];
__block NSString *responseString = [NSString stringWithString:#""];
AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:theRequest] autorelease];
operation.completionBlock = ^ {
if ([operation hasAcceptableStatusCode]) {
responseString = [operation responseString];
NSLog(#"hasAcceptableStatusCode: %#",responseString);
}
else
{
NSLog(#"[Error]: (%# %#) %#", [operation.request HTTPMethod], [[operation.request URL] relativePath], operation.error);
}
};
NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
[queue addOperation:operation];
[queue waitUntilAllOperationsAreFinished];
[httpClient release];
return responseString;
}
Thanks very much for any ideas.
- (void)af_queryChatSystem:(NSMutableDictionary *) theDict block:(void (^)(NSString *string))block {
...
}
Now within the completionBlock do:
block(operation.responseString);
block will act as the delegate for the operation. remove
-waitUntilAllOperationsAreFinished
and
return responseString
You call this like:
[YourInstance af_queryChatSystem:Dict block:^(NSString *string) {
// use string here
}];
Hope it helps. You can refer to the iOS example AFNetworking has
I strongly recommend to use this opportunity to convert to Apple's own NSURLConnection, rather than adopt yet another third party API. In this way you can be sure it won't be discontinued. I have found that the additional work required to get it to work is minimal - but it turns out to be much more robust and less error prone.
My solution is manually to run the current thread runloop until the callback have been processed.
Here is my code.
- (void)testRequest
{
MyHTTPClient* api = [MyHTTPClient sharedInstance]; // subclass of AFHTTPClient
NSDictionary* parameters = [NSDictionary dictionary]; // add query parameters to this dict.
__block int status = 0;
AFJSONRequestOperation* request = [api getPath:#"path/to/test"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject) {
// success code
status = 1;
NSLog(#"succeeded");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// failure
status = 2;
NSLog(#"failed");
}];
[api enqueueHTTPRequestOperation:request];
[api.operationQueue waitUntilAllOperationsAreFinished];
while (status == 0)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate date]];
}
STAssertEquals(status, 1, #"success block was executed");
}

iOS Application Background Downloading

Hey! I need to know how I can have my iOS Application start a download in the background of the application (like, have the download run in the AppDelegate file) so changing ViewControllers will not interrupt or cancel the download. I also need to be able to get the progress of the download (0.00000 - 1.00000), to set a UIProgressView object to, which also means I need a - (void)progressDidChangeTo:(int)progress function.
Just use ASIHTTPRequest it is way easier than NSURLRequest and does exactly what you need.
It examples that shows how to download in background and how to report progress.
I wouldn't download anything in the AppDelegate directly. Instead I would create a separated class just for that purpose. Let's call it MyService I would then initialize that class in my app delegate.
The class can work as a singleton or can be passed to each view controller that requires it.
In MyService class I would add the ASINetworkQueue and few methods to handle the requests when they are ready. Here is the code from ASI examples that you can use:
- (IBAction)startBackgroundDownloading:(id)sender
{
if (!self.queue) {
self.queue = [[[ASINetworkQueue alloc] init] autorelease];
}
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setDidFinishSelector:#selector(requestDone:)];
[request setDidFailSelector:#selector(requestWentWrong:)];
[self.queue addOperation:request]; //queue is an NSOperationQueue
[self.queue go];
}
- (void)requestDone:(ASIHTTPRequest *)request
{
NSString *response = [request responseString];
//Do something useful with the content of that request.
}
- (void)requestWentWrong:(ASIHTTPRequest *)request
{
NSError *error = [request error];
}
If you need to set the progress bar. I would just expose the setDownloadProgressDelegate of ASINetworkQueue in my MyService class and set it in my ViewControllers like that:
[[MyService service] setDownloadProgressDelegate: self.myUIProgressView];
BTW. If you need to continue downloading even when your app exits you can set ShouldContinueWhenAppEntersBackground property of your request to YES.
you can use NSURLConnection to start an asynchronous request that won't cause your UI to be frozen. You can do it by doing something like:
NSURLRequest *urlRequest = [[NSURLRequest alloc] initWithURL:url];
connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
[urlRequest release];
in order to have your progress you can use the:
connection:didReceiveResponse:(NSURLResponse *)response;
delegate call to inspect the response.expectedContentLength and then use the
connection:didReceiveData:(NSData *)data
to track the amount of data that was downloaded and calculate a percentage.
Hope this helps,
Moszi

ASIHTTPRequest: Error out while trying to invoke my delegate method

I have a instance of ASINetworkQueue, and I added instances of ASIHTTPRequest to the queue; meanwhile I have delegate methods set for the Queue as well as each requests:
[submittingReportQueue setDelegate:self];
[submittingReportQueue setRequestDidFailSelector:#selector(submitReportQueueWentWrong:)];
[submittingReportQueue setQueueDidFinishSelector:#selector(submitReportQueueFinished:)];
and in a loop I added request to the Queue, add call [submittingReportQueue go] outside of the loop.
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
NSString *auth = [[AuthenticationManager sharedInstance] authenticationHeaderValue];
request addRequestHeader:#"Authorization" value:auth];
[request addRequestHeader:#"Content-Type" value:#"application/json"];
NSString *jsonString =[self jsonStringForExpenseReport:report];
[request appendPostData:[jsonString dataUsingEncoding:NSUTF8StringEncoding]];
[request setDelegate:self];
[request setUserInfo:[NSDictionary dictionaryWithObject:report forKey:#"Report"]];
[request setDidFailSelector:#selector(submitReportRequestWentWrong:)];
[request setDidReceiveDataSelector:#selector(submitReportRequestDone:)];
[requests addObject:request];
[submittingReportQueue addOperation:request];
Following are my delegate methods:
- (void)submitReportQueueWentWrong:(ASINetworkQueue *)queue
{
NSLog(#"Submit Report WentWRong");
//
- (void)submitReportQueueFinished:(ASINetworkQueue *)queue
{
NSLog(#"Submit Report QueueFinished");
}
//
- (void)submitReportRequestWentWrong:(ASIHTTPRequest *)request
{
NSLog(#"Submit Report Queue went wrong");
}
//
- (void)submitReportRequestDone:(ASIHTTPRequest *)request
{//do work here}
However ASIHTTPRequest.m throws exception in following block of code:
// Does the delegate want to handle the data manually?
if ([[self delegate] respondsToSelector:[self didReceiveDataSelector]]) {
NSMethodSignature *signature = [[[self delegate] class]
instanceMethodSignatureForSelector:[self didReceiveDataSelector]];
NSInvocation *invocation = [[NSInvocation invocationWithMethodSignature:signature]
retain];
[invocation setSelector:[self didReceiveDataSelector]];
[invocation setArgument:&self atIndex:2];
NSData *data = [NSData dataWithBytes:buffer length:bytesRead];
[invocation setArgument:&data atIndex:3];
[invocation retainArguments];
[self performSelectorOnMainThread:#selector(invocateDelegate:) withObject:invocation waitUntilDone:[NSThread isMainThread]];
[invocation setArgument:&data atIndex:3]; throws exception, Error
message is 'NSInvalidArgumentException', reason: '*** -[NSInvocation
setArgument:atIndex:]: index (3) out of bounds [-1, 2]'
What did I do wrong?
Thanks
`
The problem is here:
[request setDidReceiveDataSelector:#selector(submitReportRequestDone:)];
and here:
- (void)submitReportRequestDone:(ASIHTTPRequest *)request
The didReceiveDataSelector is probably not the one you wanted, as it's called each time a chunk of data is received - I suspect you want to be called when the request has finished, so you should instead set the requestDidFinish selector:
[request setRequestDidFinishSelector:#selector(submitReportRequestDone:)];
For your info, the error you're seeing essentially means "The selector you have given me does not have the right call signature" or more specifically "I'm trying to call a method that takes 2 parameters, and your method only takes 1". (If I remember correctly, the reason why it fails on index 3 is that the first parameter is a hidden parameter containing the object.)