2015-11-07 4 views
3

Certains de mes utilisateurs obtiennent ce plantage (selon eux, cela arrive après 4-5 minutes d'utilisation de l'application) mais je ne peux pas le reproduire moi-même:iOS application "a des assertions actives au-delà du temps autorisé - accidents occasionnels"

Application Specific Information: 
<BKNewProcess: 0x175466c0; com.zsquare.iPadApp; pid: 5005; hostpid: -1> has active assertions beyond permitted time: 
{(
    <BKProcessAssertion: 0x17545c90> id: 48-3A424578-FF1D-4484-9026-B4C6A83AD7EF name: Background Content Fetching (191) process: <BKNewProcess: 0x175466c0; .com.zsquare.ijournalPad; pid: 5005; hostpid: -1> permittedBackgroundDuration: 30.000000 reason: backgroundContentFetching owner pid:48 preventSuspend preventThrottleDownUI preventIdleSleep preventSuspendOnSleep 
)} 

Elapsed total CPU time (seconds): 0.460 (user 0.460, system 0.000), 2% CPU 
Elapsed application CPU time (seconds): 0.013, 0% CPU 

Filtered syslog: None found 

Thread 0 name: Dispatch queue: com.apple.main-thread 
Thread 0: 
0 libsystem_kernel.dylib   0x362a4ff0 mach_msg_trap + 20 
1 libsystem_kernel.dylib   0x362a4df4 mach_msg + 38 
2 CoreFoundation     0x23fa58c4 __CFRunLoopServiceMachPort + 134 
3 CoreFoundation     0x23fa3c4c __CFRunLoopRun + 1034 
4 CoreFoundation     0x23ef7118 CFRunLoopRunSpecific + 518 
5 CoreFoundation     0x23ef6f04 CFRunLoopRunInMode + 106 
6 GraphicsServices    0x2d081ac8 GSEventRunModal + 158 
7 UIKit       0x28139f14 UIApplicationMain + 142 
8 SimpleList-iPad     0x0000f116 main (main.m:17) 
9 libdyld.dylib     0x361e9872 start + 0 

maintenant, j'ai regardé d'autres SO questions qui traitent de cet accident, mais aucune des réponses il m'a été utile, donc je pensais que je poste ici avec ma propre configuration et le code. Tout d'abord, la fonctionnalité où cela se produit est liée à une tâche répétée qui devrait s'exécuter dans l'application. Pour cela, j'ai une répétition de minuterie dans l'application lorsque l'application commence d'abord:

- (void) setupRepeatTimer { 

self.repeatTimer = [NSTimer scheduledTimerWithTimeInterval: 60.0 target:self selector:@selector(checkForAutomaticTaskTime:) userInfo:nil repeats: YES]; 
self.repeatTimer.tolerance = 1.0; 

}

- (void) checkForAutomaticTaskTime: (NSTimer *) timer { 

    if (self.taskIdentifier) { 
    [[UIApplication sharedApplication] endBackgroundTask: self.taskIdentifier]; 
    } 

    self.taskIdentifier = UIBackgroundTaskInvalid; 
    [self beginBackgroundUpdateTask]; 


    // simplified, but there are more conditional checks here 
    if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateActive && setting_enabled_for_automatic_task) { 
     [self startAutomaticBackup]; 
    } else { 
     [self endBackgroundUpdateTask]; 
    } 
} 

La sauvegarde qui doit être exécuté peut être de taille variable, en fonction des données de l'utilisateur. Voici le code général pour cela:

- (void) startAutomaticBackup { 

    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 

     [self runBackupWithCompletionBlock:^(NSString * filename) { 

      dispatch_async(dispatch_get_main_queue(), ^{ 
       // finish on the main thread 
       [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; 

       [self endBackgroundUpdateTask]; 

      }); 
     }]; 
    }); 
} 

Juste pour être sûr, le beginBackgroundUpdateTask et endBackgroundUpdateTask ressemblent exactement comme ceux des exemples de code dans SO et guide d'Apple:

