Objective-C - 构造用于后台处理的GCD代码 [英] Objective-C – Structuring GCD code for background processing

查看:102
本文介绍了Objective-C - 构造用于后台处理的GCD代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一些代码需要一些时间来处理,因此它适当地不应该在主队列上运行。但是我不知道如何正确地结构化GCD代码段。即每次应用程式启动时,我正在进行同步作业:



AppDelegate.m

   - (void)applicationDidBecomeActive:(UIApplication *)application {

AddressBookHelper * abHelper = [AddressBookHelper sharedInstance]; // NSlet的单例辅助类

[abHelper sync];
}

AddressBookHelper中的同步代码如下所示:



AddressBookHelper.m

   - (void)sync {

NSArray * people = //从核心数据获取一些人

NSMutableArray * syncConflicts;
//开始做一些业务逻辑,迭代数据等等

for(id object in people){
//处理一些数据
[syncConflicts addObject :目的];
}

self.syncConflicts = syncConflicts;

//我已经分离了这个方法以保持代码更清晰,并分离方法的逻辑
[self processSyncConflicts];
}

- (void)processSyncConflicts {

if([self.syncConflicts count]> 0){
//提醒用户同步冲突通过显示一个UIAlertView来执行操作
UIAlertView * alert;
[alert show];

} else {

//同步完成
}
}

因此,使用这个代码结构,我如何正确使用GCD将这个代码放在后台线程上?



  

- (void)applicationDidBecomeActive:(UIApplication *)application {

AddressBookHelper * abHelper = [AddressBookHelper sharedInstance]; // NSlet的单例辅助类

dispatch_queue_t queue = dispatch_queue_create(addressbookSyncQueue,0);
dispatch_async(queue,^ {
[abHelper sync];
});
}

AddressBookHelper.m
$ b

   - (void)processSyncConflicts {

if([self.syncConflicts count]> 0){
/ /通过显示UIAlertView来执行操作来警告用户关于同步冲突
UIAlertView * alert;
dispatch_queue_t mainQueue = dispatch_get_main_queue();

dispatch_async(mainQueue,^ {
[alert show];
});

} else {

//同步完成
}
}


解决方案

我在代码中看到几个潜在问题。



多个同步

如果您收到 applicationDidBecomeActive:,但之前 [abHelper sync] 完成,用户切换离开您的应用程序,然后回到您的应用程序。现在您收到另一个 applicationDidBecomeActive:,创建一个新的GCD队列,并开始另一个 [abHelper sync] 仍在运行。 Bad!



更改您的 AddressBookHelper 类以创建自己的GCD队列并将其存储在实例变量中。更改接口,以便同步操作将自身放在该队列上。示例:

  @interface AddressBookHelper:NSObject 

+(AddressBookHelper *)sharedInstance;

- (void)syncInBackground;

@end

@implementation AddressBookHelper {
dispatch_queue_t queue_;
}

- (void)syncInBackground {
if(!queue_){
queue_ = dispatch_queue_create(AddressBookHelper,0);
}
dispatch_async(queue_,^ {[self syncOnCurrentQueue];});
}

- (void)syncOnCurrentQueue {
//这是一个始终在queue_上运行的私有方法。
// ...
}

@end



属性线程安全



我假设你的主线程需要访问 syncConflicts 它在一个属性。如果是这样,您应该更新主队列上的 syncConflicts ,以便您不要在使用它的主队列操作中更改它。

  dispatch_sync(dispatch_get_main_queue(),^ {
self.syncConflicts = syncConflicts;
});



核心数据线程安全



没有告诉我们您如何访问核心数据。您需要知道Core Data对象通常不是线程安全的。您的后台同步操作应创建自己的托管对象上下文,作为主要托管对象上下文的子对象。然后,当操作完成时,它可以告诉子上下文保存,并且更改将以线程安全的方式推送到父上下文。



你会从这个子上下文得到你的对象,你不能将它们传递回主线程。相反,你需要存储每个对象的 objectID ,并使用它来使用主线程的上下文重新创建对象。

   - (void)syncOnCurrentQueue {
//这是一个始终运行在queue_上的私有方法。

NSManagedObjectContext * syncContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
syncContext.parentContext = self.mainContext;

NSArray * people = nil; //从syncContext获取一些人

NSMutableArray * syncConflictIDs = [[NSMutableArray alloc] init];
//开始做一些业务逻辑,迭代数据等等

for(id对象在人){
//处理一些数据
[syncConflictIDs addObject :[object objectID]];
}

NSError * error;
if(![syncContext save:& error]){
//保存到主上下文失败...
}

dispatch_sync(dispatch_get_main_queue ^ {
//假设主上下文有NSMainQueueConcurrencyType
NSMutableArray * syncConflicts = [[NSMutableArray alloc] initWithCapacity:syncConflictIDs.count];
for(NSManagedObjectID * objectID in syncConflictIDs){
[syncConflicts addObject:[self.mainContext objectWithID:objectID]];
}
self.syncConflicts = syncConflicts;
});

//我已经分离了这个方法以保持代码更清晰,并分离方法的逻辑
[self processSyncConflicts];
}



UIKit Thread-Safety



