সূচনাব্যাকগ্রাউন্ড টাস্কউইট এক্সপায়ারহ্যান্ডারের সঠিক ব্যবহার


107

কীভাবে এবং কখন ব্যবহার করব সে সম্পর্কে আমি কিছুটা বিভ্রান্ত beginBackgroundTaskWithExpirationHandler

অ্যাপল তাদের উদাহরণগুলিতে এটি applicationDidEnterBackgroundপ্রতিনিধি হিসাবে ব্যবহার করতে , কিছু গুরুত্বপূর্ণ কাজটি সাধারণত নেটওয়ার্ক লেনদেন সম্পন্ন করার জন্য আরও সময় পাওয়ার জন্য দেখায় ।

আমার অ্যাপ্লিকেশনটি দেখার সময় মনে হচ্ছে আমার বেশিরভাগ নেটওয়ার্ক স্টাফ গুরুত্বপূর্ণ, এবং যখন একটি শুরু করা হয় তখন আমি ব্যবহারকারী সম্পূর্ণরূপে হোম হোস্ট বোতাম টিপলে এটি সম্পন্ন করতে চাই।

তাহলে কি 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প্রতিটি পটভূমি কাজের জন্য আমার সম্পত্তি আছে a


সুইফটে সমমানের কোড

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
অ্যাপ্লিকেশনডিন্টারব্যাকগ্রাউন্ডে আমাদের কিছু করার দরকার আছে?
dips

1
আপনি যদি নেটওয়ার্ক অপারেশন শুরু করতে পয়েন্ট হিসাবে এটি ব্যবহার করতে চান তবেই । আপনি যদি কেবলমাত্র ইল এর প্রশ্ন অনুসারে একটি বিদ্যমান অপারেশন সম্পূর্ণ করতে চান তবে আপনাকে অ্যাপ্লিকেশনটিতে কিছু করার দরকার
অ্যাশলে মিলস

2
এই পরিষ্কার উদাহরণ জন্য ধন্যবাদ! (জাস্ট beginBackgroundUpdateTask করার beingBackgroundUpdateTask পরিবর্তন করেছেন।)
newenglander

30
যদি আপনি কাজটি না করেই একনাগাড়ে একাধিকবার ডুউপেটকে কল করেন তবে আপনি স্ব-ব্যাকগ্রাউন্ডআপডেটটাস্কটি ওভাররাইট করে দেবেন যাতে আগের কাজগুলি সঠিকভাবে শেষ করা যায় না। হয় আপনি প্রতিটি সময় টাস্ক শনাক্তকারী সংরক্ষণ করা উচিত যাতে আপনি এটি সঠিকভাবে শেষ করেন বা শুরু / শেষ পদ্ধতিতে একটি কাউন্টার ব্যবহার করুন।
thejaz

23

গৃহীত উত্তরটি খুব সহায়ক এবং বেশিরভাগ ক্ষেত্রেই ঠিক হওয়া উচিত, তবে দুটি বিষয়ই আমাকে বিরক্ত করেছিল:

  1. বেশিরভাগ লোক উল্লেখ করেছেন যে, সম্পত্তি হিসাবে টাস্ক আইডেন্টিফায়ার সংরক্ষণ করার অর্থ যদি পদ্ধতিটি একাধিকবার বলা হয় তবে এটি ওভাররাইট করা যেতে পারে, যার ফলে কোনও কাজ কখনই নিখুঁতভাবে শেষ হয় না যতক্ষণ না ওএসের দ্বারা সমাপ্তির সময় শেষ না করা হয় ।

  2. এই প্যাটার্নটির প্রতিটি কলের জন্য একটি অনন্য সম্পত্তি প্রয়োজন beginBackgroundTaskWithExpirationHandlerযা আপনার কাছে প্রচুর নেটওয়ার্ক পদ্ধতি সহ একটি বৃহত্তর অ্যাপ্লিকেশন রয়েছে বলে মনে হচ্ছে জটিল seems

এই সমস্যাগুলি সমাধান করার জন্য, আমি একটি সিঙ্গলটন লিখেছিলাম যা সমস্ত নদীর গভীরতানির্ণয় যত্ন করে এবং অভিধানে সক্রিয় কাজগুলি ট্র্যাক করে। টাস্ক শনাক্তকারীদের ট্র্যাক রাখতে কোনও বৈশিষ্ট্যের প্রয়োজন নেই। ভাল কাজ মনে হচ্ছে। ব্যবহারগুলি এখানে সরল করা হয়েছে:

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

//do stuff

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

Allyচ্ছিকভাবে, আপনি যদি একটি সমাপ্তি ব্লক সরবরাহ করতে চান যা কার্য শেষ করার বাইরে কিছু করে (যা অন্তর্নির্মিত রয়েছে) আপনি কল করতে পারেন:

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)();
জোসেফ

তুমি বুঝতে পেরেছ. টাইপডেফ শূন্যতা (le কমপ্লিটব্লক) (শূন্য);
জোয়েল

@ জোয়েল, ধন্যবাদ তবে এই বাস্তবায়নের জন্য সোর্স কোডের লিঙ্কটি কোথায়, আই, ই, ব্যাকগ্রাউন্ডটাস্ক ম্যানেজার?
üzgür

