iOS: Passing objects into a ASIHTTPRequest Block? - iphone

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;

Related

Objective c: Do I have to retype all the code everytime?

I'm making a server based app. Each time I do a request to the database, I have to type all the server connection code. Is it possible to reuse this somehow? In php, you usually have a file call dbConnect.php (or something similar) that you can call each time you want to connect.
Example, I would like to replace this, which I use all the time:
- (void)doSomething
{
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL: url];
__weak ASIHTTPRequest *request_b = request;
[request setDelegate: self];
[request addRequestHeader:#"Content-Type" value:#"text/html; charset=utf-8;"];
[request setDefaultResponseEncoding:NSUTF8StringEncoding];
[request setTimeOutSeconds: 10.0f];
[request setCachePolicy: ASIDoNotWriteToCacheCachePolicy | ASIDoNotReadFromCacheCachePolicy];
//Set the variables here
[request startAsynchronous];
}
... with something like:
- (void)doSomething
{
LoadServerCode; //This loads all the server code as above
//Set variables
[request startAsynchronous];
}
Thanks in advance
EDIT:
To clarify a little bit. Say I have some methods I use a lot, like creating a UILabel, or a UIView in a special way... It would be nice not to have to subclass, and end up with a bunch of classes, but rather have one class called MyConstructionMethods or something... So if I want to create a label on some different places in the app, I can just type:
MyGreenLabel; //Done, the label is created and added to the view
... instead of:
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 10)];
label.backgroundColor = [UIColor greenColor];
[self.view addSubview: label];
Hopefully you are keeping all your connection classes isolated from the rest of your code, then why cant you just make a method that will create your request set your vars and return the request for you to start async...Even if you are not keep your connection stuff isolated you can still have a static method of some class have this method...
you could implement your custom ASIHTTPRequest class:
#interface YourRequest : ASIFormDataRequest
#end
#implementation YourRequest
- (id)initWithURL:(NSURL *)newURL {
self = [super initWithURL: newURL];
if (self) {
[self addRequestHeader:#"Content-Type" value:#"text/html; charset=utf-8;"];
[self setDefaultResponseEncoding:NSUTF8StringEncoding];
[self setTimeOutSeconds: 10.0f];
[self setCachePolicy: ASIDoNotWriteToCacheCachePolicy | ASIDoNotReadFromCacheCachePolicy];
}
}
return self;
}
#end
and create the object:
- (void)doSomething {
__block YourRequest *request = [YourRequest requestWithURL: url];
__weak ASIHTTPRequest *request_b = request;
[request setDelegate : self];
//Set variables
[request startAsynchronous];
}
Have you tried using a macro?
In your .h file:
#define LoadServerCode() \
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; \
__weak ASIHTTPRequest *request_b = request; \
[request setDelegate: self]; \
[request addRequestHeader:#"Content-Type" value:#"text/html; charset=utf-8;"]; \
[request setDefaultResponseEncoding:NSUTF8StringEncoding]; \
[request setTimeOutSeconds: 10.0f]; \
[request setCachePolicy: ASIDoNotWriteToCacheCachePolicy | ASIDoNotReadFromCacheCachePolicy];
Then, in your implementation:
-(void)doSomething {
LoadServerCode();
//Set variables
[request startAsynchronous];
}
If you need to reuse that setup code multiple times in the same implementation file, consider refactoring with Extract Method to create a utility method that returns a properly-configured request object.
If you need to do this kind of thing in numerous places, consider subclassing ASIFormDataRequest so you can more succinctly create request objects with the properties configured as you most commonly set them. Alternatively, you could create some kind of request factory class with static methods for generating request objects.
you can declare methods in your header file and those will be available when using your class. so you could declare doSomthing in your .h file then implement that method in your .m file and when you want to "doSomthing" just call [className doSomthing]
if you want to show more code i can probably give you a better example

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];
}

Logic Issue - NSObject class - Beginner

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.

Need to log storage type of local variable

In short, I need to know if I am able to log the storage type for a variable.
Specifically, I want to log whether a variable has the __block storage type modifier applied to it.
Ideally, I'm looking for something like:
NSLog(#"storage type: %#", [localVar storageType]);
In case you're wondering, I think I just figured out a memory leak I've been debugging for the past few days, and I want to test if my assumption is correct.
I'm using ASIHttpRequest with setCompletionBlock and setFailedBlock, but I'm passing my request object to a convenience method that does the actual setup of the blocks, like so:
- (void)getAllHighlights:success:(ASIBasicBlockWrapper)cb1 fail:(ASIBasicBlockWrapperFail)cb2{
// blah blah blah
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setRequestMethod:#"GET"];
[request setDelegate:self];
[self executeAsynchronousRequest:request onSuccess:cb1 onFail:cb2];
}
Then, executeAsynchronousRequest sets up the Blocks and starts the request:
- (void) executeAsynchronousRequest:(ASIFormDataRequest *)request onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2
{
[request setCompletionBlock:^{
int statusCode = [safeRequest responseStatusCode];
NSString *statusMessage = [self statusErrorMessage:statusCode];
cb1([safeRequest responseString],statusMessage);
}];
[request setFailedBlock:^{
cb2(safeRequest);
}];
[request startAsynchronous];
}
My hunch tells me that even though I set up my request object as __block ASIFormDataRequest *request, when it's used within executeAsynchronousRequest, it's lost the __block storage type since it has only been typed as (ASIFormDataRequest *)request.
Thanks!
you aren't modifying request in a block, so __block isn't going to do anything for you... if you were passing in request to a block, it wouldn't be copied, it would keep the locally scoped version when you passed it into the block.

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