开发者

Problem With NSURLConnection and Location

I am using the location manager to generate location data that I send in a URL to down load data. The first time I call the location manager, it correctly returns the current location and based on the current location I can fetch the data from the URL.

However, when I attempt to retrieve the current location a second time, I receive a EXC_BAD_EXCESS.

When I try debugging with NSZombieEnabled it shows me FirstViewController.recievedData as a zombie in the didReceiveResponse method. (see marked code below)

I dug further and I found that after releasing the initial connection an unknown connection is established and then it tries to access receivedData which is already released .

The header file info :`

#import <CoreLocation/CoreLocation.h>
define SECS_OLD_MAX 1
@interface FirstViewController : UIViewController<CLLocationManagerDelegate> {
    UIActivityIndicatorView *spinner;
    CLLocationManager *locationManager;
    CLLocation *startingPoint;
    UIButton *relocateMe;
    NSMutableData *receivedData;
    NSString *lat;
    NSString *lon;
}
@property (nonatomic, retain) IBOutlet UIActivityIndicatorView *spinner;
@property (nonatomic, retain) CLLocationManager *locationManager;
@property (nonatomic, retain) CLLocation *startingPoint;
@property (nonatomic, retain) IBOutlet UIButton *relocateMe;
@property (nonatomic, retain) NSMutableData *receivedData;
@property (nonatomic, retain) NSString *lat;
@property (nonatomic, retain) NSString *lon;

`

The .m file code :

//starting the manger :

   [spinner startAnimating];
**EDIT**************************ADDED IN THE AUTORELEASE POOL BY HIB********************************
    self.locationManager = [[[CLLocationManager alloc] init]autorelease];
    // Detecting the user device
    NSString *currentDevice =  [[UIDevice currentDevice] model];
    // if its iPhone then locate the current lattitude and longitude
    if([currentDevice isEqualToString:@"iPhone"] || [currentDevice isEqualToString:@"iPhone 3G"] || [currentDevice isEqualToString:@"iPhone 3G S"]){
        DLog(@"I have identified the device as an iPhone");
        if(locationManager.locationServicesEnabled == YES){
            DLog(@"ok now the location manager gets the property");
            locationManager.delegate = self;
            // This is the most important property to set for the manager. It ultimately determines how the manager will
            // attempt to acquire location and thus, the amount of power that will be consumed.
            locationManager.desiredAccuracy = kCLLocationAccuracyBest;
            // Once configured, the location manager must be "started".
            [locationManager startUpdatingLocation] ;
        }else {
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Oops!" 
                                                            message:@"Please enable location servies"
                                                           delegate:nil
                                                  cancelButtonTitle:@"OK" 
                                                  otherButtonTitles:nil];
            [alert show];
            [alert release];
        }
    }
    //if its iPod then fetch the city based restaurants
    else if([currentDevice isEqualToString:@"iPod touch"] || [currentDevice isEqualToString:@"iPod touch 2G"]){
    }
    else if([currentDevice isEqualToString:@"iPhone Simulator"]){
       //TechZen says: there appears to be some code missing here, not sure if its relevant
    }

