NSOperationQueue block updates UI before previous task finishes - ios

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?

Related

MBProcessHUD show/hide when using semaphore

Hi I m using dispatch_group_t for async web call and during that wait period I want to show activity indicator.
Here is my code which is not working for show/hide activity indicator.
- (BaseResponse*)getResponse:(BaseRequest*)request{
request.header = [[Header alloc]init];
request.header.osType = 2;
NSString *jsonBody = [request toJSONString];
NSURL *requestURL = #"myurl";
__block BaseResponse *baseResponse;
[self showHUDProcessView]; //here activity indicator should start animating but not working as per expected.
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[[COMHandler sharedCOMHandler] makeServiceCall:jsonBody requestURL_:requestURL completion:^(ComResponse *comResponse){
[self hideHUDProcessView]; //hide activity when receive response
if (comResponse != nil) {
baseResponse = comResponse;
}else{
//show alert
}
dispatch_group_leave(group);
}];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
return baseResponse;
}
- (void) showHUDProcessView {
[MBProgressHUD showHUDAddedTo:viewController.view animated:YES];
}
- (void) hideHUDProcessView {
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:viewController.view animated:YES];
});
});
}
I also tried with starting group in block but also not work..
Tried
dispatch_semaphore_t activity = dispatch_semaphore_create(0);
//show indicator
^myblock {
//hide indicator
dispatch_semaphore_signal(activity);
}
dispatch_semaphore_wait(activity, DISPATCH_TIME_FOREVER);
also but no luck.
Can anybody suggest me when i m doing wrong??
Or want else I can do to achieve this indicator show/hide issue.

Is it safe to call XCTestExpectation fulfill method on background thread?

I am using XCTestExpectation in a lot of tests and sometimes (very randomly) some expectations are not fulfilled (although I am sure they should be).
While investigating this problem I have noticed that some expectations are fulfilled in a main thread and some are fulfilled in a background thread. And so far these problems are with the ones fulfilled in a background thread.
Is it safe to fulfill expectations from a background thread? I could not find any explicit information about that.
Below is an example of how I use XCTestExpectation:
__block XCTestExpectation *expectation = [self expectationWithDescription:#"test"];
[self doSomethingAsyncInBackgroundWithSuccess:^{
[expectation fullfill];
}];
[self waitForExpectationsWithTimeout:10.0 handler:^(NSError *error) {
expectation = nil;
if (error) {
NSLog(#"Timeout Error: %#", error);
}
}];
It's not documented anywhere that XCTestExpectation is thread-safe. due to there being no official documentation on the matter you can only guess by creating test examples:
- (void)testExpectationMainThread;
{
__block XCTestExpectation *expectation = [self expectationWithDescription:#"test"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
NSLog(#"%#", error);
}];
}
- (void)testExpectationStartMainThreadFulfilBackgroundThread;
{
__block XCTestExpectation *expectation = [self expectationWithDescription:#"test"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, kNilOptions), ^{
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
NSLog(#"%#", error);
}];
}
- (void)testExpectationBackgroundThread;
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, kNilOptions);
__block XCTestExpectation *expectation;
dispatch_sync(queue, ^{
expectation = [self expectationWithDescription:#"test"];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), queue, ^{
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
NSLog(#"%#", error);
}];
}
Here it does not crash or cause a problem however due to the lack of official documentation it is probably safer to stick to the same queue to fulfil.
you should really be stubbing the method doSomethingAsyncInBackgroundWithSuccess and provide the app with local "dummy" data.
Your unit tests should not rely on network as it is something which is variable.
You should be executing the completion block of doSomethingAsyncInBackgroundWithSuccess on the main thread (or at least provide a way to call back consistently on the same thread), you can easily do this with GCD.
- (void)doSomethingAsyncInBackgroundWithSuccess:(void (^)(void))completion;
{
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
or use NSOperationQueue mainQueue
- (void)doSomethingAsyncInBackgroundWithSuccess:(void (^)(void))completion;
{
[NSOperationQueue.mainQueue addOperationWithBlock:^{
completion();
}];
}

Execute cross thread function one by one

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"];
}

Creating block queues with completion

I want to create 2 async queues with completion blocks and after finished this blocks I want to run some action. I can not achieve it with this code. Where my bad?
dispatch_queue_t queue = dispatch_queue_create("com.company.queue", 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// block 1
dispatch_group_async(group, queue, ^{
[[WebRequests sharedInstance] request:#{#"type" : [NSNumber numberWithInt:request_uploadAdv], #"adv" : adv} withCompletion:^(id response) {
BOOL success = [response boolValue];
NSLog(#"done1 text");
// block 1 Done
}];
});
// block 2 //картинки
dispatch_group_async(group, queue, ^{
[self getImagesForAdv:adv completion:^(NSArray *images) {
[[WebRequests sharedInstance] uploadPhotos:images completion:^(BOOL success) {
uploadImagesSuccess = YES;
NSLog(#"done1 2\n");
// block 2 Done
}];
}];
});
dispatch_group_notify(group, queue, ^{
printf("all tasks are finished!\n");
});
First, you're missing a }); in there somewhere. Second, there's no need for the outer dispatch_group_async call anyway. Assuming it's there because you want these things to execute with background priority, you can do this instead:
dispatch_queue_t queue = dispatch_queue_create("com.company.queue", 0);
dispatch_set_target_queue(queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
dispatch_group_t group = dispatch_group_create();
// block 1
dispatch_group_async(group, queue, ^{
[[WebRequests sharedInstance] request:#{#"type" : [NSNumber numberWithInt:request_uploadAdv], #"adv" : adv} withCompletion:^(id response) {
BOOL success = [response boolValue];
NSLog(#"done1 text");
// block 1 Done
}];
});
// block 2 //картинки
dispatch_group_async(group, queue, ^{
[self getImagesForAdv:adv completion:^(NSArray *images) {
[[WebRequests sharedInstance] uploadPhotos:images completion:^(BOOL success) {
uploadImagesSuccess = YES;
NSLog(#"done1 2\n");
// block 2 Done
}];
}];
});
dispatch_group_notify(group, queue, ^{
printf("all tasks are finished!\n");
});

order of operations with addOperationWithBlock

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

Resources