StartBackgroundTaskWithExpirationHandler का उचित उपयोग


107

मैं थोड़ा उलझन में हूँ कि कैसे और कब उपयोग करना है beginBackgroundTaskWithExpirationHandler

ऐप्पल अपने उदाहरणों में दिखाता है कि इसे applicationDidEnterBackgroundप्रतिनिधि में उपयोग करने के लिए, कुछ महत्वपूर्ण कार्य को पूरा करने के लिए अधिक समय मिलता है, आमतौर पर एक नेटवर्क लेनदेन।

जब मेरे ऐप को देखते हैं, तो ऐसा लगता है कि मेरा अधिकांश नेटवर्क सामान महत्वपूर्ण है, और जब कोई शुरू होता है तो मैं इसे पूरा करना चाहूंगा यदि उपयोगकर्ता होम बटन दबाए।

तो क्या यह स्वीकार किया जाता है / प्रत्येक नेटवर्क लेनदेन को लपेटने के लिए अच्छा अभ्यास (और मैं डेटा के बड़े हिस्से को डाउनलोड करने के बारे में बात नहीं कर रहा हूं, यह ज्यादातर कुछ छोटी xml है) beginBackgroundTaskWithExpirationHandlerसुरक्षित पक्ष पर होने के लिए?


जवाबों:


165

यदि आप चाहते हैं कि आपका नेटवर्क लेनदेन पृष्ठभूमि में जारी रहे, तो आपको इसे पृष्ठभूमि कार्य में लपेटने की आवश्यकता होगी। यह भी बहुत महत्वपूर्ण है कि endBackgroundTaskजब आप समाप्त कर लें, तो कॉल करें - अन्यथा इसके आवंटित समय समाप्त होने के बाद ऐप को मार दिया जाएगा।

मेरा रुझान कुछ इस तरह है:

- (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: &response error: &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;
}

मेरे पास UIBackgroundTaskIdentifierप्रत्येक पृष्ठभूमि कार्य के लिए एक संपत्ति है


स्विफ्ट में समान कोड

func doUpdate () {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {

        let taskID = beginBackgroundUpdateTask()

        var response: URLResponse?, error: NSError?, request: NSURLRequest?

        let data = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)

        // Do something with the result

        endBackgroundUpdateTask(taskID)

        })
}

func beginBackgroundUpdateTask() -> UIBackgroundTaskIdentifier {
    return UIApplication.shared.beginBackgroundTask(expirationHandler: ({}))
}

func endBackgroundUpdateTask(taskID: UIBackgroundTaskIdentifier) {
    UIApplication.shared.endBackgroundTask(taskID)
}

1
हां, मैं करता हूं ... अन्यथा जब एप्लिकेशन पृष्ठभूमि में प्रवेश करता है तो वे रुक जाते हैं।
एशले मिल्स

1
क्या हमें ApplicationDidEnterBackground में कुछ भी करने की आवश्यकता है?
डिप

1
केवल तभी यदि आप नेटवर्क ऑपरेशन को शुरू करने के लिए एक बिंदु के रूप में उपयोग करना चाहते हैं । तुम सिर्फ एक मौजूदा आपरेशन प्रति @ ईयाल के प्रश्न के रूप में, को पूरा करना चाहते हैं, तो आप applicationDidEnterBackground में कुछ भी करने की जरूरत नहीं है
एशले मिल्स

2
इस स्पष्ट उदाहरण के लिए धन्यवाद! (बस परिवर्तित किया जा रहा है बैकग्राउंड यूपीडेटटैस्क से शुरू करने के लिए बैकग्राउंड यूटडेटटस्क।)
newenglander

30
यदि आप किसी कार्य के बिना कई बार doUpdate को कॉल करते हैं, तो आप self.backgroundUpdateTask को अधिलेखित कर देंगे ताकि पिछले कार्य ठीक से समाप्त न हो सकें। आपको हर बार कार्य पहचानकर्ता को संग्रहीत करना चाहिए ताकि आप इसे ठीक से समाप्त कर सकें या शुरुआत / अंत विधियों में एक काउंटर का उपयोग कर सकें।
द अज़ाज़

23

स्वीकृत उत्तर बहुत उपयोगी है और ज्यादातर मामलों में ठीक होना चाहिए, हालांकि दो चीजों ने मुझे इसके बारे में परेशान किया:

  1. जैसा कि कई लोगों ने नोट किया है, एक संपत्ति के रूप में कार्य पहचानकर्ता को संग्रहीत करने का मतलब है कि इसे अलग-अलग लिखा जा सकता है यदि विधि को कई बार कहा जाता है, तो ऐसे कार्य के लिए अग्रणी जिसे कभी भी समाप्त नहीं किया जाएगा जब तक कि समय समाप्ति पर ओएस द्वारा समाप्त करने के लिए मजबूर नहीं किया जाता है। ।

  2. इस पैटर्न के लिए प्रत्येक कॉल के लिए एक अद्वितीय गुण की आवश्यकता होती है, beginBackgroundTaskWithExpirationHandlerजो आपको बोझिल लगता है यदि आपके पास बहुत सारी नेटवर्क विधियाँ हैं।

