Проблема с NSURLConnection и местоположением

Я использую диспетчер местоположений для генерации данных о местоположении, которые я отправляю в URL для загрузки данных. Когда я в первый раз вызываю диспетчер местоположений, он корректно возвращает текущее местоположение, и на основе текущего местоположения я могу получить данные из URL.

Однако, когда я пытаюсь получить текущее местоположение во второй раз, я получаю EXC_BAD_EXCESS.

Когда я пытаюсь отладить с NSZombieEnabled это показывает мне FirstViewController.recievedData как зомби в didReceiveResponse метод. (см. отмеченный код ниже)

Я копал дальше, и я обнаружил, что после освобождения начального соединения устанавливается неизвестное соединение, а затем пытается получить доступ receivedData который уже выпущен.

Информация заголовочного файла: `

#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;

`

Код файла.m:

// запуск менеджера:

   [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

  - (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 {
        }
    }
    }

// и методы делегата

 #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(@"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];
        }

Пожалуйста помоги. Я застрял.

5 ответов

Решение

У вас есть систематическая проблема с неправильным доступом к свойствам вашего класса. Свойства не будут автоматически сохраняться и освобождаться, если вы не используете self.propertyName заставить вызов к методам доступа. Например:

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

У тебя должно быть:

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

У вас та же проблема с recievedData а также startingPoint, В подавляющем большинстве случаев, если вы используете синтезированные средства доступа, вам нужно вызывать release только для сохраненных свойств в вашем dealloc. Использование аксессуаров прояснит вашу проблему с зомби.

Не зная где EXC_BAD_ACCESS Происходит, я не могу сказать однозначно, но поскольку эта ошибка часто возникает при отправке сообщений о несуществующем объекте, я могу сказать, что весьма вероятно, что ваш обход средств доступа к свойству и их освобождение вручную, вероятно, приводит к отправке кода на nilled имущество.

Исправьте доступ и посмотрите, решит ли это проблему.

Edit01:

TechZen проблема устранена на 50 %. мое приложение работает нормально в режиме отладки, но когда я вытаскиваю кабель и запускаюсь снова, оно падает. проблема, конечно, с менеджером местоположения. но мне не ясно о сохранении и освобождении менеджера местоположения. вы не могли бы мне помочь

Я сделаю удар. Для управления вашей памятью:

  1. Всегда доступ к вашему self.locationManager используя нотацию self-dot-propertyName, чтобы убедиться, что вы используете механизм сохранения / освобождения сгенерированных средств доступа.
  2. Никогда не вызывайте релиз на любую собственность, кроме как в dealloc метод. Если вы используете нотацию с собственной точкой и для свойства установлено сохранение, все выпуски, кроме выпуска с окончанием срока службы, обрабатываются автоматически для вас. Это включает в себя случаи, когда вы обнуляете свойство или устанавливаете его для другого объекта.
  3. Если сомневаешься, не отпускай. В последнем случае легче устранить утечку памяти, чем отследить ошибку, вызванную объектом, который исчезает в случайных точках в коде, потому что его счетчик хранения искажен. Попытка сделать все возможное, чтобы предотвратить утечки, когда вы изучаете среду, является формой преждевременной оптимизации, которая вызывает больше проблем, чем предотвращает.

Я отмечаю, что в вашем locationManager:didUpdateToLocation:fromLocation: метод, который вы на самом деле не запрашиваете locationManager, переданный методу, а вместо этого запрашивает класс self.locationManager имущество. Это может быть или не быть проблемой, но лучше использовать переданный менеджер, чтобы убедиться, что вы действительно запрашиваете обновленный экземпляр менеджера. Я также не считаю необходимым повторно уничтожать и воссоздавать менеджер местоположения. Я думаю, что вы можете инициализировать его один раз и сохранить его (проверьте документы по этому вопросу).

Если очистка ссылок на вашу собственность и использование пропущенного менеджера не помогли, я предлагаю вам опубликовать новый вопрос с вычищенным кодом. В этот момент у вас законно появится новая проблема, и, кроме того, нам нужно увидеть очищенный код, чтобы определить проблему.

Edit02:

(На основе нового кода)

Вам не нужно автоматически высвобождать свойство 'self.locationManager' здесь:

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

Вы используете autorelease только тогда, когда создаете объект в своем классе и затем отправляете его другому классу. Вы никогда не выпускаете автоматически свойства класса.

Вы должны прекратить попытки освободить ваши объявленные свойства. Вы никогда не освобождаете свойства, определенные с помощью retain, за исключением метода dealloc. Вы наступаете на свойства, созданные аксессорами, которые автоматически сохраняют счет.

Вы все еще не используете методы доступа последовательно. Это:

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

должно быть:

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

и это:

locationManager.delegate = nil;

должно быть:

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

Вы должны отследить все ссылки на все ваши объявленные свойства и аффикс self. каждому (ожидайте внутри свойства свой собственный аксессор, который, кажется, вам не нужен - что хорошо в этом случае.)

Я сильно подозреваю, что ваша проблема заключается в том, что вы теряете свойство self.locationManager. Возможно, вы заставляете менеджера местоположения исчезать наугад.

Вы все еще не используете пропущенного менеджера в locationManager:didUpdateToLocation:fromLocation: Я предлагаю вам сделать это или хотя бы проверить, что переданный менеджер - это тот же объект, что и ваш self.locationManager, просто заменить self.locationManager с manager,

Вы освобождаете recceiveData в конце соединения, но не устанавливаете указатель на ноль - он все равно будет указывать на то место, где раньше находился recceiveData.

Вместо

[recievedData release];

пытаться

self.recievedData = nil;

Надеюсь, это поможет,

Сэм

Один думает, что вы, безусловно, делаете неправильно: вам нужно выделить receivedData прежде чем начать NSURLConnection, Он будет разветвляться в фоновом режиме, когда вы выделите / инициализируете его, поэтому receivedData должен быть готов до, а не после.

Я не смог найти источник вашей проблемы, но у вас есть утечка в

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

ты должен использовать

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

вместо.

Изменить: Скачать Charles Web Proxy, проверить, какие соединения вы делаете, какие ответы вы получаете, и, возможно, тогда у нас будет лучшая идея.

Изменить после комментариев: автоматически сгенерированное свойство средства доступа, определенное для сохранения, автоматически сохраняет переданный объект и освобождает его, когда для свойства установлено значение nil/ или release. Таким образом, он выполняет свою работу, но ваша задача - отслеживать управление памятью переданного объекта. Итак, да, исходный код, приведенный выше, имеет УТЕЧКУ, и вы должны выполнить свою работу и ВЫПУСТИТЬ / АВТОРИЗАЛИ свой объект ALLOCATED, который в этом случае оказывается [[CLLocationManager alloc] init].

Редактировать: я не знаю, как этот комментарий может получить -1. Это простое управление памятью. Ответы в этой теме все согласны с тем, что это правильный пост: iPhone: это утечка или нет

Я не уверен, что было актуальной проблемой. но когда я сравнивал пример Apple LocateMe, я вижу locatiomManager.delegate = nil; Это решает проблему полностью.

Другие вопросы по тегам