2013-09-24 1 views
3

Получил одноэлементный класс, так называемый RequestManager, который обрабатывает запросы, сделанные разными модулями и фоновые задачи моего приложения.Передача NSURLSession: Как реализовать собственный класс SessionDelegate соответственно?

@interface RequestFactory : NSObject 

- (void)requestDataWith:(NSString *)token 
        id:(NSString *)id 
       sender:(id<RequestFactoryDelegate>)sender; 
... 

@end 

Затем я получил еще один класс, так называемый SessionDelegate, который будет обрабатывать все обратные вызовы во время запроса.

@interface SessionDelegate : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate> 

@property (weak, nonatomic) id <RequestFactoryDelegate> delegate; 

@end 

Моя идея заключается в том, чтобы инкапсулировать функции в этих классах, чтобы не перегружать свои классы, потому что мне нужно много вспомогательных классов с CommonCrypto и так далее.

Поэтому я быстро установил код RequestFactoryDelegate для отправки полученных данных отправителю, который инициировал запрос на отправку.

- (void)requestDataWith:(NSString *)token 
        id:(NSString *)id 
       sender:(id<RequestFactoryDelegate>)sender 
{ 
    self.sessionDelegate.delegate = sender; 

    NSMutableURLRequest *request = //create the request here 

    NSURLSessionDataTask *dataTask = [self.defaultSession dataTaskWithRequest:request]; 
    [dataTask resume]; 
} 

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

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

- (void)foo 
{ 
    [requestFactory requestDataWith:token 
           id:id 
          sender:senderA]; // let's assume this takes 20s 

    [requestFactory requestDataWith:token 
           id:id 
          sender:senderB]; // let's assume this takes 1s 
} 

Поскольку запрос senderA все еще продолжается, senderB устанавливает делегата к нему, и что происходит, функция Делегат senderB запускается дважды.

<senderB> 
<senderB> 

Ну ... мне действительно нужно реализовать собственный делегат (или не в том же классе, что и RequestFactory или нет), но, как я обрабатывать методы обратного вызова, так что я могу правильно ответить на любой senderA или отправитель?

Моя последняя идея состоит в том, чтобы переопределить класс NSURLSessionTasks и реализовать собственное свойство делегирования или заблокировать свойство или что-то еще.

Большое спасибо заранее.

+0

Я думаю, что стоит уточнить, что единственный делегат объекта NSURLSession ДОЛЖЕН обрабатывать ВСЕ задачи. Кроме того, 'NSURLSessionTask' не может быть подклассом, а также не имеет такого свойства пользовательского контекста. ИМХО, это недостаток дизайна, но, увы, я не Apple. Ваш дизайн должен учитывать это. – CouchDeveloper

+0

TBH Мне очень нравится этот шаблон дизайна NSURLSession, так как я знаю, как правильно его обрабатывать. Идея, что все задачи используют один объект сеанса, действительно крут, преимущества вы можете прочитать, проверяя видео WWDC2013. – Kuno

+1

Тогда почему вы спросили? Ну, в любом случае - проблема НЕ в том, что есть сеанс и одна или несколько задач сеанса. На самом деле это хорошее разделение. Я имею в виду именно то, где у вас были проблемы. Вы можете решить это несколькими способами. Использование словаря, как и вы, является жизнеспособным решением, но я считаю его «взломом» из-за ограничений, предоставляемых API. Тем не менее, я делаю то же самое;) – CouchDeveloper

ответ

5

В Objective-C существует альтернатива подклассу, которая может быть вам нужна: associating objects.

Он работает следующим образом: вы можете «присоединить» (связать) объект к другому объекту с помощью настраиваемого ключа, а затем получить его. Поэтому в вашем случае вы бы сделали что-то вроде:

#include <objc/runtime.h> 

// Top level of your .m file. The type and content of this 
// variable don't matter much, we need the _address_ of it. 
// See the first link of this answer for details. 
static char kDelegateKey = 'd'; 

