问题
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
}
}
回答1:
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
}
}
}
来源:https://stackoverflow.com/questions/13712299/objective-c-structuring-gcd-code-for-background-processing