2009-12-10 5 views
2

Я начал заниматься рамкой ObjectiveFlickr с целью создания относительно простого приложения для iPhone, показывающего геотегическое содержимое Flickr в текущем регионе MKMapView. Я столкнулся с проблемой, связанной с резьбой before, и теперь у меня такое чувство, что я получаю что-то принципиально неправильное в своей архитектуре. В основном то, что у меня есть:Правильное место для вещей в Objective-C

  1. MainViewController, что создает и обрабатывает объект MKMapView и кнопка
  2. Нажатие кнопки вызывает метод, который вызывает Flickr API для геотегами фотографий в пределах текущей карты степени.
  3. Метод обратного вызова для этого вызова API выполняет итерации через результаты и помещает их в объект NSMutableArray объектов FlickrImage. FlickrImage - это простой класс данных, содержащий местоположение изображения flickr как CLLocation, NSURL, указывающий на миниатюру, и заголовок NSString.

Фрагмент кода для шага 2:

-(void)actionSearchForTripodPhotos { 
    if(currentBoundingBox == nil) { 
     // TODO add a messagebox saying we're waiting for location info - or just lock the app until we're sure. 
     return; 
    } 
    NSString *dateTakenMinimumUNIXTimeStampString = [NSString stringWithFormat:@"%f",[[NSDate dateWithTimeIntervalSinceNow:-100000] timeIntervalSince1970]]; 
    OFFlickrAPIRequest *flickrAPIRequest = [[OFFlickrAPIRequest alloc] initWithAPIContext:[CloudMadeMap101AppDelegate sharedDelegate].flickrAPIContext]; 
    [flickrAPIRequest setDelegate:self]; 
    NSString *flickrAPIMethodToCall = @"flickr.photos.search"; 
    NSString *bboxString = [NSString stringWithFormat:@"%f,%f,%f,%f",currentBoundingBox.bottomLeftLat ,currentBoundingBox.bottomLeftLon ,currentBoundingBox.topRightLat ,currentBoundingBox.topRightLon]; 
    NSLog(@"bounding box to be sent to flickr: %@",bboxString); 
    NSDictionary *requestArguments = [[NSDictionary alloc] initWithObjectsAndKeys:FLICKR_API_KEY,@"api_key",[NSString stringWithFormat:@"%f",currentLocation.coordinate.latitude],@"lat",[NSString stringWithFormat:@"%f",currentLocation.coordinate.longitude],@"lon",dateTakenMinimumUNIXTimeStampString,@"min_upload_date",nil]; 
    [flickrAPIRequest callAPIMethodWithGET:flickrAPIMethodToCall arguments:requestArguments]; 
} 

Фрагмент кода для шага 3:

- (void)flickrAPIRequest:(OFFlickrAPIRequest *)inRequest didCompleteWithResponse:(NSDictionary *)inResponseDictionary { 
NSDictionary *photosDictionary = [inResponseDictionary valueForKeyPath:@"photos.photo"]; 
NSDictionary *photoDictionary; 
FlickrImage *flickrImage; 
for (photoDictionary in photosDictionary) { 
    NSLog(@"photodictionary is %@",[photoDictionary description]); 
    flickrImage = [[FlickrImage alloc] init]; 
    flickrImage.thumbnailURL = [[appDelegate sharedDelegate].flickrAPIContext photoSourceURLFromDictionary:photoDictionary size:OFFlickrThumbnailSize]; 
    flickrImage.hasLocation = TRUE; // TODO this is actually to be determined... 
    flickrImage.ID = [NSString stringWithFormat:@"%@",[photoDictionary valueForKeyPath:@"id"]]; 
    flickrImage.owner = [photoDictionary valueForKeyPath:@"owner"]; 
    flickrImage.title = [photoDictionary valueForKeyPath:@"title"]; 
    [flickrImages addObject:flickrImage]; 
    [photoDictionary release];   
} 
} 

Это все идет хорошо. API досадно не возвращает геолокацию для каждой отдельной фотографии, поэтому для этого требуется другой вызов API. Я думал, что я мог бы сделать это внутри класса FlickrImage, но здесь он получает некрасиво:

  • MainViewController создает экземпляр FlickrImage каждой итерации и сохраняет его в NSMutableArray.
  • Экземпляр FlickrImage вызывает геолокацию Flickr API asunchronously и должен сохранять координату, возвращенную в соответствующей переменной-члене.

Я уверен, что это не происходит, потому что я получаю

malloc: *** error for object 0x451bc04: incorrect checksum for freed object - object was probably modified after being freed. 

посыпают вокруг моей отладочный вывод, и почти всегда EXC_BAD_ACCESS но не всегда в той же точке.

Я явно делаю что-то принципиально неправильное здесь, но что?

+0

У вас есть идеи, что такое объект 0x452bc04? Если вы регистрируете адреса своих объектов, особенно объекты FlickrImage, вы можете посмотреть адрес отладчика и увидеть, по крайней мере, какой класс не прошел. – TechZen

+0

Вы должны вставить код последних двух шагов (создание, сохранение и изменение FlickrImages). –

+0

@ kai1968 - добавил фрагменты кода – mvexel

ответ

2

При перебрать словарь нет необходимости вызывать [photoDictionary release]:

NSDictionary *photosDictionary = 
     [inResponseDictionary valueForKeyPath:@"photos.photo"]; 