- (void) beginBackgroundUpdateTask 
{ 
    self.taskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"CJAutoBackup" expirationHandler:^{ 
     NSLog(@"beginBackgroundTask about to end = %lu", (unsigned long)self.taskIdentifier); 
     [self endBackgroundUpdateTask]; 
    }]; 
} 

- (void) endBackgroundUpdateTask 
{ 
    if (self.taskIdentifier) { 
     [[UIApplication sharedApplication] endBackgroundTask: self.taskIdentifier]; 
     self.taskIdentifier = UIBackgroundTaskInvalid; 
    } 
} 

====

Donc, c'est la configuration que j'ai. D'après les descriptions des utilisateurs, il semble que l'application se bloque au premier plan avec ce rapport d'erreur, après que l'application a fonctionné pendant 4-5 minutes. Si elles désactivent le paramètre de sauvegarde automatique, l'application fonctionne correctement.

La principale confusion ici est que l'application se bloque lorsque l'application l'utilise au premier plan, pas l'arrière-plan, mais le message d'erreur parle d'assertions de tâches en arrière-plan. Comment est-ce possible?

En outre, que pourrais-je faire pour éviter ce problème? J'ai vu la propriété backgroundTimeRemaining de UIApplication mentionnée, mais je ne suis pas sûr comment ou où l'utiliser dans mon exemple.

Est-ce que l'utilisation de NSTimer peut entraîner des complications? Sur l'appareil, il ne semble pas déclencher le minuteur si l'application est déjà en arrière-plan.

Appréciez l'aide.

Répondre

3

C'est un problème de threading. Le problème est en effet, comme vous le suspectez, l'affacturage et l'architecture de vos appels de tâche en arrière-plan début/fin. Vous tentez de conserver un identificateur de tâche d'arrière-plan en tant que propriété , que vous modifiez ensuite de manière répétitive toutes les 60 secondes, quelle que soit la durée de la tâche (sur un fil d'arrière-plan). Cette architecture de pseudo-boucle déroutante entraîne une désynchronisation de vos tâches d'arrière-plan avec leurs identifiants. Ainsi, l'heure d'une tâche d'arrière-plan expire sans que vous ayez appelé endBackgroundTask avec l'identificateur approprié.

Vous devez réviser cette architecture de sorte que vous ayez un identifiant de tâche d'arrière-plan par tâche, ce qui permet de tout séparer. Je pense que vous trouverez le moyen le plus simple d'exprimer chaque sauvegarde automatique comme une NSOperation séparée.

+0

Merci.Le temporisateur devrait appeler 'endBackgroundTask' sur tout identifiant existant et le marquer comme invalide dès le début de la temporisation, avant qu'il ne commence une autre tâche, ce serait donc toujours un problème? En y regardant à nouveau, il me semble que le problème pourrait être que le processus de «sauvegarde» pourrait prendre plus de 60 secondes, donc au moment où il est fait et que le bloc d'achèvement est appelé, il appelle endBackgroundTask sur un identifiant expiré. Cela générerait-il le même problème et la même trace de pile, ou pensez-vous que cela pose toujours un problème avec certains identifiants qui n'obtiennent pas de 'endBackgroundTask'? –

+0

Je pense que nous disons la même chose. Ecoute, je n'arrive pas ŕ comprendre ce que ton code fait, et je ne pense pas que tu puisses non plus. Mais nous savons que vous avez un seul identifiant et pourtant vous avez la possibilité de multiples tâches de sauvegarde simultanées. C'est fou, même si ce_never_ arrive (et il semble que dans votre test, il n'a pas). D'où ma suggestion: chaque sauvegarde est son propre NSOperation avec sa propre tâche d'arrière-plan et son propre identifiant de tâche d'arrière-plan. Plus de "boucle"! Votre code sera beaucoup plus simple. – matt

+0

Je suis d'accord avec le point général, et je pense que je vois le problème. Le correctif consisterait à supprimer le taskIdentifier de la fonction NSTimer et à l'appeler uniquement pour envelopper la fonctionnalité de sauvegarde réelle (ajoutez également un booléen pour empêcher l'appel du code de sauvegarde pendant l'exécution de la sauvegarde). De cette façon, il n'y a qu'une seule tâche de fond à la fois. –