I must be doing something wrong, but the Automatic Reference Counting docs don't give me a hint on what it might be. What I'm doing is calling a method with a block callback from inside a delegate method. Accessing that same delegate from inside the block results in a bad access. The problem is the object I'm passing - loginController which is sending the message to its delegate - is clearly not released, when I don't access it inside the block I can call the method multiple times without an issue. Here's my code:
- (void)loginViewDidSubmit:(MyLoginViewController *)loginController
{
NSString *user = loginController.usernameLabel.text;
NSString *pass = loginController.passwordLabel.text;
__block MyLoginViewController *theController = loginController;
[self loginUser:user withPassword:pass callback:^(NSString *errorMessage) {
DLog(#"error: %#", errorMessage);
DLog(#"View Controller: %#", theController); // omit this: all good
theController = nil;
}];
}
NSZombieEnabled does not log anything and there is no usable stack trace from gdb. What am I doing wrong here? Thanks for any pointers!
Edit:
I figured the problem has a bigger scope - the callback above is called from an NSURLConnectionDelegate method (the block itself is a strong property for that delegate so ARC should call Block_copy()). Do I need to take special measurements in this scenario?
Flow (the loginController stays visible all the time):
loginController
[delegate loginViewDidSubmit:self];
View Delegate
(method shown above calls the loginUser: method, which does something like:)
httpDelegate.currentCallback = callback;
httpDelegate.currentConnection = // linebreak for readability
[[NSURLConnection alloc] initWithRequest:req
delegate:httpDelegate
startImmediately:YES];
NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)aConnection
didFailWithError:(NSError *)error
{
if (NULL != currentCallback) {
currentCallback([error localizedDescription]);
self.currentCallback = NULL;
}
}
And this is where I get the bad access, but ONLY if I access that loginController variable...
Set copy attribute to the property, or just call 'copy' method for the block.
- (void)loginUser:(NSString *)user withPassword:(NSString *)pass callback:(void (^callback)(NSString *))
{
callback = [callback copy];
The actual solution was that I had the block as a strong property, but it should have been a copy property! D'oh!
First "Solution":
I just found a way to prevent the bad access. As shown in my Edit above, the View Delegate forwards the block to the httpDelegate (an instance of another class), which in turn keeps a strong reference to the block. Assigning the block to a temporary variable and forwarding the temporary block variable solves the problem, for whatever reason. So:
This crashes on block execution, as described
httpDelegate.currentCallback = callback;
This works
MyCallbackType aCallback = callback;
httpDelegate.currentCallback = aCallback;
I'll accept this as the answer, if anybody has more insights I'm happy to revise my decision. :)
I figure what is happening there is that the loginController is dead right after calling its delegate. Therefore a crash occurs. Without more information I can think of possible scenarios only:
The block do not retains the loginController object (__block type modifier). If the block is executed asynchronously, the loginController might no longer be available if it was killed elsewere. Therefore, no matter what you want to do with it, you wont be able to access it inside the block and the app will crash. This could happen if the controller is killed after sending loginViewDidSubmit.
I think most likely this could be your situation: The loginController calls its delegate object. The delegate method ends up synchronously invoking the callback block that kills the controller. The controller is expected to be alive after invoking the delegate method. Killing it inside the delegate method, most likely will cause crashes to happen. To make sure this is the problem, simply nil the loginController in the delegate method and put an NSLog statement in the controller after calling the delegate, never mind the block, you will get a crash there.
Perhaps if you paste some code we could help more.
My best.
Related
I have a class that uses NSURLConnection to fire a POST request.
I have other classes use a delegate on this class that it uses to fire an event when a response has been received.
When I've parsed the response, I call the delegate like so:
- (void)connectionDidFinishLoading:(NSURLConnection*)conn { ...
if (delegate)
{
[delegate serverDataLayerResponse:entity];
} ... }
I'm getting "EXC_BAD_ACCESS(code=1, address-..." on the line inside the if block.
I've even tried #try and #catch around that part but it stills kills my app.
I'm suspecting that the delegate is still pointing to as object in memory that has been released? How can I guard from this?
Thanks for any help.
You've got a bad pointer. delegate is nonzero, so the test passes, but doesn't point to a valid object. You could put a breakpoint in the delegate's -dealloc to detect whether the object was deallocated. Also, try breaking where you assign the delegate and make sue you've got a valid object at that point.
Probably a noob question, but I cannot seem to get it right at the moment. I am working on an app where I have an Actionsheet for the confirmation of some basic things. However after the delegate is called for that Actionsheet my initial calling object is released (or not initiated).
In the delegate method I then want to call a method on that object but it just not do anything from that point.
The self.inviteSponsorsFromVC is not initiated anymore in this scenario and I want to call the saveSponsorWithEmail method from it. I cannot just reinitiate it, as the object had some objects in it, it has to use.
Everything works correctly if I just remove the actionsheet and call the saveSponsorWithEmail method directly without using a delegate.
This is my delegate method:
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
//Get the name of the current pressed button
NSString *buttonTitle = [actionSheet buttonTitleAtIndex:buttonIndex];
if ([buttonTitle isEqualToString:NSLocalizedString(#"Send invitation", nil)]) {
ContactFromAddressBook *contactFromAddressBook = [self.tableData objectAtIndex:selectedIndex.row];
[self.inviteSponsorsFromVC saveSponsorWithEmail:contactFromAddressBook.email andName:contactFromAddressBook.fullName];
}
if ([buttonTitle isEqualToString:NSLocalizedString(#"Cancel", nil)]) {
NSLog(#"Cancel pressed --> Cancel ActionSheet");
}
}
My guess is that at in the delegate method the content of self.inviteSponsorsFromVC is nil. In Objective-C, when you send a message to nil the message is simply ignored (unlike C++, for instance, where you get a crash when you call a method on a NULL object).
As an experiment you can try either one of these:
If you use ARC, make the property self.inviteSponsorsFromVC a strong reference
If you don't use ARC, say [self.inviteSponsorsFromVC retain] at some point before you display the action sheet
Either way, what you need to do is to make sure that the object in self.inviteSponsorsFromVC is not deallocated before you invoke a method in it.
EDIT after your comment
The property declaration is good, it's got the strong attribute on it. In your InviteSponsorsFrom class, try to add a dealloc method and set a breakpoint there to see if the object is deallocated, and where the call comes from.
- (void) dealloc
{
[super dealloc];
}
Also make sure that an instance of InviteSponsorsFrom is created in the first place. I assume you have an initializer somewhere in that class where you can set a breakpoint and/or add an NSLog statement to make sure that the instance is created.
I have a class named "ServerDataLayer" that holds a NSURLConnection, and a NSMutableData that its writing the received HTTP data in to. When the connection finishes, it simply fires a delegate that my caller passed itself as a reference, the method looks like this:
-(void) serverDataLayerResponse:(id)entity
{
if ([entity isMemberOfClass:[LoginResponse class]])
{
LoginResponse *response = (LoginResponse*)entity;
NSLog(#"Error Code: %d", response.errorCode);
NSLog(#"Error Message: %#", response.errorMessage);
NSLog(#"Registered: %c", response.registered);
NSLog(#"AuthToken: %#", response.authToken);
[AppData shared].authToken = response.authToken;
ServerDataLayer *request = [[[ServerDataLayer alloc] initWithServer:_serverUrl delegate:self] autorelease];
[request getPlayerDetails];
//[_server getPlayerDetails];
}
}
Here's my problem...the internal _receivedData and _connection variables are currently in use whilst this delegate method is in progress. I wanted to use my same ServerDataLayer instance to fire another request off "[_server getPlayerDetails]", but the _connection and _receivedData variables internally were getting overwritten and I was getting in to a mess about when to retain/release at the right time.
So my work around was just to instantiate the ServerDataLayer each time I wanted to talk to the server. Now...in the example above, I'm instantiating the request with an 'alloc', and setting an 'autorelease' as I lose scope of this 2nd request. Will this 2nd request stay in memory whilst it's NSURLConnection is busy internally performing the request?
I'm getting a bit lost at this point on how to manage the object references for this kind of process. Any help would be appreciated.
An NSURLConnection, if used via the delegate methods will attach itself as an input to a run loop. However it won't retain its delegate. So your ServerDataLayer would be deallocated (and hopefully remember to cancel the connection). You could use object associations to give your object the same lifecycle as the URL connection, if you were suitably careful about the potential retain loop.
If you use sendAsynchronousRequest:... then you'll probably be fine anyway; assuming you reference self or any instance variable in the completion block then you'll be retained by the block and live for at least as long as it does.
There's really no need to confuse yourself over retain/release any more. Even if ARC isn't an option, you can just declare the relevant instance variables as retain properties within a class extension and use self.property notation to set new values. Retains and releases will be handled for you.
The only caveat is that you should never use dot notation in either your init or dealloc as a special case of the rule that it isn't safe to call methods on a class that's only half instantiated or is half destroyed.
Just use multiple ServerDataLayer instances.
This blog offers a nice solution for handling multiple NSURLConnections: make a custom "CustomURLConnection" class that has an additional tag property.
http://blog.emmerinc.be/index.php/2009/03/02/custom-nsurlconnection-class-with-tag/
http://blog.emmerinc.be/index.php/2009/03/15/multiple-async-nsurlconnections-example/
Basically, he has simply added a tag property to the exsisting NSURLConnection:
CustomURLConnection.m
- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately tag:(NSString*)tag {
self = [super initWithRequest:request delegate:delegate startImmediately:startImmediately];
if (self) {
self.tag = tag;
}
return self;
}
then, later in the normal NSURLConnection loading methods, you can do:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//Log the connection’s tag
CustomURLConnection *ttttag = (CustomURLConnection *)connection; // **HERE**
NSLog(#”%#”, ttttag.tag);
NSMutableData *dataForConnection = [self dataForConnection:(CustomURLConnection*)connection];
[connection release];
}
So, that's where I'm having trouble. The way I see it, this is how things go:
I create a "connection+tag"
The first code snippet I posted above creates a regular "connection" (no tag), which will eventually call the the normal NSURLConnection methods like connectionDidFinishLoading. What happens to the tag at this point?
In the connectionDidFinishLoading method I'm able to cast the connection back into a "connection+tag", then find that missing tag information that had been discarded. How?
Maybe I'm just confusing myself, but it seems as if the tag was discarded when it starts down the normal NSURLConnection path. But then by casting it as the subclass, I'm again able to recover the tag property. Where did it live/go in the mean time?
Could someone with a better understanding of inheritance explain this to me?
With this code:
[[CustomURLConnection alloc] initWithRequest:... delegate:... startImmediately:... startImmediately tag:...];
you create an instance of CustomURLConnection. Now here is where your understanding is wrong: this CustomURLConnection object can freely call all methods of its superclasses but it will always remain a CustomURLConnection. The tag is always there.
The methods that are defined in the superclass such as initWithRequest:delegate:startImmediately: don't know about the tag but they don't have to, either. When the delegate method gets called:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
the connection argument is the very same CustomURLConnection that you created yourself above. The type in the method signature is different but that doesn't matter; because you know that this connection is of the CustomURLConnection type, you can just cast the connection object to the correct type and access the new property. But even if you wouldn't do that, the tag would still be there all the time.
I'm not sure what you mean by:
The first code snippet I posted above creates a regular "connection" (no tag).
What you've done here is create a subclass of NSURLConnection. Anywhere you can use the latter, you can use the former. NSURLConnection* means "a pointer to an NSURLConnection* or a subclass of it." So the original object you created was a CustomURLConnection and it included an extra ivar. That ivar doesn't disappear just because intermediary users refer to it by its superclass.
Stick with me. I'm visually impaired, have never used this site before, and will probably not post this in precisely the format that you are all used to. I apologize for any unintentional faux pas's herein.
Using Objective-C in an iOS project…
I have a singleton class, set up in what appears to be the usual way for Objective-C. It is, in the main, a series of methods which accept NSString values, interprets them, and return something else. In the code below, I'm simplifying things to the barest minimum, to emphasize the problem I am having.
From the singleton class:
- (NSUInteger) assignControlState:(NSString *)state {
// excerpted for clarity...
return UIControlStateNormal; // an example of what might be returned
}
Now, an instance of another class tries to use this method like so:
- (void) buttonSetup:(UIButton*)button {
[button setTitle:#"something" forState:[[SingletonClass accessToInstance] assignControlState:#"normal"]];
}
This code actually works. HOwever, when the system goes to draw the UI which includes the button whose title was set in this way, an EXC_BAD_ACCESS error occurs.
If the assignControlState method is moved into the same class as the buttonSetup method, no error is generated.
I'm guessing this is something about Apple's memory management that I'm not fully understanding, and how things go in and out of scope, but for the life of me, I can't figure out where I'm going wrong.
HOpe someone can help. Thanks.
The problem is in your accessToInstance method. I'll bet you are under-retaining. The implementation should be more like this:
static SingletonClass *sSingletonClass = nil;
#implementation
+ (id)accessToInstance {
if (sSingletonClass == nil) {
sSingletonClass = [[[self class] alloc] init];
}
return sSingletonClass;
}
#end
Now, if your program is following normal memory management rules, the singleton will stay around. You can check by writing:
- (void)dealloc {
[super dealloc]; // <-- set a breakpoint here.
}
If the debugger ever stops at this breakpoint, you know something in your program has over-released the singleton.
You know that bit you excerpted for clarity? I think you need to show us what it is because there's probably an over release in it somewhere.
Specifically, I think you release an autoreleased object. If you do that and don't use the object again, everything will carry on normally until the autorelease pool gets drained. The autorelease pool gets drained automatically at the end of the event at about the same time as the drawing normally occurs.
That would also explain the delayed crash following the NSLogs.