//didupdatetolocation method

  - (void)locationManager:(CLLocationManager *)manager
        didUpdateToLocation:(CLLocation *)newLocation
               fromLocation:(CLLocation *)oldLocation {
        // store the location as the "best effort"
        DLog(@"Lat = %g Long = %g",newLocation.coordinate.latitude,newLocation.coordinate.longitude);
        NSDate *eventDate = newLocation.timestamp; 
        NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
        DLog(@"NSTIME INTERVAL = %i",howRecent);
        //Is the event recent and accurate enough ?
        if (abs(howRecent) < SECS_OLD_MAX) {
            self.lat = [NSString stringWithFormat:@"%g",newLocation.coordinate.latitude];
            self.lon = [NSString stringWithFormat:@"%g",newLocation.coordinate.longitude];
            [[NSUserDefaults standardUserDefaults] setObject:lat forKey:@"LATITUDE"];
            [[NSUserDefaults standardUserDefaults] setObject:lon forKey:@"LONGITUDE"];
        DLog(@"inside Lat = %g Long = %g",newLocation.coordinate.latitude,newLocation.coordinate.longitude);
        self.startingPoint = newLocation;
        [locationManager stopUpdatingLocation];
**EDIT********************************REMOVED BY HIB******************************
        self.locationManager = nil; 
        [locationManager release];  
**EDIT********************************REMOVED BY HIB******************************

**ADDED BY HIB********************************************
        locationManager.delegate = nil; 
**ADDED BY HIB********************************************
        @try {
            //passing the parameter for more condition
            self.lat = [NSString stringWithFormat:@"%g",startingPoint.coordinate.latitude];
            self.lon = [NSString stringWithFormat:@"%g", startingPoint.coordinate.longitude];
            NSString *string2 = [[NSString alloc] initWithFormat:@"%@/Service.asmx/someMethod?lat1=%g&lon1=%g&recordSize=0"
                                 ,[[NSUserDefaults standardUserDefaults] stringForKey:@"textEntry_key"],startingPoint.coordinate.latitude,startingPoint.coordinate.longitude];
            NSURL *url = [[NSURL alloc] initWithString:string2];
            [string2 release];
            NSMutableURLRequest* request2=[NSMutableURLRequest requestWithURL:url];
            [request2 setHTTPMethod:@"GET"]; 
            [request2 setTimeoutInterval:25.0];
            [[NSURLCache sharedURLCache] setMemoryCapacity:0];
            [[NSURLCache sharedURLCache] setDiskCapacity:0];
            NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:request2 delegate:self];
            if (theConnection) {
                receivedData = [[NSMutableData data]retain];
            } else {
                // inform the user that the download could not be made
                UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Sorry !"
                                                                message:@"The server is not avaialable \n Please try againa later" 
                                                               delegate:nil
                                                      cancelButtonTitle:@"OK"
                                                      otherButtonTitles:nil];
                [alert show];
                [spinner stopAnimating];
            }
            [url release];
        }
        @catch (NSException * e) {
        }
        @finally {
        }
    }
    }

//and the delegate methods

 #pragma mark -
    #pragma mark connection methods
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
    {
        // this method is called when the server has determined that it
        // has enough information to create the NSURLResponse
        // it can be called multiple times, for example in the case of a
        // redirect, so each time we reset the data.
        // receivedData is declared as a method instance elsewhere

    **************************************the zombie is here *********************************
        [receivedData setLength:0];
    *****************************************************************************************
    }
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
        // append the new data to the receivedData
        // receivedData is declared as a method instance elsewhere
        [receivedData appendData:data];
    }
    - (void)connection:(NSURLConnection *)connection
      didFailWithError:(NSError *)error
    {
        [spinner stopAnimating];
        // release the connection, and the data object
        [connection release];
        // receivedData is declared as a method instance elsewhere
        [receivedData release];
        // inform the user
        DLog(@"Connection failed! Error - %@ %@",
              [error localizedDescription],
              [[error userInfo] objectForKey:NSErrorFailingURLStringKey]);
        // alert the user in the inter face.
        UIAlertView* alert = [[UIAlertView alloc]initWithTitle:@"Sorry !"
                                                       message:@"The server is not available.\n Please try again later."
                                                      delegate:nil
                                             cancelButtonTitle:@"OK" 
                                             otherButtonTitles:nil];
        [alert show];
        [alert release];
    }

    - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    {
        // do something with the data
        // receivedData is declared as a method instance elsewhere
        DLog(开发者_如何学Go@"Succeeded! Received %d bytes of data",[receivedData length]);
        [spinner stopAnimating];
        // release the connection, and the data object
        if(receivedData == nil)
        {
            UIAlertView* alert = [[UIAlertView alloc]initWithTitle:@"Sorry !" 
                                                           message:@"The server is not available.\n Please try again later or select city." 
                                                          delegate:nil 
                                                 cancelButtonTitle:@"OK" 
                                                 otherButtonTitles:nil];
            [alert show];
            [alert release];
            [spinner stopAnimating];
        }
        else
        {
        //just parse and use the data 
        }
        [connection release];
        [receivedData release];
        }

Please help . I am stuck.


You have a systematic problem with not properly accessing your class properties. The properties will not be automatically retained and released unless you use the self.propertyName to force the call to the accessors. For example:

[locationManager stopUpdatingLocation]; <-- direct access 
self.locationManager = nil; <-- access through generated accessor
[locationManager release]; <-- direct access again with release bypassing the automatic memory management

You should have:

