简体   繁体   中英

Delegates and object retention objective-c (iOS)

I have an issue with retention of objects in an array. I am building a framework to interface with Amazon Route 53 and following as closely as I can the rest of the AWS IOS SDK. I am making a call to a webservice that returns XML. The response from the webservice is marshalled into a class by a NSXMLParser delegate. The delegate switches to another delegate to handle a nested portion of the XML. The second delegate class unmarshalls the nested portion into a second class. The objects of this second class are placed in an array in the first class. When the second delegate is released it is releasing the objects (not the array they are placed in, just the objects themselves. When the response comes back all of the properties are populated, but the objects in the array point to released objects (and of course winds up making a call to a deallocated object)

There is a lot of code involved in this, so I'm hoping that there's something obvious about assigning objects to an array from a delegate. I'm pretty sure I'm maintaining strong references throughout the process, and I thought if the objects are added to the array they couldn't/wouldn't be released from it until the array is released.

If you are interested in looking at the code, it is available here:

https://bitbucket.org/entr04y/aws-sdk-ios

The application is in the samples subdirectory (Route53_demo) and the initial call is in ListHostedZonesViewController.m (viewWillAppear). That calls the Route53 framework in the src directory - AmazonRoute53Client.m, which calls Route53ListHostedZonesRequestMarshaller.m as it's delegate to unmarshall the response. The second delegate is Route53HostedZoneUnmarshaller.m.

Hopefully someone can point out what I'm missing... I've been staring at it for the last four days and I can't figure out what I've done that is different than some of the sample code from AWS that follows this same pattern.

As requested below, I'll try to add the relevant parts of the code, since it doesn't seem to be something obvious with how this is being done:

The inital request is made from a view controller like this:

Route53ListHostedZonesRequest *request = [[Route53ListHostedZonesRequest alloc] initWithMaxItems:100];

Route53ListHostedZonesResponse *listHostedZonesResponse = [[AmazonClientManager r53] listHostedZones:request];

That is then handled in AmazonRoute53Client.m (which is in my framework):

-(Route53ListHostedZonesResponse *)listHostedZones:(Route53ListHostedZonesRequest *)listHostedZonesRequest
{

AmazonServiceRequest *request = [Route53ListHostedZonesRequestMarshaller createRequest:listHostedZonesRequest];

return (Route53ListHostedZonesResponse *)[self invoke:request rawRequest:listHostedZonesRequest unmarshallerDelegate:[Route53ListHostedZonesResponseUnmarshaller class]];
}

The response is sent to Route53ListHostedZonesResponseUnmarshaller.m and parsed into an instance of Route53ListHostedZonesResponse class:

 -(Route53ListHostedZonesResponse *)response
 {
 if (nil == response) {
    response = [[Route53ListHostedZonesResponse alloc] init];
}
return response;
}

When the parser hits the nested parts that contain the hosted zones it is delegated out to another unmarshaller Route53HostedZoneUnmarshaller:

if ([elementName isEqualToString:@"HostedZone"]) {
    [parser setDelegate:[[[Route53HostedZoneUnmarshaller alloc] initWithCaller:self withParentObject:self.response.hostedZones withSetter:@selector(addObject:)] autorelease]];
}

(hostedZones is declared as an NSMutableArray and is a nonatomic, retained property of the Route53HostedZonesResponse class) which places the items from this piece of the xml into an instance of Route53HostedZone for each of the hosted zones contained in the XML:

-(Route53HostedZone *)response
{
if (nil == response) {
    response = [[Route53HostedZone alloc] init];
}
return response;
}

At the end of the xml block the object is returned to the caller like so:

if ([elementName isEqualToString:@"HostedZone"])  {
    if ( caller != nil) {
        [parser setDelegate:caller];
    }
   if (parentObject != nil && [parentObject respondsToSelector:parentSetter]) {
        [parentObject performSelector:parentSetter withObject:self.response];
    } 
    return;

}

At the end of the xml response, the Route53ListHostedZonesResponse object is populated like so:

if ([elementName isEqualToString:@"ListHostedZonesResponse"])
{
    if (caller != nil) {
        [parser setDelegate:caller];
    }

    if (parentObject != nil && [parentObject respondsToSelector:parentSetter]) {
        [parentObject performSelector:parentSetter withObject:self.response];
    }

    return;
}

At this point, the AWS runtime library and the Route53 library are done handling the response and both of the unmarshaller objects, the Route53ListHostedZonesResponse object and all of the Route53HostedZone objects are released.

Back at the view controller, when I try to access objects in listHostedZoneResponse, all of the properties are available, except the objects in the hostedZones array which are now zombies. I overrode dealloc in Route53HostedZoneUnmarshaller.m like so:

-(void)dealloc
{
AMZLogDebug(@"deallocating zone object unmarshaller");
//   [response release];
//    [super dealloc];
}

Which prevents the unmarshaller from being released and by extension the hostedZones objects, at which point the code works (but of course leaks like a sieve) How can I get the Route53HostedZone objects into the hostedZones array so they are retained?

Wow, this is some convoluted code. I'm just guessing that the problem might be in this line:

[parser setDelegate:
 [[[Route53HostedZoneUnmarshaller alloc]
                               initWithCaller:self 
                             withParentObject:self.response.hostedZones 
                                   withSetter:@selector(addObject:)] 
  autorelease]
 ];

I don't see what parser 's type is, but in Objective-C in general delegates are not retained. So the whole Route53HostedZoneUnmarshaller may be garbage by the time it is used.

Try removing the call to autorelease and store the delegate object in a property until the parsing process is finished.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM