Warnings ASIHttpRequest - objective-c

i am using ASIHTTPRequest in my iOS APP. i am doing like this :
.h
#interface MyClassr
ASIFormDataRequest *currentRequest;
}
NSURL *url = [NSURL URLWithString:requestUrl];
currentRequest = [ASIFormDataRequest requestWithURL:url];
currentRequest.requestMethod=#"GET";
currentRequest.delegate =self;
[currentRequest setCompletionBlock:^{
listesRestaurants = [XMLParser parseRestaurantResponse:[currentRequest responseData]];
NSLog(#"%#",[currentRequest responseString]);
if (apDelegate.modeGeoloc) {
[map removeAnnotations:map.annotations];
[self addAnnotation];
[self calculDistance];
}
and i have a warnign in the line: [currentRequest setCompletionBlock:^ // Block will be retained by an object strongly retained by the captured object
// Capturing 'self' strongly in this block is likely to lead to a retain cycle
How i can correct this warnind please ?

You need to create a weak reference to self:
__weak MyClassr* blockSelf = self;
and then use that reference in your block:
[blockSelf addAnnotation];
[blockSelf caculDistance];
etc.

Related

Strange "zombie" in forwardInvocation: + getArgument:atIndex methods

Here is part from my code:
- (void)viewDidLoad
{
[super viewDidLoad];
CGRect frame = [[UIScreen mainScreen] bounds];
_webView = [[UIWebView alloc] initWithFrame:frame];
[_webView setHidden:NO];
[self.view addSubview:_webView];
_vk = [[DPVkontakteCommunicator alloc] initWithWebView:_webView];
DPVkontakteUserAccount *user;
NSString *accessToken = [[NSUserDefaults standardUserDefaults]
objectForKey:#"accessToken"];
NSInteger userId = [[[NSUserDefaults standardUserDefaults]
objectForKey:#"userId"] integerValue];
user = [[DPVkontakteUserAccount alloc]
initUserAccountWithAccessToken:accessToken
userId:userId];
NSLog(#"%#", user);
[user setSuccessBlock:^(NSDictionary *dictionary)
{
NSLog(#"%#", dictionary);
}];
NSDictionary *options = #{#"uid":#"1"};
// [user usersGetWithCustomOptions:#{#"uid":#"1"}]; // Zombie
[user usersGetWithCustomOptions:options]; // Not zombie
// __block NSDictionary *options = #{};
//
// [_vk startOnCancelBlock:^{
// NSLog(#"Cancel");
// } onErrorBlock:^(NSError *error) {
// NSLog(#"Error: %#", error);
// } onSuccessBlock:^(DPVkontakteUserAccount *account) {
// NSLog(#"account:%#", account);
//
// [account setSuccessBlock:^(NSDictionary *dictionary)
// {
// NSLog(#"%#", dictionary);
// }];
//
// [account docsGetUploadServerWithCustomOptions:options];
// }];
}
and here is the part which processes the userGetWithCustomOptions: method:
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSString *methodName = NSStringFromSelector([anInvocation selector]);
NSDictionary *options;
[anInvocation getArgument:&options
atIndex:2];
NSArray *parts = [self parseMethodName:methodName];
NSString *vkURLMethodSignature = [NSString stringWithFormat:#"%#%#.%#",
kVKONTAKTE_API_URL,
parts[0],
parts[1]];
// appending params to URL
NSMutableString *fullRequestURL = [vkURLMethodSignature mutableCopy];
[fullRequestURL appendString:#"?"];
[options enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop)
{
[fullRequestURL appendFormat:#"%#=%#&", key, [obj encodeURL]];
}];
[fullRequestURL appendFormat:#"access_token=%#", _accessToken];
// performing HTTP GET request to vkURLMethodSignature URL
NSURL *url = [NSURL URLWithString:fullRequestURL];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation;
operation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:urlRequest
success:^(NSURLRequest *request,
NSHTTPURLResponse *response,
id JSON)
{
_successBlock(JSON);
}
failure:^(NSURLRequest *request,
NSHTTPURLResponse *response,
NSError *error,
id JSON)
{
_errorBlock(error);
}];
[operation start];
}
problem is that when I am using "options" variable - it works fine, but when using direct values - it fails, app crashes. Using Profile I have found that method call directs to deallocated object.
Why this happens?
There is no other code that can help.
ViewController.m code: https://gist.github.com/AndrewShmig/5398546
DPVkontakteUserAccount.m: https://gist.github.com/AndrewShmig/5398557
The problem is that the parameter of getArgument: is type void *. And you are passing &value, which is NSDictionary * __strong * (pointer to a strong reference) to it. The cast is valid because it is possible to assign any non-object pointer to and from void * without any warnings.
When you pass a "pointer to strong" to a function, that means the function should expect the pointer to a "strong reference", and when the function exits, it should preserve the fact that the pointer points to a "strong reference". What this means is that if the function changes the reference (pointed to by the pointer), it must first release the previous value and then retain the new value.
However, what does getArgument:atIndex: do with its void * argument? It is agnostic about the thing pointed to, and simply copies the value into the memory pointed to. Therefore, it does not do any of this retain and release stuff. Basically, it performs a plain-old pre-ARC non-retaining assignment into your value variable.
So why is it crashing? What is happening is that value is at first nil, and then inside the getArgument:atIndex:, it assigns the new value into it, but it does not retain it. However, ARC assumes that it has been retained, since value is a strong reference. So at the end of the scope, ARC releases it. This is an over-release, since it was never retained.
The solution is to not pass a "pointer to strong" into getArgument:, because that method does not know anything about "strong". Instead, pass a "pointer to unsafe_unretained" or "pointer to void" into it, and then convert it to a strong reference later:
NSDictionary * __unsafe_unretained temp;
[anInvocation getArgument:&temp atIndex:2];
NSDictionary *options = temp; // or you can just use temp directly if careful
or alternately:
void *temp;
[anInvocation getArgument:&temp atIndex:2];
NSDictionary *options = (__bridge NSDictionary *)temp;

Passing delegate through another object with ARC

I've got 2 classes, MPRequest and MPModel.
The MPModel class has a method to lookup something from the core data store, and if not found, creates an MPRequest to retrieve it via a standard HTTP request (The method in MPModel is static and not and instance method).
What I want is to be able to get a progress of the current HTTP request. I know how to do this, but I'm getting a little stuck on how to inform the view controller. I tried creating a protocol, defining a delegate property in the MPRequest class, altering the method in MPModel to accept this delegate, and in turn passing it to the MPRequest when it is created.
This is fine, however ARC is then releasing this delegate whilst the request is running and thus doesn't do what I want. I'm trying to avoid making my delegate object a strong reference in case it throws up any reference cycles but I don't know any other way of doing this.
To start the request, from my view controller I'm running
[MPModel findAllWithBlock:^(NSFetchedResultsController *controller, NSError *error) {
....
} sortedBy:#"name" ascending:YES delegate:self]
Inside the findAllWithBlock method, I have
MPRequest *objRequest = [MPRequest requestWithURL:url];
objRequest.delegate = delegate;
[objRequest setRequestMethod:#"GET"];
[MPUser signRequest:objRequest];
[objRequest submit:^(MPResponse *resp, NSError *err) {
...
}
And in the MPRequest class I have the following property defined :
#property (nonatomic, weak) NSObject<MPRequestDelegate> *delegate;
Any ideas or suggestions?
As requested, here is some more code on how things are being called :
In the view controller :
[MPPlace findAllWithBlock:^(NSFetchedResultsController *controller, NSError *error) {
_placesController = controller;
[_listView reloadData];
[self addAnnotationsToMap];
[_loadingView stopAnimating];
if (_placesController.fetchedObjects.count > 0) {
// We've got our places, but if they're local copies
// only, new ones may have been added so just update
// our copy
MPSyncEngine *engine = [[MPSyncEngine alloc] initWithClass:[MPPlace class]];
engine.delegate = self;
[engine isReadyToSync:YES];
[[MPSyncManager sharedSyncManager] registerSyncEngine:engine];
[[MPSyncManager sharedSyncManager] sync];
}
} sortedBy:#"name" ascending:YES delegate:self];
Here, self is never going to be released for obvious reasons, so I don't see how this is the problem.
Above, MPPlace is a subclass of MPModel, but the implementation of the findAllWithBlock:sortedBy:ascending:delegate: is entirely in MPModel
The method within MPModel looks like this
NSManagedObjectContext *context = [[MPCoreDataManager sharedInstance] managedObjectContext];
[context performBlockAndWait:^{
__block NSError *error;
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([self class])];
[request setSortDescriptors:#[[[NSSortDescriptor alloc] initWithKey:key ascending:asc]]];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:nil
cacheName:nil];
[controller performFetch:&error];
if (!controller.fetchedObjects || controller.fetchedObjects.count == 0) {
// Nothing found or an error, query the server instead
NSString *url = [NSString stringWithFormat:#"%#%#", kMP_BASE_API_URL, [self baseURL]];
MPRequest *objRequest = [MPRequest requestWithURL:url];
objRequest.delegate = delegate;
[objRequest setRequestMethod:#"GET"];
[MPUser signRequest:objRequest];
[objRequest submit:^(MPResponse *resp, NSError *err) {
if (err) {
block(nil, err);
} else {
NSArray *objects = [self createListWithResponse:resp];
objects = [MPModel saveAllLocally:objects forEntityName:NSStringFromClass([self class])];
[controller performFetch:&error];
block(controller, nil);
}
}];
} else {
// Great, we found something :)
block (controller, nil);
}
}];
The delegate is simply being passed on to the MPRequest object being created. My initial concern was that the MPRequest object being created was being released by ARC (which I guess it probably is) but it didn't fix anything when I changed it. I can't make it an iVar as the method is static.
The submit method of the request looks like this :
_completionBlock = block;
_responseData = [[NSMutableData alloc] init];
[self prepareRequest];
[self prepareRequestHeaders];
_connection = [[NSURLConnection alloc] initWithRequest:_urlRequest
delegate:self];
And when the app starts downloading data, it calls :
[_responseData appendData:data];
[_delegate requestDidReceive:(float)data.length ofTotal:_contentLength];
Where _contentLength is simply a long storing the expected size of the response.
Got it working. It was partly an issue with threading, where the core data thread was ending before my request, me looking at the output from a different request entirely, and the way ARC handles memory in blocks.
Thanks for the help guys

Determine whether a weak referenced object has been deallocated

I use the following code to download images:
- (void)downloadImageAtUrl:(id)url
andDelegate:(id<IPServerDelegate>)delegate_ {
NSURL *correctUrl = nil;
if ([url isKindOfClass:[NSString class]])
correctUrl = [NSURL URLWithString:[NSString stringWithFormat:#"%#%#", kHostURL, [url substringFromIndex:1]]];
else
correctUrl = url;
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:correctUrl];
[imageRequestsArray addObject:request];
[request setCompletionBlock:^{
if (request.responseStatusCode == 200) {
if (delegate_ && [delegate_ respondsToSelector:#selector(didDownloadImage:atUrl:)]) {
[delegate_ didDownloadImage:request.responseData atUrl:request.url];
}
}
else {
if (delegate_ && [delegate_ respondsToSelector:#selector(failedToDownloadImageWithUrl:)]) {
[delegate_ failedToDownloadImageWithUrl:request.url];
}
}
[imageRequestsArray removeObject:request];
}];
[request setFailedBlock:^{
if (delegate_ && [delegate_ respondsToSelector:#selector(failedToDownloadImageWithUrl:)]) {
[delegate_ failedToDownloadImageWithUrl:request.url];
}
[imageRequestsArray removeObject:request];
}];
[request startAsynchronous];
}
If the delegate_ object has been deallocated the app crashes. How do I determine that the object delegate_ has been deallocated without having to create a direct reference to it? I know about __weak pointers in iOS 5, but my app must be compatible with iOS 4.3.
Well, when your delegate_ is deallocated, I assume that it is release. When it is, after:
[object release];
Do this:
object=nil;
On your code then, just check if it exists:
if(delegate_){
}
Ok, so I found a solution. I'm storing all the ASIHTTPRequest objects in the requests array. When any of my UIViewControllers is about to disappear (viewWillDisappear), I'm enumeration through the array and send cancel message to each of the request objects. The only inconvenience here is that those requests have to be resumed each time view appears again, but at least it's not crashing.

NSString EXC_BAD_ACCESS objective C

I'm new in Objective C and I have some problem....
It's my code:
1)
testAppDelegate.h (not all):
#interface testAppDelegate : NSObject <NSApplicationDelegate> {
IBOutlet NSWindow *windowLogin;
IBOutlet NSWindow *windowContactList;
IBOutlet NSTextField *memStatus;
NSString *access_token, *expires_in, *user_id;
NSMutableArray *records;}
2) testAppDelegate.m (not all):
int posInStr(NSString *subString, NSString *genString){
NSRange match;
match = [genString rangeOfString:subString];
return match.location;
}
NSString* pars(NSString *str_,NSString *str,NSString *_str){
NSString *tmp;
int startPos = posInStr(str_,str) + [str_ length];
tmp = [str substringFromIndex:startPos];
int finishPos = posInStr(_str, tmp);
return [tmp substringToIndex:finishPos];
}
-(IBAction)but2Click: (id)sender{
NSString *tmp2 = access_token;
NSString *tmp = [NSString stringWithFormat:#"https://api.vkontakte.ru/method/messages.getDialogs?count=3&access_token=%#",tmp2];
NSURL *url = [NSURL URLWithString:tmp];
NSLog(#"%#",tmp);
NSLog(#"%#",url);
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setDidFinishSelector:#selector(showLoaded)];
[request startAsynchronous];
}
-(IBAction)but1Click:(id) sender{
NSURL *url = [NSURL URLWithString:#"http://api.vkontakte.ru/oauth/authorize?client_id=293&scope=friends,messages&redirect_uri=http://api.vkontakte.ru/blank.html&display=popup&response_type=token"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setDidFinishSelector:#selector(requestFinishedtest:)];
[request startAsynchronous];
}
- (void)requestFinishedtest:(ASIHTTPRequest *)request
{
[memStatus setStringValue:#"Loading..."];
NSString *tmp = [NSString stringWithFormat:#"%#",[request url]];
[tmp retain];
access_token = pars(#"access_token=", tmp, #"&");
NSLog(#"%#",access_token);
expires_in = pars(#"expires_in=", tmp ,#"&");
user_id = pars(#"user_id=", [NSString stringWithFormat:#"%#&",tmp], #"&");
[memStatus setStringValue:#"Logined"];
[windowLogin orderOut:nil];
[windowContactList makeKeyAndOrderFront:self];
[NSApp activateIgnoringOtherApps:YES];
}
My problem:
"EXC_BAD_ACCESS" in "but2Click"
You are assigning an autoreleased object here:
access_token = pars(#"access_token=", tmp, #"&");
access_token must be getting released before the but2click method is invoked by a button tap.
You need to retain it if you want to use it later.
It's going to be really hard to figure this out from code -- you are going to have to debug it.
I wrote this blog to help understand and debug EXC_BAD_ACCESS
Basically, you are dereferencing a pointer that is pointing to memory that isn't allocated to your process. The main reasons that this could happen are
You are using an object that has been deallocated
The heap is corrupt
The things you should do to debug this:
Do a Build and Analyze. The reports of leaks are bad, but not related to this issue -- you want to look for issues of too few retains
Turn on Zombies and run in the debugger. Now, none of your objects will be deallocated, but when they have a retain count 0, they will complain to the debugger if you use them.
There are other tips on the blog that are a little harder to explain

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. :-)