Tinkamas naudoti „StartBackgroundTaskWithExpirationHandler“ naudojimas

Aš esu šiek tiek supainiotas apie tai, kaip ir kada naudoti „ beginBackgroundTaskWithExpirationHandler .

„Apple“ savo pavyzdžiuose rodo, kad jis naudojamas „ applicationDidEnterBackground delegate, kad gautų daugiau laiko atlikti svarbią užduotį, paprastai tinklo operaciją.

Žiūrint mano paraišką, atrodo, kad dauguma mano tinklo medžiagos yra svarbi, ir kai ji pradės veikti, norėčiau ją užpildyti, jei vartotojas spustelėjo namų mygtuką.

Ar tai yra įprasta / gera praktika suvynioti kiekvieną tinklo operaciją (ir aš nekalbu apie didelių duomenų pakrovimą, daugiausia iš mažo xml), beginBackgroundTaskWithExpirationHandler , kad būtų saugus?

94
25 апр. nustatė Eyal 25 Bal 2012-04-25 19:16 '12, 07:16 PM 2012-04-25 19:16
@ 6 atsakymai

Jei norite, kad tinklo sandoris tęstųsi fone, turite jį įdėti į foninį darbą. Taip pat labai svarbu, kad jūs endBackgroundTask nes priešingu atveju paraiška bus nužudyta pasibaigus jai priskirtam laikui.

