I am facing some weird results with addOperationWithBlock.
My function looks something like this:
-(void) myFunction{
NSLog(#"VISITED");
..
for (NSDictionary *Obj in myObjects)
{
[operationQueue addOperationWithBlock:^(void){
MyObject* tmp = [self tediousFunction:Obj];
// threadTempObjects is member NSMutableArray
[self.threadTempObjects addObject:tmp];
NSLog(#"ADDED");
}];
}
[operationQueue addOperationWithBlock:^(void){
[self.myArray addObjectsFromArray:self.threadTempObjects];
for(myObject *myObj in self.myArray)
{
// MAIN_QUEUE
[[NSOperationQueue mainQueue] addOperationWithBlock:^(void) {
[self updateUI:myObj];
}];
}
}];
[operationQueue addOperationWithBlock:^(void){
[[NSOperationQueue mainQueue] addOperationWithBlock:^(void) {
[self filterResults];
}];
}];
}
My dictionary contains 4 values, and therefore the ADDED shows in the log 4 times.
BUT,
when I check inside the filterResults, I see that there are only 2 objects inside myArray. Meaning that the 4 times the operationQueue was called did not end before the filterResults operation was called (although it was added later!)
I thought that the operationQueue is serial and that I can count on it that when I add an operation it would be added right after the last operation.
So it is weird that only 2 operations are in the array in the aftermath.
What am I missing? Thanks
From what you shared as your initialisation code we can learn that operationQueue is not serial, meaning it will execute operations, and allocate thread up until the system set maximal thread count (same as with GCD).
This mean that operations added to operationQueue are running in parallel.
To run them serially set the maxConcurrentOperationCount to 1.
Try something like:
__block __weak id weakSelf = self;
[operationQueue setMaxConcurrentOperationCount:1];
for (NSDictionary *Obj in myObjects)
{
[operationQueue addOperationWithBlock:^{
MyObject* tmp = [weakSelf tediousFunction:Obj];
// threadTempObjects is member NSMutableArray
[weakSelf.threadTempObjects addObject:tmp];
NSLog(#"ADDED");
}];
}
[operationQueue addOperationWithBlock:^{
[weakSelf.myArray addObjectsFromArray:weakSelf.threadTempObjects];
for(myObject *myObj in weakSelf.myArray)
{
// MAIN_QUEUE
[[NSOperationQueue mainQueue] addOperationWithBlock:^(void) {
[weakSelf updateUI:myObj];
}];
[[NSOperationQueue mainQueue] addOperationWithBlock:^(void) {
[weakSelf filterResults];
}];
}
}];
But, this is equal (or even less efficient) to simply:
__block __weak id weakSelf = self;
[operationQueue addOperationWithBlock:^{
for (NSDictionary *Obj in myObjects) {
MyObject* tmp = [weakSelf tediousFunction:Obj];
// threadTempObjects is member NSMutableArray
[[NSOperationQueue mainQueue] addOperationWithBlock:^(void) {
[weakSelf updateUI:tmp];
}];
[weakSelf.myArray addObject:tmp];
NSLog(#"ADDED");
}
[[NSOperationQueue mainQueue] addOperationWithBlock:^(void) {
[weakSelf filterResults];
}];
}];
Related
In the following code, how would you avoid nested blocks increasing the retain count of 'self'.
This is how I avoid nested blocks
-(void)openSession {
[self.loginManager logInWithReadPermissions:#[#"user_photos"]
fromViewController:[self.datasource mediaAccountViewControllerForRequestingOpenSession:self]
handler:[self loginHandler]];
}
-(void(^)(FBSDKLoginManagerLoginResult *result, NSError *error))loginHandler {
__weak typeof (self) weakSelf = self;
return ^ (FBSDKLoginManagerLoginResult *result, NSError *error) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (error) {
[strongSelf.delegate mediaAccount:strongSelf failedOpeningSessionWithError:error];
} else if (result.isCancelled) {
[strongSelf.delegate mediaAccountSessionOpeningCancelledByUser:strongSelf];
} else {
[strongSelf.delegate mediaAccountDidOpenSession:strongSelf];
}
[strongSelf notifyWithCompletion:[strongSelf completionHandler]]
};
}
-(void)notifyWithCompletion:(void(^)(void))completion {
[self notify];
completion();
}
-(void(^)(void))completionHandler {
return ^ {
//do something
};
}
But how do you avoid many nested blocks, which is often the case when you use GCD within a block ?
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self parseLoadsOfData];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUI];
});
});
Are there retain cycles here ?
__weak typeof(self) *weakSelfOuter = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
__strong typeof(self) *strongInnerSelf1 = weakSelfOuter;
[strongInnerSelf1 parseLoadsOfData];
__weak typeof(self) *weakInnerSelf = strongInnerSelf1;
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(self) *strongInnerSelf2 = weakInnerSelf;
[strongInnerSelf2 updateUI];
});
});
I have some special functions in my project, they will execute cross many threads, such as childContext perform block or AFNetwork response block:
(void)my_function {
dispatch_async(dispatch_get_main_queue(), ^{
NSManagedObjectContext *childContext = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType] autorelease];
childContext.parentContext = self.managedObjectContext;
[childContext performBlock:^{
[self.operationManager POST:URL parameters:nil block:^(AFHTTPRequestOperation *operation, id responseObject) {
//Do something
[childContext performBlock:^{
//Do something
}];
}];
}];
});
}
Now I want to execute them one by one (including all blocks in function). After reading a couple of other answers, some of the Apple documentation, I get some answers:
1. NSRecursiveLock
I can add NSRecursiveLock for each function, but the issue is that I can't lock/unlock cross thread.
(void)my_function {
[MyLock lock]; // Lock here
dispatch_async(dispatch_get_main_queue(), ^{
NSManagedObjectContext *childContext = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType] autorelease];
childContext.parentContext = self.managedObjectContext;
[childContext performBlock:^{
[self.operationManager POST:URL parameters:nil block:^(AFHTTPRequestOperation *operation, id responseObject) {
//Do something
[childContext performBlock:^{
//Do something
[MyLock unlock]; //Unlock here
}];
}];
}];
});
}
2. NSOperation
I can add each function to NSOperationQueue as a NSOperation, and set concurrent operation number to 1, but the issue is that I can't make sure the code in block has been executed even the NSOperation finishes successfully.
3. #synchronized
I can add #synchronized, but has same issue as NSOperation
My suggestion is using NSOperationQueue and Asynchronous NSOperation.
You can make sure operation execute one by one in two ways
addDependency:
Set maxConcurrentOperationCount to 1
With Asynchronous NSOperation,it is you to decide when this Operation is done.
For example
-(void)my_function {
dispatch_async(dispatch_get_main_queue(), ^{
NSManagedObjectContext *childContext = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType] autorelease];
childContext.parentContext = self.managedObjectContext;
[childContext performBlock:^{
[self.operationManager POST:URL parameters:nil block:^(AFHTTPRequestOperation *operation, id responseObject) {
//Do something
[childContext performBlock:^{
//Do something
//Only mark this NSOperation is done when all task of this function is finished.
}];
}];
}];
});
}
How to make an Asynchronous NSOperation,This is what I usually do,you can also refer it in the document.
Keep two property
#interface AsyncOperation (){
BOOL finished;
BOOL executing;
}
Use these two property to manage Operation state
-(BOOL)isFinished{
return finished;
}
-(BOOL)isExecuting{
return executing;
}
3.Mark Operation start
Note:start function is called on the thread the Operation created
-(void)start{
if ([self isCancelled])
{
// Must move the operation to the finished state if it is canceled.
[self willChangeValueForKey:#"isFinished"];
finished = NO;
[self didChangeValueForKey:#"isFinished"];
return;
}
// If the operation is not canceled, begin executing the task.
[self willChangeValueForKey:#"isExecuting"];
[NSThread detachNewThreadSelector:#selector(main) toTarget:self withObject:nil];
executing = YES;
[self didChangeValueForKey:#"isExecuting"];
}
Mark Operation is done
I use this function
-(void)setOperationFinished{
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
I need to complete following tasks in order and update UI after all the task are completed.
Since data is depended on each other I have created NSOperationQueue *myQueue but one of the tasks taking so long to complete meanwhile last task is not waiting for previous task to finish and just updating the UI
Task order should be
1.Show Loading hub
2.Parse html get ids
3.Download Json with the parsed ids
4.populate database with json
4.1 This part also has a lot of background work populating database takes about 5-6 seconds.
5.Hide loading hub
So 5 is executed before 4 and 4.1 finishes....
basically I want this Serial but this is happening I guess Concurrent
I have also tried 'dispatch_sync' no luck
-(void)viewDidAppear:(BOOL)animated
{
//show hud
[self performSelectorOnMainThread:#selector(showLoadingHud) withObject:self waitUntilDone:YES];
[self startProcess];
}
-(void)startProcess
{
NSOperationQueue *myQueue = [NSOperationQueue mainQueue];
[myQueue setMaxConcurrentOperationCount:1];
__block NSMutableArray *imdbIDs=[[NSMutableArray alloc] init];
NSMutableArray *urlList=[[NSMutableArray alloc] initWithArray:[ImdbURLs getImdburlArray]];
NSBlockOperation *getiImdbViaHtmlParser = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakHtmlParser = getiImdbViaHtmlParser;
[weakHtmlParser addExecutionBlock:^{
//first
for (NSString *imdbUrl in urlList) {
...
}];
NSBlockOperation *getJsonFromIMDB = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakGetJsonFromIMDB = getJsonFromIMDB ;
[weakGetJsonFromIMDB addExecutionBlock:^{
//second
for (NSString *imdbStrings in imdbIDs) {
//this also calls database methods and populates database with safe threads
[self AFReqTest:imdbStrings];//I guess this causing the problem
}
}];
NSBlockOperation *checkResult = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakCheckResult = checkResult ;
[weakCheckResult addExecutionBlock:^{
//dismiss hud
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self dismissLoadingHud];//this is getting called before getJsonFromIMDB block finishes
}];
}];
[checkResult addDependency:getJsonFromIMDB];
[getJsonFromIMDB addDependency:getiImdbViaHtmlParser];
[myQueue addOperation:getJsonFromIMDB];
[myQueue addOperation:getiImdbViaHtmlParser];
[myQueue addOperation:checkResult];
}
-(void)AFReqTest:(NSString *)idString
{
...//buch of code
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//here I call another background method with result
[self insertIntoDatabase:responseObject];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
-(void)insertIntoDatabase:(NSDictionary *)responseObject
{
ParseImdbResponse *parse=[[ParseImdbResponse alloc] initWithResponseDic:responseObject];
PopulateDatabase *inserInstance=[[PopulateDatabase alloc] initWithResponseDic:parse];
dispatch_queue_t insertqueue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(insertqueue, ^{
[inserInstance inserIntoMoviesTable];
});
dispatch_sync(insertqueue, ^{
[inserInstance inserIntoGenresTable];
});
dispatch_sync(insertqueue, ^{
[inserInstance inserIntoActorsTable];
});
dispatch_sync(insertqueue, ^{
[inserInstance inserIntoDirectorsTable];
});
dispatch_sync(insertqueue, ^{
[inserInstance inserIntoMovieGenresTable];
});
dispatch_sync(insertqueue, ^{
[inserInstance inserIntoMovieCastTable];
});
}
Sorry for the long code I am not sure problem is my code structure or am I missing something?
I'm trying to fetch JSON data from 5 different URLs. The network requests can be performed in parallel, though the responses have to be processed in a certain order. In addition, I also want to have a single point of error handling logic.
The code I'm having right now is like the following. The problem is, only the subscription of signalFive and signalSix has been invoked. The subscribeNext block for all the other signals has never been invoked. I suspect the problem is because the subscription happens after the sendNext occurs.
Is there a better/standard way to perform this kind of request?
- (RACSubject *)signalForFetchingFromRemotePath:(NSString *)remotePath
{
RACSubject *signal = [RACSubject subject];
[self.requestOperationManager GET:remotePath parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
for (id obj in responseObject) {
[signal sendNext:obj];
}
[signal sendCompleted];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[signal sendError:error];
}];
return signal;
}
FMDatabase *db = [SomeDatabase defaultDatabase];
[db beginTransaction];
RACSubject *singalOne = [self signalForFetchingFromRemotePath:[self urlStringWithPath:SYNC_ONE_PATH]];
RACSubject *singalTwo = [self signalForFetchingFromRemotePath:[self urlStringWithPath:SYNC_TWO_PATH]];
RACSubject *singalThree = [self signalForFetchingFromRemotePath:[self urlStringWithPath:SYNC_THREE_PATH]];
RACSubject *singalFour = [self signalForFetchingFromRemotePath:[self urlStringWithPath:SYNC_FOUR_PATH]];
RACSubject *singalFive = [self signalForFetchingFromRemotePath:[self urlStringWithPath:SYNC_FIVE_PATH]];
RACSubject *singalSix = [self signalForFetchingFromRemotePath:[self urlStringWithPath:SYNC_SIX_PATH]];
RACSignal *combined = [RACSignal merge:#[singalOne, singalTwo, singalThree, singalFour, singalFive, singalSix]];
[combined subscribeError:^(NSError *error){
[db rollback];
}];
[singalFive subscribeNext:^(NSDictionary *dict) {
[ClassE save:dict];
} completed:^{
[singalSix subscribeNext:^(NSDictionary *dict) {
[ClassF save:dict];
} completed:^{
[singalOne subscribeNext:^(NSDictionary *dict){
[ClassA save:dict];
} completed:^{
[singalTwo subscribeNext:^(NSDictionary *dict){
[ClassB save:dict];
} completed:^{
[singalThree subscribeNext:^(NSDictionary *dict) {
[ClassC save:dict];
} completed:^{
[singalFour subscribeNext:^(NSDictionary *dict){
[ClassD save:dict];
} completed:^{
NSLog(#"Completed");
[db commit];
}];
}];
}];
}];
}];
}];
If you need to enforce a specific order, use +concat: instead of +merge:.
On its own, concatenation means that the requests will not be performed in parallel. If you want to recover that behavior, you can use -replay on each signal (to start it immediately) before passing it to +concat:.
As an aside, nested subscriptions are almost always an anti-pattern. There's usually a built-in operator to do what you want instead.
I usually use combineLatest:
NSArray *signals = #[singalOne, singalTwo, singalThree, singalFour, singalFive, singalSix];
[[RACSignal combineLatest:signals] subscribeNext:^(RACTuple *values) {
// All your values are here
} error:^(NSError *error) {
// error
}];
This question already has answers here:
NSOperationQueue serial FIFO queue
(3 answers)
Closed 9 years ago.
I'm having trouble understanding the way NSOperationQueue works.
Say I have:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount=1;
[queue addOperationWithBlock:^{
[someObject someSelector];
}];
[queue addOperationWithBlock:^{
[someObject anotherSelector];
}];
The second block is being called even before the first block finishes - the opposite of what I want. I tried using – performSelectorOnMainThread:withObject:waitUntilDone: instead, but the second block is still being executed first - presumably because the block thread is not being completed on the main thread, and so it is not blocked with waitUntilDone. I added a break point inside my someSelector block, and it is reached after a break point inside the second block.
I don't quite get it. Help me!!
If there are explicit dependencies between the operations, then use addDependency:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount=1;
NSOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
[someObject someSelector];
}];
NSOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
[someObject anotherSelector];
}];
[operation2 addDependency:operation1];
[queue addOperation:operation1];
[queue addOperation:operation2];
If your operations are doing asynchronous activity, then you should define a custom operation, and only call completeOperation (which will post the isFinished message) when the asynchronous task is done).
// SomeOperation.h
#import <Foundation/Foundation.h>
#interface SomeOperation : NSOperation
#end
and
// SomeOperation.m
#import "SomeOperation.h"
#interface SomeOperation ()
#property (nonatomic, readwrite, getter = isFinished) BOOL finished;
#property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
#end
#implementation SomeOperation
#synthesize finished = _finished;
#synthesize executing = _executing;
#pragma Configure basic operation
- (id)init
{
self = [super init];
if (self) {
_finished = NO;
_executing = NO;
}
return self;
}
- (void)start
{
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[self main];
}
- (void)completeOperation
{
self.executing = NO;
self.finished = YES;
}
- (void)main
{
// start some asynchronous operation
// when it's done, call `completeOperation`
}
#pragma mark - Standard NSOperation methods
- (BOOL)isConcurrent
{
return YES;
}
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
#end
Thus, with the following code, it won't start operation2 until the asynchronous task initiated in main in SomeOperation object, operation1, calls its completeOperation method.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount=1;
NSOperation *operation1 = [[SomeOperation alloc] init];
NSOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
[someObject anotherSelector];
}];
[operation2 addDependency:operation1];
[queue addOperation:operation1];
[queue addOperation:operation2];