Как вернуть объект из класса, который использует NSURLConnection и его делегатские классы? - PullRequest
9 голосов
/ 05 января 2011

Я пытаюсь переместить код из класса UITableViewController в класс "помощника".

Код использует NSURLConnection для получения и анализа JSON, а затем заполняет NSMutableArray.

То, что я хотел бы сделать, это вызвать метод в моем классе помощника, который возвращает NSMutableArray.Чего я не понимаю, так это как вернуть массив из класса делегата connectionDidFinishLoading NSURLConnection (где массив фактически построен), как если бы он был из первоначально вызванного метода, который установил соединение.Другими словами, как метод, который вызывает NSURLConnection, возвращает управление, чтобы он мог возвращать значение всей операции?

Вот соответствующие методы из вспомогательного класса.Как получить метод getMovies для возврата списка listOfMovies, встроенного в класс делегата connectionDidFinishLoading?

-(NSMutableArray)getMovies:(NSURL*)url {

responseData = [[NSMutableData data] retain];       

NSURLRequest *request = [NSURLRequest requestWithURL:url];
//NSURLRequest* request = [NSURLRequest requestWithURL: url cachePolicy: NSURLRequestUseProtocolCachePolicy timeoutInterval: 30.0];

connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];

}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {

[responseData setLength:0];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
//TODO error handling for connection
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {      

//---initialize the array--- 

listOfMovies = [[NSMutableArray alloc] init];

tmdbMovies = [[NSArray alloc] init];
posters = [[NSArray alloc] init];
thumbs = [[NSDictionary alloc] init];

NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];

SBJsonParser *json = [[SBJsonParser new] autorelease];

tmdbMovies = [json objectWithString:responseString];

// loop through all the top level elements in JSON
for (id movie in tmdbMovies) {

    // 0 - Name
    // 1 - Meta
    // 2 - Url


    if ((NSNull *)[movie objectForKey:@"name"] != [NSNull null]) {

        if (![[movie objectForKey:@"name"] isEqualToString:@""]) {
            name = [movie objectForKey:@"name"];
        }

    } 

    if ((NSNull *)[movie objectForKey:@"info"] != [NSNull null]) {

        if (![[movie objectForKey:@"info"] isEqualToString:@""]) {
            meta = [movie objectForKey:@"info"];
        }

    } 

    if ((NSNull *)[movie objectForKey:@"thumb"] != [NSNull null]) {

        if (![[movie objectForKey:@"thumb"] isEqualToString:@""]) {
            thumbUrl = [movie objectForKey:@"thumb"];
        }

    } 

    NSLog(@"Name: %@", name);
    NSLog(@"Info: %@", meta);
    NSLog(@"Thumb: %@", thumbUrl);

    NSMutableArray *movieData = [[NSMutableArray alloc] initWithObjects:name,meta,thumbUrl,nil];

    // add movieData array to listOfJMovies array
    [listOfMovies addObject:movieData];


    [movieData release];
}


//FIXME: Connection warning
if (connection!=nil) { 
    [connection release]; 
}

[responseData release]; 
[responseString release];



}

Ответы [ 4 ]

13 голосов
/ 05 января 2011

Что вам действительно нужно сделать, это создать @protocol, который создает делегат для вашего вспомогательного класса. Затем измените -(NSMutableArray)getMovies:(NSURL*)url на -(void)getMovies:(NSURL*)url

Класс, вызывающий ваш вспомогательный метод, должен реализовать делегат вашего вспомогательного метода.

Затем - (void)connectionDidFinishLoading:(NSURLConnection *)connection вызывает метод (ы) делегата. Лучше иметь один для успеха и один для неудачи.

= Начало обновления =

Вам также необходимо определить id delegate в вашем файле помощника, который вызывающий класс устанавливает для себя после init, но перед вызовом -(void)getMovies:(NSURL*)url. Таким образом, вспомогательный файл знает, куда нужно перезвонить.

getMovies *movieListCall = [[getMovies alloc] init];
movieListCall.delegate = self;
[movieListCall getMovies:<your NSURL goes here>];