इन मुद्दों को हल करने के लिए, मैंने एक सिंगलटन लिखा जो सभी प्लंबिंग का ध्यान रखता है और एक शब्दकोश में सक्रिय कार्यों को ट्रैक करता है। कार्य पहचानकर्ताओं पर नज़र रखने के लिए आवश्यक कोई गुण नहीं। अच्छा काम करने लगता है। उपयोग सरल है:

//start the task
NSUInteger taskKey = [[BackgroundTaskManager sharedTasks] beginTask];

//do stuff

//end the task
[[BackgroundTaskManager sharedTasks] endTaskWithKey:taskKey];

वैकल्पिक रूप से, यदि आप एक पूर्ण ब्लॉक प्रदान करना चाहते हैं जो कार्य को समाप्त करने से परे कुछ करता है (जिसमें निर्मित है) तो आप कॉल कर सकते हैं:

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

नीचे उपलब्ध प्रासंगिक स्रोत कोड (संक्षिप्तता के लिए शामिल एकल सामग्री)। टिप्पणियाँ / प्रतिक्रिया का स्वागत करते हैं।

- (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]];

        }

    }
}

1
वास्तव में इस समाधान की तरह। एक सवाल हालांकि: कैसे / क्या आप के रूप में typedefपूर्ण समापन किया? बस यह:typedef void (^CompletionBlock)();
जोसफ

आपको यह मिला। typedef void (^ कंप्लीटेशनब्लॉक) (void);
जोएल

@joel, धन्यवाद, लेकिन इस कार्यान्वयन के लिए स्रोत कोड का लिंक कहां है, i, e, BackGroundTaskManager?
54zgür

जैसा कि ऊपर उल्लेख किया गया है "संक्षिप्तता के लिए एकल सामग्री को बाहर रखा गया है"। [BackgroundTaskManager साझा किए गए कार्य] एक सिंगलटन देता है। सिंगलटन की हिम्मत ऊपर दी गई है।
जोएल

एक सिंगलटन का उपयोग करने के लिए बनाया गया। मुझे नहीं लगता कि वे लोग उतने ही बुरे हैं जितने लोग बाहर हैं!
क्रेग वाटकिंसन 20

20

यहाँ एक स्विफ्ट क्लास है जो एक बैकग्राउंड टास्क को इनकैप्सुलेट करती है:

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
    }
}

इसका उपयोग करने का सबसे सरल तरीका:

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

यदि आपको समाप्त होने से पहले एक प्रतिनिधि कॉलबैक की प्रतीक्षा करने की आवश्यकता है, तो कुछ इस तरह का उपयोग करें:

class MyClass {
    backgroundTask: BackgroundTask?

    func doSomething() {
        backgroundTask = BackgroundTask(application)
        backgroundTask!.begin()
        // Do something that waits for callback
    }

    func callback() {
        backgroundTask?.end()
        backgroundTask = nil
    } 
}

स्वीकार किए गए उत्तर में जैसी समस्या है। समाप्ति हैंडलर वास्तविक कार्य को रद्द नहीं करता है, लेकिन केवल इसे समाप्त के रूप में चिह्नित करता है। इनकैप्सुलेशन से अधिक यह कारण है कि हम स्वयं ऐसा करने में सक्षम नहीं हैं। इसलिए Apple ने इस हैंडलर को उजागर किया, इसलिए यहां अतिक्रमण गलत है।
एरियल बोगडज़िविक्ज 13

@ArielBogdziewicz यह सच है कि यह उत्तर beginविधि में अतिरिक्त सफाई के लिए कोई अवसर प्रदान नहीं करता है , लेकिन यह देखना आसान है कि उस सुविधा को कैसे जोड़ा जाए।
मैट

6

जैसा कि यहां उल्लेख किया गया है और अन्य एसओ सवालों के जवाब में, आप beginBackgroundTaskकेवल तब उपयोग नहीं करना चाहते हैं जब आपका ऐप पृष्ठभूमि में जाएगा; इसके विपरीत, आप के लिए एक पृष्ठभूमि कार्य का उपयोग करना चाहिए किसी भी समय लेने वाली आपरेशन जिसका पूरा होने आप अगर एप्लिकेशन भी सुनिश्चित करना चाहते हैं करता है पृष्ठभूमि में जाने।

इसलिए आपका कोड कॉलिंग beginBackgroundTaskऔर endBackgroundTaskसुसंगत रूप से एक ही बॉयलरप्लेट कोड के दोहराव के साथ समाप्त होने की संभावना है । इस पुनरावृत्ति को रोकने के लिए, यह निश्चित रूप से उचित है कि बॉयलरप्लेट को कुछ एकल एन्कैप्सुलेटेड इकाई में पैकेज करना चाहिए।