NSDictionary *photoDictionary; 
FlickrImage *flickrImage; 
for (photoDictionary in photosDictionary) { 
    ... 
    [photoDictionary release];    

Я думаю, что это, где ваша проблема.

При звонке release, и объект достигает ref count 0, он освобождается.

Поскольку вы не должны были этого делать, позже, когда словарь будет выпущен, он отправит release каждому из своих элементов, но вы, возможно, уже освободили их.

Это базовое управление памятью в объективе-c. Посмотрите на управление памятью и retain/release/autorelease материал для более подробного объяснения.

+0

Спасибо stefanB, другие тоже указали это, но я ценю, что вы подробно остановились на этой теме. Я обязательно буду читать по управлению памятью еще немного, это определенно одно из слабых мест в моих навыках ObjC - отсутствие фона в C/C++, но только в сборках мусора, таких как Java и .NET. Я просто не имел обыкновение заботиться об этом. – mvexel

+0

Управление памятью на самом деле довольно простое, это только запутывает, когда вы пытаетесь спроектировать взаимодействие между компонентами, и вы теряете связь с тем, кому принадлежит. Objective-c фактически добавляет очень приятное управление подсчетами ссылок с помощью материалов keep/release/autorelease. Если вы изучите управление памятью в спецификациях Apple, вы найдете несколько простых правил, которые будут следовать, что объяснит, как использовать память дескриптора относительно использования Cocoa. – stefanB

6

Ошибка означает то, что она говорит: вы, вероятно, изменили память после ее освобождения, а EXC_BAD_ACCESS указывает, что вы пытаетесь получить доступ к элементу массива, который не существует. Я бы проверял, что ваши объекты FlickrImage не освобождаются от времени, когда вы пытаетесь вызвать метод геолокации.

Там нет ничего в дизайне, который выделяется как существенные недостатки

+3

+1. И используйте NSZombieEnabled, чтобы выяснить, какой объект является виновником. – diciu

+0

@ennuikiller: спасибо за заверение;) Это то, во что я верю *, действительно, и я хотел бы это проверить, но как я могу это сделать? – mvexel

+0

Я добавил некоторые фрагменты кода, это может прояснить ситуацию. Благодаря! – mvexel

2

@techzen - это где мне не хватает Xcode /GdB навыки. Как я могу их зарегистрировать? То, что действительно обеспечит полезную проницательность.

В случае классов, присущих NSObject, вы можете просто NSSC распечатать объект напрямую.

NSLog(@"myObject=%@", myObjectInstance); 

NSLog будет вызывать метод экземпляра debugDescription, которые, как правило, распечатать что-то вроде:

<MyObjectClass 0x451bc04> 

Некоторые классы производят намного больше информации, но иногда они не предоставляют экземпляры решения, так что вы должны напечатайте его напрямую:

NSLog(@"myObject's Address=%p",myObjectInstance); 

Спецификатор формата «% p» - это трюк. Вы, вероятно, хотите, чтобы плоть его немного как:

NSLog(@"<%@ %p>", [myObjectInstance class], myObjectInstance); 
// prints <MyObjectClass 0x451bc04> 

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

+0

Спасибо за эти ценные указатели. Я думаю, что этот материал не настолько хорошо освещен в документации разработчика Apple, или я не замечаю здесь ничего? – mvexel

1

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

NSDictionary *a=[NSDictionary dictionaryWithObjectsAndKeys:@"bob",@"bobKey",@"steve",@"steveKey",nil]; 
NSDictionary *b=[NSDictionary dictionaryWithObjectsAndKeys:@"bob1",@"bobKey",@"steve1",@"steveKey",nil]; 
NSDictionary *c=[NSDictionary dictionaryWithObjectsAndKeys:a,@"a",b,@"b",nil]; 
NSDictionary *d; 
for (d in c) { 
    NSLog(@"[d class]=%@,[d description]=%@",[d class],d); 
    NSLog(@"[c valueForKey:d]=%@",[c valueForKey:d]); 
} 

печатает:

[d class]=NSCFString,[d description]=a 
[c valueForKey:d]={ 
    bobKey = bob; 
    steveKey = steve; 
} 

[d class]=NSCFString,[d description]=b 
[c valueForKey:d]={ 
    bobKey = bob1; 
    steveKey = steve1; 
} 

Обратите внимание, что даже если d определяется как NSDictionary он по-прежнему назначается в качестве строкового значения ключей.

Вам нужно изменить:

NSDictionary *photoDictionary; 
FlickrImage *flickrImage; 
for (photoDictionary in photosDictionary) { 

к чему-то вроде:

NSString *dictKey; 
NSDictionary *photoDictionary; 
for (dictKey in photosDictionary) { 
    photoDictionary=[photosDictionary valueForKey:dictKey]; 
    ... 

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

+0

Хм, интересный момент. В моем текущем коде, однако, NSLog (@ "photodictionary является% @", [photoDictionary description]); выводит, по-видимому, действительное описание словарного объекта, например: photodictionary is { farm = 3; id = 4173994142; isfamily = 0; isfriend = 0; ispublic = 1; владелец = "37727710 @ N08"; secret = 75f2885f80; server = 2521; title = "Dit is een kunstwerk"; } – mvexel

+0

У вас есть ключи от вашего набора photosdictionary для объектов фотодокумента? Это легко сделать, если вы измените свои ключи и значения. Вы должны записать возвращаемое значение '[photosDictionary valueForKey: photodictionary]' и посмотреть, что вы получаете. – TechZen

Смежные вопросы