Mano tendencija atrodo tokia:

 - (void) doUpdate { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self beginBackgroundUpdateTask]; NSURLResponse * response = nil; NSError * error = nil; NSData * responseData = [NSURLConnection sendSynchronousRequest: request returningResponse:  error:  // Do something with the result [self endBackgroundUpdateTask]; }); } - (void) beginBackgroundUpdateTask { self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [self endBackgroundUpdateTask]; }]; } - (void) endBackgroundUpdateTask { [[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask]; self.backgroundUpdateTask = UIBackgroundTaskInvalid; } 

Turiu UIBackgroundTaskIdentifier savybę kiekvienai fono užduočiai.


Lygiavertis kodas „Swift“

 func doUpdate () { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { let taskID = beginBackgroundUpdateTask() var response: NSURLResponse?, error: NSError?, request: NSURLRequest? let data = NSURLConnection.sendSynchronousRequest(request, returningResponse:  error:  // Do something with the result endBackgroundUpdateTask(taskID) }) } func beginBackgroundUpdateTask() -> UIBackgroundTaskIdentifier { return UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({}) } func endBackgroundUpdateTask(taskID: UIBackgroundTaskIdentifier) { UIApplication.sharedApplication().endBackgroundTask(taskID) } 
152
25 апр. atsakymą pateikė Ashley Mills 25 balandis. 2012-04-25 19:23 '12, 19:23 PM 2012-04-25 19:23

Priimtas atsakymas yra labai naudingas, ir daugeliu atvejų jis turėtų būti geras, bet visa tai man neramu:

  • Kaip pažymėjo nemažai žmonių, užduoties identifikatoriaus išsaugojimas kaip nuosavybė reiškia, kad jis gali būti perrašytas, jei metodas yra kelis kartus panaudotas, o tai lemia užduotį, kuri niekada nebus teisėtai užbaigta, kol OS nebebus priverstinai baigtas pasibaigus galiojimo laikui.

  • Šiam šablonui reikia unikalaus savybės kiekvienam beginBackgroundTaskWithExpirationHandler skambučiui „ beginBackgroundTaskWithExpirationHandler , kuris atrodo sudėtingas, jei turite didesnį taikymą su daugeliu tinklo metodų.

Norėdami išspręsti šias problemas, parašiau singletoną, kuris rūpinasi visa santechnika ir seka aktyvias užduotis žodynuose. Nėra jokių funkcijų, reikalingų užduočių ID sekimui. Atrodo, kad jis veikia gerai. Naudojimas supaprastintas:

 //start the task NSUInteger taskKey = [[BackgroundTaskManager sharedTasks] beginTask]; //do stuff //end the task [[BackgroundTaskManager sharedTasks] endTaskWithKey:taskKey]; 

Pasirinktinai, jei norite pateikti užbaigimo bloką, kuris daro kažką be užduoties užbaigimo (kuris yra įdėtas), galite skambinti:

 NSUInteger taskKey = [[BackgroundTaskManager sharedTasks] beginTaskWithCompletionHandler:^{ //do stuff }]; 

Atitinkamas šaltinio kodas pateikiamas žemiau (vienas punktas neįtrauktas į trumpumą). Komentarai / atsiliepimai yra sveikintini.

 - (id)init { self = [super init]; if (self) { [self setTaskKeyCounter:0]; [self setDictTaskIdentifiers:[NSMutableDictionary dictionary]]; [self setDictTaskCompletionBlocks:[NSMutableDictionary dictionary]]; } return self; } - (NSUInteger)beginTask { return [self beginTaskWithCompletionHandler:nil]; } - (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion; { //read the counter and increment it NSUInteger taskKey; @synchronized(self) { taskKey = self.taskKeyCounter; self.taskKeyCounter++; } //tell the OS to start a task that should continue in the background if needed NSUInteger taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [self endTaskWithKey:taskKey]; }]; //add this task identifier to the active task dictionary [self.dictTaskIdentifiers setObject:[NSNumber numberWithUnsignedLong:taskId] forKey:[NSNumber numberWithUnsignedLong:taskKey]]; //store the completion block (if any) if (_completion) [self.dictTaskCompletionBlocks setObject:_completion forKey:[NSNumber numberWithUnsignedLong:taskKey]]; //return the dictionary key return taskKey; } - (void)endTaskWithKey:(NSUInteger)_key { @synchronized(self.dictTaskCompletionBlocks) { //see if this task has a completion block CompletionBlock completion = [self.dictTaskCompletionBlocks objectForKey:[NSNumber numberWithUnsignedLong:_key]]; if (completion) { //run the completion block and remove it from the completion block dictionary completion(); [self.dictTaskCompletionBlocks removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]]; } } @synchronized(self.dictTaskIdentifiers) { //see if this task has been ended yet NSNumber *taskId = [self.dictTaskIdentifiers objectForKey:[NSNumber numberWithUnsignedLong:_key]]; if (taskId) { //end the task and remove it from the active task dictionary [[UIApplication sharedApplication] endBackgroundTask:[taskId unsignedLongValue]]; [self.dictTaskIdentifiers removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]]; } } } 
19
27 дек. Atsakymą pateikė Joel . 2014-12-27 08:20 '14, 08:20 2014-12-27 08:20

Čia yra „Swift“ klasė , apimanti fono užduoties vykdymą:

 class BackgroundTask { private let application: UIApplication private var identifier = UIBackgroundTaskInvalid init(application: UIApplication) { self.application = application } class func run(application: UIApplication, handler: (BackgroundTask) -> ()) { // NOTE: The handler must call end() when it is done let backgroundTask = BackgroundTask(application: application) backgroundTask.begin() handler(backgroundTask) } func begin() { self.identifier = application.beginBackgroundTaskWithExpirationHandler { self.end() } } func end() { if (identifier != UIBackgroundTaskInvalid) { application.endBackgroundTask(identifier) } identifier = UIBackgroundTaskInvalid } } 

Lengviausias būdas jį naudoti:

 BackgroundTask.run(application) { backgroundTask in // Do something backgroundTask.end() } 

Jei reikia palaukti, kol atstovas paskambins, kol baigsis, naudokite šiuos veiksmus:

 class MyClass { backgroundTask: BackgroundTask? func doSomething() { backgroundTask = BackgroundTask(application) backgroundTask!.begin() // Do something that waits for callback } func callback() { backgroundTask?.end() backgroundTask = nil } } 
15
15 апр. Atsakymą pateikė phatmann balandžio 15 d 2015-04-15 06:17 '15 at 6:17 2015-04-15 06:17

Įdiegiau „Joel“ sprendimą. Čia yra visas kodas:

.h failas:

 #import <Foundation/Foundation.h> @interface VMKBackgroundTaskManager : NSObject + (id) sharedTasks; - (NSUInteger)beginTask; - (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion; - (void)endTaskWithKey:(NSUInteger)_key; @end 

.m failas:

 #import "VMKBackgroundTaskManager.h" @interface VMKBackgroundTaskManager() @property NSUInteger taskKeyCounter; @property NSMutableDictionary *dictTaskIdentifiers; @property NSMutableDictionary *dictTaskCompletionBlocks; @end @implementation VMKBackgroundTaskManager + (id)sharedTasks { static VMKBackgroundTaskManager *sharedTasks = nil; static dispatch_once_t onceToken; dispatch_once( ^{ sharedTasks = [[self alloc] init]; }); return sharedTasks; } - (id)init { self = [super init]; if (self) { [self setTaskKeyCounter:0]; [self setDictTaskIdentifiers:[NSMutableDictionary dictionary]]; [self setDictTaskCompletionBlocks:[NSMutableDictionary dictionary]]; } return self; } - (NSUInteger)beginTask { return [self beginTaskWithCompletionHandler:nil]; } - (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion; { //read the counter and increment it NSUInteger taskKey; @synchronized(self) { taskKey = self.taskKeyCounter; self.taskKeyCounter++; } //tell the OS to start a task that should continue in the background if needed NSUInteger taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [self endTaskWithKey:taskKey]; }]; //add this task identifier to the active task dictionary [self.dictTaskIdentifiers setObject:[NSNumber numberWithUnsignedLong:taskId] forKey:[NSNumber numberWithUnsignedLong:taskKey]]; //store the completion block (if any) if (_completion) [self.dictTaskCompletionBlocks setObject:_completion forKey:[NSNumber numberWithUnsignedLong:taskKey]]; //return the dictionary key return taskKey; } - (void)endTaskWithKey:(NSUInteger)_key { @synchronized(self.dictTaskCompletionBlocks) { //see if this task has a completion block CompletionBlock completion = [self.dictTaskCompletionBlocks objectForKey:[NSNumber numberWithUnsignedLong:_key]]; if (completion) { //run the completion block and remove it from the completion block dictionary completion(); [self.dictTaskCompletionBlocks removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]]; } } @synchronized(self.dictTaskIdentifiers) { //see if this task has been ended yet NSNumber *taskId = [self.dictTaskIdentifiers objectForKey:[NSNumber numberWithUnsignedLong:_key]]; if (taskId) { //end the task and remove it from the active task dictionary [[UIApplication sharedApplication] endBackgroundTask:[taskId unsignedLongValue]]; [self.dictTaskIdentifiers removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]]; NSLog(@"Task ended"); } } } @end 
1
22 мая '16 в 10:01 2016-05-22 10:01 vomako atsakymas gegužės 22 d. 16 d. 10:01 2016-05-22 10:01

Pirmiausia perskaitykite dokumentaciją: https://developer.apple.com/documentation/uikit/uiapplication/1623031-beginbackgroundtaskwithexpiratio

Fono užduotis turi atitikti šiuos reikalavimus:

  • Fono užduotis turėtų būti pranešta kuo greičiau, tačiau tai neturėtų būti daroma prieš pradedant mūsų tikrąją užduotį. Metodas „ beginBackgroundTaskWithExpirationHandler: veikia asinchroniškai, todėl, jei jis vadinamas „ applicationDidEnterBackground: pabaigoje applicationDidEnterBackground: jis neregistruoja foninės užduoties ir nedelsdamas kreipiasi į galiojimo laiko tvarkytoją.
  • Galiojimo pabaigos tvarkytojas turi atšaukti mūsų tikrąją užduotį ir pažymėti foninę užduotį kaip baigtą. Tai verčia mus saugoti fono užduoties identifikatorių, saugomą kažkur, pavyzdžiui, kaip tam tikros klasės atributą. Ši nuosavybė turi būti kontroliuojama, kad nebūtų galima perrašyti.
  • Galiojimo termino tvarkytojas vykdomas iš pagrindinio gijos, taigi jūsų tikroji užduotis turėtų būti saugi, jei norite jį atšaukti.
  • Mūsų dabartinė užduotis turi būti panaikinta. Tai reiškia, kad mūsų tikroji užduotis turi turėti cancel metodą. Priešingu atveju kyla pavojus, kad jis bus nutrauktas nenuspėjamu būdu, net jei pažymėsime foninę užduotį kaip užbaigtą.
  • Kodas, kurio sudėtyje yra beginBackgroundTaskWithExpirationHandler: gali būti vadinamas bet kur ir bet kuriame beginBackgroundTaskWithExpirationHandler: . Tai neturėtų būti „ applicationDidEnterBackground: delegato metodas applicationDidEnterBackground:
  • Nereikia to daryti sinchroninėms operacijoms, trumpesnėms nei 5 sekundėms, kai naudojate applicationDidEnterBackground: metodą applicationDidEnterBackground: (prašome perskaityti dokumentą https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622997-appdidtertergroundground_> )
  • applicationDidEnterBackground metodas turi būti baigtas per mažiau nei 5 sekundes, todėl visos fono užduotys turi būti vykdomos antrajame sriegyje.

Pavyzdys:

 class MySpecificBackgroundTask: NSObject, URLSessionDataDelegate { // MARK: - Properties let application: UIApplication var backgroundTaskIdentifier: UIBackgroundTaskIdentifier var task: URLSessionDataTask? = nil // MARK: - Initializers init(application: UIApplication) { self.application = application self.backgroundTaskIdentifier = UIBackgroundTaskInvalid } // MARK: - Actions func start() { self.backgroundTaskIdentifier = self.application.beginBackgroundTask { self.cancel() } self.startUrlRequest() } func cancel() { self.task?.cancel() self.end() } private func end() { self.application.endBackgroundTask(self.backgroundTaskIdentifier) self.backgroundTaskIdentifier = UIBackgroundTaskInvalid } // MARK: - URLSession methods private func startUrlRequest() { let sessionConfig = URLSessionConfiguration.background(withIdentifier: "MySpecificBackgroundTaskId") let session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil) guard let url = URL(string: "https://example.com/api/my/path") else { self.end() return } let request = URLRequest(url: url) self.task = session.dataTask(with: request) self.task?.resume() } // MARK: - URLSessionDataDelegate methods func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { self.end() } // Implement other methods of URLSessionDataDelegate to handle response... } 

Galima naudoti mūsų paraiškos delegate:

 func applicationDidEnterBackground(_ application: UIApplication) { let myBackgroundTask = MySpecificBackgroundTask(application: application) myBackgroundTask.start() } 
08 янв. Atsakymas, kurį pateikė Ariel Bogdziewicz 08 sausis 2019-01-08 17:07 '19, 17:07 pm 2019-01-08 17:07
  • (void) doUpdate {dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {

     [self beginBackgroundUpdateTask]; NSURLResponse * response = nil; NSError * error = nil; NSData * responseData = [NSURLConnection sendSynchronousRequest: request returningResponse:  error: 

    klaida];

     // Do something with the result [self endBackgroundUpdateTask]; 

    }); }

  • (void) beginBackgroundUpdateTask {self.backgroundUpdateTask = [[UIApplication sharedApplication] sākumaBackgroundTaskWithExpirationHandler: ^ {[self endBackgroundUpdateTask]; }]; }

  • (void) endBackgroundUpdateTask {[[UAppication sharedApplication] endBackgroundTask: self.backgroundUpdateTask]; self.backgroundUpdateTask = UIBackgroundTaskInvalid; }

Ačiū Ashley Mills, tai puikiai tinka man.

0
03 марта '17 в 17:34 2017-03-03 17:34 atsakymas duodamas Bilal L kovo 3 d. 17 d. 17:34 2017-03-03 17:34