سؤال

أنا أستخدم Grand Central Dispatch (GCD) في طلبي للقيام ببعض الرفع الثقيل. يستخدم التطبيق البيانات الأساسية لأغراض تخزين البيانات. هذا هو السيناريو الخاص بي (جنبا إلى جنب مع السؤال ذي الصلة):

dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_queue_t request_queue = dispatch_queue_create("com.app.request", NULL);

dispatch_async(request_queue, ^{
    MyNSManagedObject *mObject = [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];

    // … 
    // <heavy lifting>
    // … 

    // … 
    // <update mObject>
    // … 

    [self saveManagedObjectContext];
});     

كنتيجة ل [self saveManagedObjectContext], fetchResultsController تسمى أساليب المفوض تلقائيًا. وبالتالي ، يركل منطق تحديث واجهة المستخدم.

الآن سؤالي هو ، هل أحتاج إلى استخدام main_queue إلى عن على -saveManagedObjectContext؟ هل يجب علي أداء جميع العمليات على بلدي NSManagedObject في main_queue؟ بعض العمليات التي تحديث NSManagedObject قد يستغرق 2-3 ثوان. يرجى تقديم النصيحة.

هل كانت مفيدة؟

المحلول

كما تعلمون أو لاحظت أنه يجب عليك إجراء عمليات واجهة المستخدم على الموضوع الرئيسي. كما ذكرت ذلك عند حفظ تحديث واجهة المستخدم. يمكنك حل هذا عن طريق تعشيش مكالمة dispatch_sync على الموضوع الرئيسي.

dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_queue_t request_queue = dispatch_queue_create("com.app.request", NULL);

__block __typeof__(self) blockSelf = self;

dispatch_async(request_queue, ^{
    MyNSManagedObject *mObject = [blockSelf.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];

    // update and heavy lifting...

    dispatch_sync(main_queue, ^{
      [blockSelf saveManagedObjectContext];
    });
});     

استخدام blockSelf هو تجنب إنشاء دورات مرجعية عن طريق الخطأ. (كتل عملية)

نصائح أخرى

هناك قاعدة ذهبية عندما يتعلق الأمر بالبيانات الأساسية - سياق كائن مُدار واحد لكل مؤشر ترابط. سياقات الكائن المدارة ليست آمنة لخيط مؤشر ترابط ، لذا إذا كنت تقوم بعمل في مهمة خلفية ، فأنت إما تستخدم مؤشر الترابط الرئيسي لتجنب تعارضات الخيوط مع عمليات واجهة المستخدم ، أو تقوم بإنشاء سياق جديد للقيام بالعمل فيه. إذا كان العمل سيستغرق بعد بضع ثوان ، يجب عليك القيام بالأخير لمنع واجهة المستخدم الخاصة بك من القفل.

للقيام بذلك ، يمكنك إنشاء سياق جديد ومنحه نفس المتجر الثابت مثل السياق الرئيسي الخاص بك:

NSManagedObjectContext *backgroundContext = [[[NSManagedObjectContext alloc] init] autorelease];
[backgroundContext setPersistentStoreCoordinator:[mainContext persistentStoreCoordinator]];

افعل أي العمليات التي تحتاج إلى القيام بها ، ثم عند حفظ هذا السياق الجديد ، تحتاج إلى التعامل مع إشعار حفظ ودمج التغييرات في سياقك الرئيسي مع mergeChangesFromContextDidSaveNotification: رسالة. يجب أن يبدو الرمز شيئًا كهذا:

/* Save notification handler for the background context */
- (void)backgroundContextDidSave:(NSNotification *)notification {
    /* Make sure we're on the main thread when updating the main context */
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(backgroundContextDidSave:)
                               withObject:notification
                            waitUntilDone:NO];
        return;
    }

    /* merge in the changes to the main context */
    [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}

/* ... */

/* Save the background context and handle the save notification */
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(backgroundContextDidSave:)
                                             name:NSManagedObjectContextDidSaveNotification
                                           object:backgroundContext];

[backgroundContext save:NULL];

[[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:NSManagedObjectContextDidSaveNotification
                                              object:syncContext];

من المهم معالجة Save Notifcation ودمجها ، وإلا فلن يرى واجهة المستخدم/السياق الرئيسي الخاص بك التغييرات التي أجريتها. من خلال الاندماج ، ستحصل على تغيير أحداث FetchResultsController الرئيسية وما إلى ذلك.

شيء مهم آخر هو أن نلاحظ أنه لا يمكن استخدام مثيلات NSManagedObject إلا في السياق الذي تم جلبه منه. إذا كانت العملية تحتاج إلى مرجع إلى كائن ما ، فعليك تمرير الكائن objectID إلى التشغيل وإعادة صياغة مثيل NSManagedObject من السياق الجديد باستخدام existingObjectWithID:. لذلك شيء مثل:

/* This can only be used in operations on the main context */
MyNSManagedObject *objectInMainContext =
    [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];

/* This can now be used in your background context */
MyNSManagedObject *objectInBackgroundContext =
    (MyNSManagedObject *) [backgroundContext existingObjectWithID:[objectInMainContext objectID]];

نظرًا لأن البيانات الأساسية تتطلب سياق كائن مُدار واحد لكل مؤشر ترابط ، فإن الحل المحتمل هو تتبع سياق لكل مؤشر ترابط في مدير عالمي ، ثم تتبع الإخطارات حفظ وانتشر إلى جميع مؤشرات الترابط:

على افتراض:

@property (nonatomic, strong) NSDictionary* threadsDictionary;

فيما يلي كيفية الحصول على الكائن المدار (لكل موضوع):

- (NSManagedObjectContext *) managedObjectContextForThread {

// Per thread, give one back
NSString* threadName = [NSString stringWithFormat:@"%d",[NSThread currentThread].hash];

NSManagedObjectContext * existingContext = [self.threadsDictionary objectForKey:threadName];
if (existingContext==nil){
    existingContext = [[NSManagedObjectContext alloc] init];
    [existingContext setPersistentStoreCoordinator: [self persistentStoreCoordinator]];
    [self.threadsDictionary setValue:existingContext forKey:threadName];
}

return existingContext;

}

في مرحلة ما من طريقة init لمديرك العالمي (استخدمت مفردة):

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundContextDidSave:)                                                    name:NSManagedObjectContextDidSaveNotification                                                   object:nil];

ثم لتلقي الإخطارات حفظ ونشر جميع كائنات السياق المدارة الأخرى:

- (void)backgroundContextDidSave:(NSNotification *)notification {
    /* Make sure we're on the main thread when updating the main context */
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(backgroundContextDidSave:)
                               withObject:notification
                            waitUntilDone:NO];
        return;
    }

    /* merge in the changes to the main context */
    for (NSManagedObjectContext* context in [self.threadsDictionary allValues]){
            [context mergeChangesFromContextDidSaveNotification:notification];
    }
}

(تمت إزالة بعض الطرق الأخرى للوضوح)

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top