मुझे ऐसा करने के लिए कुछ मौजूदा उत्तर पसंद हैं, लेकिन मुझे लगता है कि ऑपरेशन उपवर्ग का उपयोग करने का सबसे अच्छा तरीका है:

  • आप किसी भी OperationQueue पर ऑपरेशन को संलग्न कर सकते हैं और उस कतार में हेरफेर कर सकते हैं जैसा कि आप फिट देखते हैं। उदाहरण के लिए, आप कतार में किसी भी मौजूदा परिचालन को समय से पहले रद्द करने के लिए स्वतंत्र हैं।

  • यदि आपके पास करने के लिए एक से अधिक चीजें हैं, तो आप कई पृष्ठभूमि कार्य संचालन को चेन कर सकते हैं। संचालन समर्थन निर्भरताएँ।

  • ऑपरेशन कतार एक पृष्ठभूमि कतार हो सकती है (और होनी चाहिए); इस प्रकार, आपकी कार्य अंदर एसिंक्रोनस कोड प्रदर्शन के बारे में चिंता करने की कोई जरूरत है, क्योंकि ऑपरेशन है है अतुल्यकालिक कोड। (वास्तव में, यह ऑपरेशन के अंदर एक और स्तर के अतुल्यकालिक कोड को निष्पादित करने का कोई मतलब नहीं है , क्योंकि ऑपरेशन इससे पहले कि कोड भी शुरू हो सकता है। यदि आपको ऐसा करने की आवश्यकता है, तो आप दूसरे ऑपरेशन का उपयोग करेंगे।)

यहाँ एक संभावित ऑपरेशन उपवर्ग है:

class BackgroundTaskOperation: Operation {
    var whatToDo : (() -> ())?
    var cleanup : (() -> ())?
    override func main() {
        guard !self.isCancelled else { return }
        guard let whatToDo = self.whatToDo else { return }
        var bti : UIBackgroundTaskIdentifier = .invalid
        bti = UIApplication.shared.beginBackgroundTask {
            self.cleanup?()
            self.cancel()
            UIApplication.shared.endBackgroundTask(bti) // cancellation
        }
        guard bti != .invalid else { return }
        whatToDo()
        guard !self.isCancelled else { return }
        UIApplication.shared.endBackgroundTask(bti) // completion
    }
}

यह स्पष्ट होना चाहिए कि इसका उपयोग कैसे किया जाए, लेकिन अगर ऐसा नहीं है, तो कल्पना करें कि हमारे पास एक वैश्विक ऑपरेशन क्यू है:

let backgroundTaskQueue : OperationQueue = {
    let q = OperationQueue()
    q.maxConcurrentOperationCount = 1
    return q
}()

तो कोड के एक विशिष्ट समय लेने वाले बैच के लिए हम कहेंगे:

let task = BackgroundTaskOperation()
task.whatToDo = {
    // do something here
}
backgroundTaskQueue.addOperation(task)

यदि आपके समय लेने वाले कोड के बैच को चरणों में विभाजित किया जा सकता है, तो आप अपना कार्य रद्द होने पर जल्दी से झुकना चाहते हैं। उस स्थिति में, बस समय से पहले बंद हो जाना। ध्यान दें कि बंद के भीतर से कार्य के लिए आपका संदर्भ कमजोर होना चाहिए या आपको एक चक्र बनाए रखना होगा। यहाँ एक कृत्रिम चित्रण है:

let task = BackgroundTaskOperation()
task.whatToDo = { [weak task] in
    guard let task = task else {return}
    for i in 1...10000 {
        guard !task.isCancelled else {return}
        for j in 1...150000 {
            let k = i*j
        }
    }
}
backgroundTaskQueue.addOperation(task)

यदि आपके पास सफाई कार्य करने की स्थिति में पृष्ठभूमि का कार्य समय से पहले ही रद्द हो जाता है, तो मैंने एक वैकल्पिक cleanupहैंडलर संपत्ति (पूर्ववर्ती उदाहरणों में उपयोग नहीं की गई) प्रदान की है। इसमें शामिल नहीं होने के लिए कुछ अन्य उत्तरों की आलोचना की गई।


मैंने अब इसे गिथब प्रोजेक्ट के रूप में प्रदान किया है: github.com/mattneub/BackgroundTaskOperation
मैट

1

मैंने जोएल का समाधान लागू किया। यहाँ पूरा कोड है:

.h फ़ाइल:

#import <Foundation/Foundation.h>

@interface VMKBackgroundTaskManager : NSObject

+ (id) sharedTasks;

- (NSUInteger)beginTask;
- (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion;
- (void)endTaskWithKey:(NSUInteger)_key;

@end

.m फ़ाइल:

#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(&onceToken, ^{
        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
इसके लिए धन्यवाद। मेरा उद्देश्य-ग महान नहीं है। क्या आप कुछ कोड जोड़ सकते हैं जो दिखाते हैं कि इसका उपयोग कैसे करना है?
पोमो

क्या आप उर कोड का उपयोग करने के बारे में एक पूरा उदाहरण दे सकते हैं
अमृत ​​एंग्री

बहुत अच्छा। धन्यवाद।
एल्योसक नोव
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.