Вы увидите несколько дополнительных строк для включения delegate в файлы getMovies.h и getMovies.m.

= конец обновления =

в вашем файле getMovies.h добавьте:

@protocol getMoviesDelegate

@required
- (void)getMoviesSucceeded:(NSMutableArray *)movieArray;
- (void)getMoviesFailed:(NSString *)failedMessage;

@end

@interface getMovies : NSOBject {
id delegate;
}

@property (nonatomic, assign) id delegate;

в вашем файле getMovies.m добавьте:

@synthesize delegate;

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    //TODO error handling for connection
    if ([delegate respondsToSelector:@selector(getMoviesFailed:)]) {
        [delegate getMoviesFailed:[error localizedDescription]];
    }
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {      

//finishes with
    if ([delegate respondsToSelector:@selector(getMoviesSucceeded:)]) {
        [delegate getMoviesSucceeded:listOfMovies];
    }
}

обновить .h файл класса вызовов для использования getMoviesDelegate:

@interface MoviesView : UIViewController <getMoviesDelegate>{
.
.
.
}

добавить методы getMoviesDelegate в файл .m вашего вызывающего класса

- (void)getMoviesSucceeded:(NSMutableArray *)movieArray {
//deal with movieArray here
}

- (void)getMoviesFailed:(NSString *)failedMessage {
//deal with failure here
}

Это не проверено, но, надеюсь, дает вам дорожную карту для работы.

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

7 голосов
/ 05 января 2011

По сути, происходит то, что вы запускаете асинхронную сетевую нагрузку (асинхронный - почти верный способ сделать это), а затем вам нужен какой-то способ возобновить любую операцию, которую вы выполняли до начала загрузки. У вас есть несколько вариантов:

  • Создайте свой собственный протокол делегата . Ваш UITableViewController затем установит себя в качестве делегата помощника, а помощник вызовет helperDidLoad или любой другой метод, который вы назвали. Больше информации о написании делегатов в Руководстве по программированию Какао.

  • Использовать блоки и стиль прохождения продолжения . Это немного сложнее, но мне это нравится. В вашем UITableViewController вы напишите что-то вроде этого:

     [helper doSomething:^ (id loaded) { 
         [modelObject refresh:loaded]; // or whatever you need to do
     }];
    

    И тогда в вашем помощнике вы напишите:

     - (void)doSomething:(void ^ (id))continuation {
         _continuation = continuation;
         //kick off network load
     }
    
    
     - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
         _continuation(_data);
     }
    
  • Использовать уведомления . Прочитайте документы NSNotificationCenter.
  • Используйте КВО . Руководство по программированию KVO содержит много полезной информации о наблюдении значения ключа.
0 голосов
/ 05 января 2011

Вот шаги, псевдокод, как стиль:

  1. [helperInstance setDelegate:self]; // where self is your UITableViewController class

  2. в вашем классе помощников, в ConnectionDidFinishLoading сделать что-то вроде этого:

    [delegate finishedLoadingData:JSONData];

Также вы можете определить протокол для вашего делегата и объявить делегата следующим образом в вашем классе помощника:

@property (nonatomic, assign) id<YourProtocol> delegate;

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

0 голосов
/ 05 января 2011

Как мне получить метод getMovies, чтобы он возвращал listOfMovies, встроенный в класс делегата connectionDidFinishLoading?

Я собираюсь доказать, что вы не должны этого делать.

Сетевые запросы должны выполняться асинхронно. Если бы ваш getMovies должен был сделать синхронный запрос и вернуться только тогда, когда у него были данные, вы заблокировали бы весь этот поток, пока ожидали завершения сетевого подключения. Это плохая идея в целом и ужасная идея, если ваш основной поток вызывает getMovies. Блокировка основного потока не позволит вам реагировать на прикосновения или обновлять пользовательский интерфейс, ваше приложение будет выглядеть замороженным, а ОС прекратит его, если ваши пользователи сначала не разочаруются.

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

...