উপরে উল্লিখিত হিসাবে "বংশবৃদ্ধির জন্য বাদ দেওয়া সিঙ্গলটন স্টাফ"। [ব্যাকগ্রাউন্ডটাস্কম্যানেজার শেয়ারডটাস্কস] একটি সিঙ্গলটন দেয়। সিঙ্গেলনের সাহস উপরে সরবরাহ করা হয়।
জোয়েল

একটি সিঙ্গলটন ব্যবহারের জন্য উত্সাহিত। আমি সত্যিই ভাবি না যে এগুলি লোকেরা যত খারাপ করেছে!
ক্রেগ ওয়াটকিনসন

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

গৃহীত উত্তরের মতো একই সমস্যা। মেয়াদোত্তীর্ণ হ্যান্ডলার রিয়েল টাস্ক বাতিল করে না, তবে কেবল এটি শেষ হিসাবে চিহ্নিত করে। বেশি পরিমাণে এনক্যাপসুলেশনের কারণে আমরা নিজেরাই তা করতে সক্ষম নই। এজন্য অ্যাপল এই হ্যান্ডলারটিকে উন্মোচিত করেছে, সুতরাং এখানে encapsulation ভুল।
এরিয়েল বোগডিজিউইচ

@ অ্যারিলবোগডিজিউইচz এটি সত্য যে এই উত্তরটি beginপদ্ধতিতে অতিরিক্ত পরিষ্কারের জন্য কোনও সুযোগ দেয় না , তবে কীভাবে কীভাবে এই বৈশিষ্ট্যটি যুক্ত করা যায় তা দেখতে সহজ।
ম্যাট

6

যেমনটি এখানে উল্লেখ করা হয়েছে এবং অন্যান্য এসও প্রশ্নের উত্তর হিসাবে উল্লেখ করা হয়েছে, আপনি beginBackgroundTaskকেবল তখনই ব্যবহার করতে চাইবেন না যখন আপনার অ্যাপটি ব্যাকগ্রাউন্ডে যাবে; বিপরীতে, আপনার যে কোনও সময় গ্রহণকারী ক্রিয়াকলাপের জন্য একটি ব্যাকগ্রাউন্ড টাস্ক ব্যবহার করা উচিত যার অ্যাপ্লিকেশন ব্যাকগ্রাউন্ডে চলে না গেলেও আপনি এটি নিশ্চিত করতে চান completion

সুতরাং আপনার কোডটি একই বয়লারপ্লেট কোডটির পুনরাবৃত্তিগুলি কল করার জন্য beginBackgroundTaskএবং endBackgroundTaskসুসংগতভাবে শেষ হতে পারে। এই পুনরাবৃত্তি রোধ করতে, বয়লারপ্লেটটিকে কিছু একক এনপ্যাপুলেটেড সত্তায় প্যাকেজ করতে চান তা অবশ্যই যুক্তিসঙ্গত।

আমি এটি করার জন্য কিছু বিদ্যমান উত্তর পছন্দ করি তবে আমার মনে হয় একটি অপারেশন সাবক্লাস ব্যবহার করা সবচেয়ে ভাল উপায়:

  • আপনি যে কোনও অপারেশনকিউতে অপারেশনটিকে সারিবদ্ধ করতে পারেন এবং উপযুক্ত দেখায় সেই সারিটি পরিচালনা করতে পারেন। উদাহরণস্বরূপ, আপনি কাতারে বিদ্যমান যে কোনও ক্রিয়াকলাপ অসময়ে বাতিল করতে পারেন।

  • যদি আপনার একাধিক কাজ করতে হয় তবে আপনি একাধিক ব্যাকগ্রাউন্ড টাস্ক অপারেশনগুলি চেইন করতে পারেন। অপারেশন নির্ভরতা সমর্থন করে।

  • অপারেশন সারি একটি পটভূমি সারি হতে পারে (এবং হওয়া উচিত); সুতরাং, আপনার টাস্কের মধ্যে অ্যাসিনক্রোনাস কোড সম্পাদন করার বিষয়ে কোনও চিন্তা করার দরকার নেই, কারণ অপারেশন হ'ল অ্যাসিনক্রোনাস কোড। (প্রকৃতপক্ষে, অপারেশনের অভ্যন্তরে অ্যাসিক্রোনাস কোডের অন্য স্তরটি কার্যকর করার কোনও অর্থ হয় না , কারণ কোডটি শুরু হওয়ার আগেই অপারেশন শেষ হয়ে যায়। যদি আপনাকে এটি করার দরকার হয় তবে আপনি অন্য অপারেশনটি ব্যবহার করবেন।)

এখানে একটি সম্ভাব্য অপারেশন সাবক্লাস রয়েছে:

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হ্যান্ডলারের সম্পত্তি সরবরাহ করেছি (পূর্ববর্তী উদাহরণগুলিতে ব্যবহৃত হয় না)। অন্যান্য কিছু উত্তর এটি সহ না জন্য সমালোচিত হয়েছিল।


আমি এখন এটি একটি গিথব
ম্যাট

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
এর জন্য ধন্যবাদ. আমার উদ্দেশ্য-সি দুর্দান্ত নয়। আপনি কি এমন কিছু কোড যুক্ত করতে পারেন যা দেখায় যে এটি কীভাবে ব্যবহার করা যায়?
পোমো

আপনি কীভাবে আপনার কোডটি ব্যবহার করতে হয় তার একটি সম্পূর্ণ উদাহরণ দিতে পারেন
আম্র অ্যাগ্রি

খুব সুন্দর. ধন্যবাদ।
অ্যালোশাক
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.