Проблема с 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 %. мое приложение работает нормально в режиме отладки, но когда я вытаскиваю кабель и запускаюсь снова, оно падает. проблема, конечно, с менеджером местоположения. но мне не ясно о сохранении и освобождении менеджера местоположения. вы не могли бы мне помочь
Я сделаю удар. Для управления вашей памятью:
- Всегда доступ к вашему
self.locationManager
используя нотацию self-dot-propertyName, чтобы убедиться, что вы используете механизм сохранения / освобождения сгенерированных средств доступа. - Никогда не вызывайте релиз на любую собственность, кроме как в
dealloc
метод. Если вы используете нотацию с собственной точкой и для свойства установлено сохранение, все выпуски, кроме выпуска с окончанием срока службы, обрабатываются автоматически для вас. Это включает в себя случаи, когда вы обнуляете свойство или устанавливаете его для другого объекта. - Если сомневаешься, не отпускай. В последнем случае легче устранить утечку памяти, чем отследить ошибку, вызванную объектом, который исчезает в случайных точках в коде, потому что его счетчик хранения искажен. Попытка сделать все возможное, чтобы предотвратить утечки, когда вы изучаете среду, является формой преждевременной оптимизации, которая вызывает больше проблем, чем предотвращает.
Я отмечаю, что в вашем 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;
Это решает проблему полностью.