[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
//[locationManager release]; this line is now unneeded because the accessor handles it

You have the same problem with recievedData and startingPoint. In the vast majority of cases, if you use the synthesized accessors you only need to call release on retained properties in your dealloc. Use the accessors will clear up your zombie problem.

Without knowing where the EXC_BAD_ACCESS occurs I can't say definitively but since that error often occurs when messaging an non-existant object I can say that it is very likely that your bypassing of the property accessors and your releasing of them manually is probably causing the code to send to nilled property.

Fix the access and see if that resolves the problem.

Edit01:

TechZen the problem is removed 50 %. my application runs nicely in the debugging mode but when I pull out the cable and starts again it crashes . the problem is certainly with location manager . but I am not clear about retains and release of location manager . can you help me

I'll take a stab at it. For your memory management:

  1. Always access your self.locationManager using the self-dot-propertyName notation to make sure you utilize the retention/release mechanism of generated accessors.
  2. Never call release on any property except in the dealloc method. If you use the self-dot notation and have the property set to retain, all but the end of life release is handled automatically for you. This includes times when you nil the property or set it to another object.
  3. When in doubt, don't release. It's easier to fix a memory leak latter than it is to track down a bug caused by an object that disappears at random points in the code because its retain count is skewed. Trying to hard to prevent leaks when your learning the environment is a form of premature optimization that causes more trouble than it prevents.

I note that in your locationManager:didUpdateToLocation:fromLocation: method you don't actually query the locationManager passed to the method but instead query the class's self.locationManager property. This may or not be a problem but it is best to use the passed in manager to make sure you are in fact querying the manager instance that updated. I also don't think it necessary to destroy and recreate the location manager repeatedly. I think you can initialize it once and keep it around (check the docs on that.)

If cleaning up your property references and using the passed manager does not help, I suggest you post a new question with the cleaned up code. At that point you will legitimately have a new issue and besides we need to see the cleaned up code to spot the problem.

Edit02:

(Based on new code)

You don't need to autorelease your 'self.locationManager' property here:

self.locationManager = [[[CLLocationManager alloc] init]autorelease];

You only use autorelease when you create an object and in your class and then send it to another class. You never autorelease properties of class.

You need to stop trying to release your declared properties. You never release properties defined with retain except in the dealloc method. You are stepping on the properties generated accessors that maintain the retain count automatically.

Your are still not using the accessors consistently. This:

if(locationManager.locationServicesEnabled == YES){
    DLog(@"ok now the location manager gets the property");
    locationManager.delegate = self;
    // This is the most important property to set for the manager. It ultimately determines how the manager will
    // attempt to acquire location and thus, the amount of power that will be consumed.
    locationManager.desiredAccuracy = kCLLocationAccuracyBest;
    // Once configured, the location manager must be "started".
    [locationManager startUpdatingLocation] ;

should be:

if(self.locationManager.locationServicesEnabled == YES){
    DLog(@"ok now the location manager gets the property");
    self.locationManager.delegate = self;
    // This is the most important property to set for the manager. It ultimately determines how the manager will
    // attempt to acquire location and thus, the amount of power that will be consumed.
    self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
    // Once configured, the location manager must be "started".
    [self.locationManager startUpdatingLocation] ;

and this:

locationManager.delegate = nil;

should be:

self.locationManager.delegate = nil; //<-- why are you doing this anyway? 

You need to track down all references to all your declared properties and affix self. to each one (expect inside a properties custom accessor which you don't seem to have use -- which is good in this case.)

I strongly suspect your problem is your unnecessary fiddling with the retention of the self.locationManager property. You maybe causing the location manager to disappear at random.

You are still not using the passed manager in locationManager:didUpdateToLocation:fromLocation: I suggest you do so or at least test that the passed manager is the same object as your self.locationManager. simply replace self.locationManager with manager.


One think you are certainly doing wrong: you need to allocate receivedData before you start the NSURLConnection. It will fork in the background right when you alloc/init it, so receivedData needs to be ready before, not after.


You are releasing recievedData at the end of a connection but are not setting your pointer to nil - it will still be pointing at where recievedData used to be.

Instead of

[recievedData release];

try

self.recievedData = nil;

Hope that helps,

Sam


I could not find the source of your problem but you have a leak in

self.locationManager = [[CLLocationManager alloc] init];

you should use

self.locationManager = [[[CLLocationManager alloc] init] autorelease];

instead.

Edit: Download Charles Web Proxy, check what connections you are making, what responses you get, and maybe we'll have a better idea then.

Edit after Comments: The autogenerated accessor property defined to retain automatically retains the passed object, and releases it when you set the property to nil/or release. So it soes ITS job, but its YOUR job to keep track of memory management of the passed object. So, yes, the initial code above has a LEAK, and you should do your job and RELEASE/AUTORELEASE your ALLOCATED object, which in this case happens to be [[CLLocationManager alloc] init].

Edit : I don't know how this comment can get -1. It's simple memory management. The answers on this thread all agree this is a correct post: iPhone: Is this a leak or not


I am not sure what was the actual problem . but when I was comparing the apple LocateMe example I see locatiomManager.delegate = nil; It solves the problem completely .

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