2017-05-05 3 views
0

Dans une petite application iOS, j'ai besoin de récupérer un fichier JSON d'un serveur (similaire à une requête DNS) et d'utiliser cette réponse pour initier d'autres connexions. Pour ce faire, j'ai un objet qui s'occupe de la requête JSON, en utilisant NSURLSession. Cet objet reçoit l'objet principal (MainController) en tant que délégué et doit appeler une méthode lorsque les données JSON sont disponibles. Voici l'appel dans le MainController:NSURLSession et les délégués

- (void)serverResolve:(NSString *)serverID withPass:(NSString *)pass { 
    // Must retrieve the server list from JSON 
    ServerListRetriever *slCom = [[ServerListRetriever alloc] init]; 
    slCom.delegate = self; 
    [slCom searchServer:serverID usingPass:pass]; 
} 

Mon problème est que - dès le completionHandler - entre en action, le MainController ne fera pas ce qu'il devrait. Et je n'ai aucune idée pourquoi ???

Ceci est le contenu de ServerListRetriever (en-tête):

#import "ServerListRetrieverDelegate.h" 

@interface ServerListRetriever : NSObject 
@property (assign, nonatomic) id <ServerListRetrieverDelegate> delegate; 

- (void)searchServer:(NSString *)serverID usingPass:(NSString *)pass; 
@end 

... (et mise en œuvre)

#import "ServerListRetriever.h" 
#define SERVER_LIST @"https://www.example.com/hosts.json" 
@implementation ServerListRetriever 

- (void)searchServer:(NSString *)serverID usingPass:(NSString *)pass 
{ 
    // This will work... 
    [self.delegate serverConnect:@"https://www.example.net/" withPass:pass]; 
    return; 

    // And if removing the above two lines, this won't work 
    NSURLSession *session = [NSURLSession sharedSession]; 
    [[session dataTaskWithURL:[NSURL URLWithString:SERVER_LIST] 
      completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 
       // Handle response 
       if (error) { 
        [self.delegate serverListFailedWithError:error]; 
       } else { 
        // NSLog(@"Retrieved JSON: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); 

        // Extract the server URL 
        NSError *jsonError = nil; 
        NSDictionary *parsedObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; 

        if (jsonError) { 
         [self.delegate serverListFailedWithError:jsonError]; 
         return; 
        } 

        NSString *serverURL = [parsedObject objectForKey:serverID]; 
        NSLog(@"Resolved server URL: %@", serverURL); 

        // Continue with connection 
        [self.delegate serverConnect:serverURL withPass:pass]; 
       } 
      }] resume]; 
} 

@end 

La méthode serverConnect sera lancé dans les deux cas, mais lorsqu'il est appelé par le completionHandler , beaucoup de pense ne fonctionnera pas correctement alors. J'ai vérifié le délégué par débogage, et il a le même ID dans les deux cas.

Comme je suis toujours aux prises avec la gestion de la mémoire sur iOS, perdre un objet au début pourrait être une explication. Pourtant, je manque d'expérience pour localiser le problème. Merci pour vos idées et conseils!

+0

En attendant, j'ai aussi essayé de se débarrasser de la classe supplémentaire 'ServerListRetriever' et d'écrire son code directement dans le MainViewController. Cela signifie qu'aucun délégué n'est utilisé du tout. Pas de changement. – BurninLeo

Répondre

0

Un débat est en cours pour savoir si les délégués devraient être forts ou non. Si elles sont faibles, alors vous garantissez que vous n'obtiendrez pas de boucles de retenue, mais cela signifie également que si le délégué n'est pas conservé par quelqu'un d'autre, il peut disparaître avant la fin des opérations asynchrones.

La manière dont ceci est habituellement traité consiste à faire une copie interne forte du délégué, par ex.

@interface ServerListRetriever() 
@property (strong, nonatomic) id <ServerListRetrieverDelegate> strongDelegate; 
@end 

... 

// When an operation is started: 
self.strongDelegate = self.delegate; 

// When the *last* operation ends (keep an array of operations or something): 
self.strongDelegate = nil; 

Vous pouvez également marquer la propriété délégué public forte, mais vous devrez explicitement soit nul sur le délégué du ServerListRetriever lorsque vous avez terminé avec ou nul la dernière référence à l'objet ServerListRetriever lui-même.

+0

Comme le projet n'est pas (encore) en mode ARC, je ne peux pas utiliser 'weak' ou' strong' (pour autant que je sache, au moins 'weak' génère une erreur que cette propriété ne peut pas être synthétisée). – BurninLeo

+0

Pour les non-ARC, s/strong/retain /. – dgatwood

+0

J'avais 'retain' au lieu de' assign', mais cela ne changeait rien :( – BurninLeo

0

En fait, je ne comprends pas encore complètement la solution ... mais l'astuce consiste à gérer la réponse en dehors du thread principal. Voir Running NSURLSession completion handler on main thread

/** 
* Resolve the server URL by the server ID, and then connect. 
*/ 
- (void)serverResolve:(NSString *)serverID withPass:(NSString *)pass { 
    NSURLSession *session = [NSURLSession sharedSession]; 
    NSURLSessionDataTask *download = [session dataTaskWithURL:[NSURL URLWithString:SERVER_LIST] 
      completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 
       // Handle response 
       if (error) { 
        [self serverListFailedWithError:error]; 
       } else { 
        // Extract the server URL 
        NSError *jsonError = nil; 
        NSDictionary *parsedObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; 

        if (jsonError) { 
         [self serverListFailedWithError:jsonError]; 
         return; 
        } 

        // Working out of the main thread did the trick 
        dispatch_async(dispatch_get_main_queue(), ^{ 
         NSString *serverURL = [parsedObject objectForKey:serverID]; 
         [self serverConnect:serverURL withPass:pass]; 
        }); 
       } 
      }]; 

    [download resume]; 
}