- (void)requestDataWith:(NSString *)token 
        id:(NSString *)id 
       sender:(id<RequestFactoryDelegate>)sender 
{ 
    NSMutableURLRequest *request = //create the request here 

    NSURLSessionDataTask *dataTask = [self.defaultSession dataTaskWithRequest:request]; 

    // Associate the sender with the dataTask. We use "assign" here 
    // to avoid retain cycles as per the delegate pattern in Obj-C. 
    objc_setAssociatedObject(dataTask, &kDelegateKey, sender, OBJC_ASSOCIATION_ASSIGN); 

    [dataTask resume]; 
} 

- (void)someOtherMethodWithDataTask:(NSURLSessionDataTask *)dataTask 
{ 
    // Read the attached delegate. 
    id<RequestFactoryDelegate> delegate = objc_getAssociatedObject(dataTask, &kDelegateKey); 

    // Do something with the delegate. 
} 
+0

Большое спасибо, правильный ответ от мюнхенского существа;) Кстати, есть ли еще один удобный способ обработки обратного вызова от делегата? – Kuno

+0

Ну, вместо использования интерфейса вы также можете использовать блок. Objective-C позволяет обрабатывать блоки как объекты, поэтому вы можете: objc_setAssociatedObject (foo, & kKey, aBlock, OBJC_ASSOCIATION_COPY); MyBlockType block = objc_getAssocatedObject (foo, &kKey); block (bar); ' – DarkDust

+0

Ну, ваш метод не работает - я только что реализовал его, но возвращаемое значение функции getAssociatedObject равно нулю. Кроме того, я имел в виду, есть ли другой шаблон один из них должен использовать реализацию NSURLSession? Например, я не нашел ничего в документации класса относительно пользовательского делегирования ... – Kuno

0

Вот мое решение.

Я просто использую уникальный идентификатор каждого объекта sessionTask. Таким образом, мой объект-делегат содержит словарь с блоками в качестве значений для выполнения при успешном/неудачном действии и идентификатор в качестве ключей для определения правильного блока выполнения.

В файле .h я объявил словарь и способ добавления объекта ключ/значение:

@property (nonatomic, strong) NSDictionary *completionHandlerDictionary; 

- (void)addCompletionHandler:(CompletionHandlerType)handler 
        forTask:(NSString *)identifier; 

И в файле .m я называю блок обработчика.

- (void)addCompletionHandler:(CompletionHandlerType)handler 
        forTask:(NSString*)identifier 
{ 
    if ([self.completionHandlerDictionary objectForKey:identifier]) { 
     NSLog(@"Error: Got multiple handlers for a single task identifier. This should not happen.\n"); 
    } 

    [self.completionHandlerDictionary setObject:handler forKey:identifier]; 
} 

- (void)callCompletionHandlerForTask:(NSString *)identifier 
{ 
    CompletionHandlerType handler = [self.completionHandlerDictionary objectForKey:identifier]; 

    if (handler) { 
     [self.completionHandlerDictionary removeObjectForKey: identifier]; 
     NSLog(@"Calling completion handler.\n"); 

     handler(); 
    } 
} 

Все, просто, как есть.

10

Вы можете прикрепить произвольный объект к задаче:

NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url]; 
id whatever = // anything at all! 
[NSURLProtocol setProperty:whatever forKey:@"thing" inRequest:req]; 
NSURLSessionDownloadTask* task = [[self session] dataTaskWithRequest:req]; 

и получить его позже (в делегат), как это:

NSURLRequest* req = task.originalRequest; 
id thing = [NSURLProtocol propertyForKey:@"thing" inRequest:req]; 

Значение здесь (whatever) может быть любой объект , Это может быть обратный вызов обработчика завершения - я сделал это, и он работает отлично.

+0

можно также использовать свойство 'task.taskDescription' –

+1

@JohnSmith Не, если это не NSString. В моем ответе я приводил пример, связанный с обработчиком завершения (блок) - вы не можете делать _that_ с помощью простого 'taskDescription'. Пункт моего ответа состоит в том, что это _general_. – matt

+0

Да, конечно, я указал на это для будущих читателей как гораздо более простой пример, чтобы получить свойство, прикрепленное к задаче. Спасибо за отличный ответ, раньше не знал об этом. –

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