您只能在主线程上操作UIKit对象,例如 UIAlertView 。你实际上没有显示你的 alloc / init 你的警报视图,但我认为你不是强制它在主线程上给出的位置,你声明的变量。您需要确保在主线程上执行此操作。

   - (void)processSyncConflicts {
dispatch_async(dispatch_get_main_queue (),^ {
if([self.syncConflicts count]> 0){
//通过显示UIAlertView来采取行动来提醒用户关于同步冲突
UIAlertView * alert = [[UIAlertView alloc] init ...];
[alert show];
} else {
//同步完成
}
}
}


I have some code that takes a little time to process and thus appropriately it should not run on the main queue. However I'm not sure on how to correctly "structure" the GCD code segments. I.e every time the app becomes active I'm doing a syncing operation:

AppDelegate.m

- (void)applicationDidBecomeActive:(UIApplication *)application {

    AddressBookHelper *abHelper = [AddressBookHelper sharedInstance]; // singleton helper class of NSObject

    [abHelper sync];
}

The syncing code inside AddressBookHelper looks something like this:

AddressBookHelper.m

- (void)sync {

    NSArray *people = // Fetching some people from Core Data

    NSMutableArray *syncConflicts;
    // Start doing some business logic, iterating over data and so on

    for (id object in people) {
    // Process some data
        [syncConflicts addObject:object];
    }

    self.syncConflicts = syncConflicts;

    // I have separated this method to keep the code cleaner and to separate the logic of the methods
    [self processSyncConflicts];
}

- (void)processSyncConflicts {

    if ([self.syncConflicts count] > 0) {
        // Alert the user about the sync conflict by showing a UIAlertView to take action
        UIAlertView *alert;
        [alert show];

    } else {

        // Syncing is complete
    }
}

So with this code structure, how would I properly use GCD to put this code on a background thread?

Is it as easy as doing this?

AppDelegate.m

- (void)applicationDidBecomeActive:(UIApplication *)application {

    AddressBookHelper *abHelper = [AddressBookHelper sharedInstance]; // singleton helper class of NSObject

    dispatch_queue_t queue = dispatch_queue_create("addressbookSyncQueue", 0);
    dispatch_async(queue, ^{
            [abHelper sync];
    });
}

AddressBookHelper.m

- (void)processSyncConflicts {

    if ([self.syncConflicts count] > 0) {
        // Alert the user about the sync conflict by showing a UIAlertView to take action
        UIAlertView *alert;
        dispatch_queue_t mainQueue = dispatch_get_main_queue();

        dispatch_async(mainQueue, ^{
            [alert show];
        });

    } else {

        // Syncing is complete
    }
}

解决方案

I see several potential problems in your code.

Multiple Simultaneous Syncs

Suppose you receive applicationDidBecomeActive:, but before [abHelper sync] finishes, the user switches away from your app, and then back to your app. Now you receive another applicationDidBecomeActive:, create a new GCD queue, and start another [abHelper sync] while the first one is still running. Bad!

Change your AddressBookHelper class to create its own GCD queue and store it in an instance variable. Change the interface so that the sync operation puts itself on that queue. Example:

@interface AddressBookHelper : NSObject

+ (AddressBookHelper *)sharedInstance;

- (void)syncInBackground;

@end

@implementation AddressBookHelper {
    dispatch_queue_t queue_;
}

- (void)syncInBackground {
    if (!queue_) {
        queue_ = dispatch_queue_create("AddressBookHelper", 0);
    }
    dispatch_async(queue_, ^{ [self syncOnCurrentQueue]; });
}

- (void)syncOnCurrentQueue {
    // This is now a private method that always runs on queue_.
    // ...
}

@end

Property Thread-Safety

I assume your main thread needs to access syncConflicts, since you're storing it in a property. If so, you should update syncConflicts on the main queue, so that you don't change it in the middle of some main-queue operation that is using it.

    dispatch_sync(dispatch_get_main_queue(), ^{
        self.syncConflicts = syncConflicts;
    });

Core Data Thread-Safety

You didn't show us how you access Core Data. You need to be aware that Core Data objects are not generally thread-safe. Your background sync operation should create a managed object context of its own, as a child of the main managed object context. Then, when the operation is complete, it can tell the child context to save, and the changes will be pushed to the parent context in a thread-safe way.

Since you'll be getting your people objects from this child context, you can't pass them back to the main thread. Instead, you need to store each object's objectID, and use that to recreate the objects using the main thread's context.

- (void)syncOnCurrentQueue {
    // This is now a private method that always runs on queue_.

    NSManagedObjectContext *syncContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
    syncContext.parentContext = self.mainContext;

    NSArray *people = nil; // Fetching some people from syncContext

    NSMutableArray *syncConflictIDs = [[NSMutableArray alloc] init];
    // Start doing some business logic, iterating over data and so on

    for (id object in people) {
        // Process some data
        [syncConflictIDs addObject:[object objectID]];
    }

    NSError *error;
    if (![syncContext save:&error]) {
        // save to main context failed...
    }

    dispatch_sync(dispatch_get_main_queue(), ^{
        // assuming main context has NSMainQueueConcurrencyType
        NSMutableArray *syncConflicts = [[NSMutableArray alloc] initWithCapacity:syncConflictIDs.count];
        for (NSManagedObjectID *objectID in syncConflictIDs) {
            [syncConflicts addObject:[self.mainContext objectWithID:objectID]];
        }
        self.syncConflicts = syncConflicts;
    });

    // I have separated this method to keep the code cleaner and to separate the logic of the methods
    [self processSyncConflicts];
}

UIKit Thread-Safety

You can only manipulate UIKit objects like UIAlertView on the main thread. You didn't actually show where you alloc/init your alert view, but I assume you're not forcing it to be on the main thread given where you declared the variable. You need to make sure you do it on the main thread.

- (void)processSyncConflicts {
    dispatch_async(dispatch_get_main_queue(), ^{
        if ([self.syncConflicts count] > 0) {
            // Alert the user about the sync conflict by showing a UIAlertView to take action
            UIAlertView *alert = [[UIAlertView alloc] init...];
            [alert show];
        } else {
            // Syncing is complete
        }
    }
}

这篇关于Objective-C - 构造用于后台处理的GCD代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