I have three methods, two of them run at the same time. And the third method should be started only when the first and second method together complete their work. Either the first or second method, competitors, can finish their work first.
- (void)method1 {
//DO Long Work
isMethod1Complete = YES;
[self method3];
}
- (void)method2 {
//DO Long Work
isMethod2Complete = YES;
[self method3];
}
- (void)method3 {
if (isMethod1Complete && isMethod2Complete) {
//DO Work once
}
}
Method 3 should always be called once. But the problem is that there is a situation that method1 and method2 have finished working at the same time, and method3 is called twice. Tell me how to solve this problem in objective c for iOS?
Update:A concrete example, I have two services that call delegates when they finish their work.
- (void)method1Handler {
isMethod1Complete = YES;
[self method3];
}
- (void)method2Handler {
isMethod1Complete = YES;
[self method3];
}
How can this be solved without blocks?
For blocks, Rob's example is the best.
You say:
I have three methods, two of them run at the same time.
That means that they must be asynchronous or running on background queues (otherwise there's no way for them to run at the same time).
So, the idea is that you should give them both completion handlers (which will be called when they're done):
- (void)method1WithCompletion:(void(^ _Nonnull)(void))completion {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
//DO Long Work asynchronously
completion();
});
}
- (void)method2WithCompletion:(void(^ _Nonnull)(void))completion {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
//DO Long Work asynchronously
completion();
});
}
- (void)method3 {
// final task
}
In the above example, I added explicit dispatch_async calls to a background queue to ensure that the two long tasks run asynchronously. But if the code is already doing something asynchronous (e.g. a network request), then you will likely not need these dispatch_async calls, but just put the completion() call inside the completion handler provided by whatever API you are already using. But without more information about what method1 and method2 are doing, I cannot be more specific.
But, setting that aside, once your method1 and method2 have their own completion handlers, you can use dispatch_group_notify to identify what should be done when all of the dispatch_group_enter calls are balanced by their corresponding dispatch_group_leave calls:
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self method1WithCompletion:^{
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[self method2WithCompletion:^{
dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[self method3];
});
In subsequent comments, you mentioned that you are not using a completion block-based API, but rather a delegate-protocol-based API. You have a few options, for example:
You can use the same above closure pattern, but just save the completion handlers as block properties, e.g.:
For example, define block properties:
#property (nonatomic, copy, nullable) void (^completionOne)(void);
#property (nonatomic, copy, nullable) void (^completionTwo)(void);
Then, your method1 and method2 would save these blocks:
- (void)method1WithCompletion:(void(^ _Nonnull)(void))completion {
self.completionOne = completion;
// start your time consuming asynchronous process
}
// and your completion delegate method can then call the saved closure
// and then remove it
- (void)method1DidComplete {
self.completionOne();
self.completionOne = nil;
}
- (void)method2WithCompletion:(void(^ _Nonnull)(void))completion {
self.completionTwo = completion;
// start second asynchronous process
}
// same as above
- (void)method2DidComplete {
self.completionTwo();
self.completionTwo = nil;
}
The delegate-protocol completion API would then just call the saved block properties (and probably reset them to nil to free the memory associated with those blocks).
Then you can use the dispatch group notify process as shown in my original answer, above.
Alternatively, rather than using blocks, you can just use dispatch group by itself. For example, define dispatch group property:
#property (nonatomic, strong, nullable) dispatch_group_t group;
Then, you create your group and start your two tasks:
self.group = dispatch_group_create();
[self method1];
[self method2];
dispatch_group_notify(self.group, dispatch_get_main_queue(), ^{
[self method3];
});
And, the two methods then dispatch_group_enter when you start the tasks and dispatch_group_leave in their respective completion handler delegate methods:
- (void)method1 {
dispatch_group_enter(self.group);
// start first asynchronous process
}
// in your delegate completion method, you "leave" the group
- (void)method1DidComplete {
dispatch_group_leave(self.group);
}
- (void)method2 {
dispatch_group_enter(self.group);
// start second asynchronous process
}
- (void)method2DidComplete {
dispatch_group_leave(self.group);
}
- (void)method3 {
// you might as well remove the group now that you're done with it
self.group = nil;
// final task
NSLog(#"doing three");
}
Personally, I would generally lean towards the first option (that way, the dispatch group stuff is contained in a single method), but either approach works.
Why not dispatching the "call" to method3 in a serial queue?
dispatch_queue_t notSimQ;
notSimQ = dispatch_queue_create("notSimQ", NULL);
- (void)method1 {
//DO Long Work
isMethod1Complete = YES;
dispatch_async( notSimQ, // or sync
^{
[self method3];
});
}
- (void)method2 { … } // similiasr
- (void)method3 { … } // unchanged
The calls to method3 are never in competition.
- (void)method1 {
//DO Long Work
isMethod2Complete = YES;
dispatch_async(dispatch_get_main_queue(), ^(void){
[self method3];
}
}
- (void)method2 { … }
- (void)method3 { … }
- (void)viewDidLoad {
[super viewDidLoad];
[self method1];
[self method2];
}
- (void)method1 {
//DO Long Work
isMethod1Complete = YES;
}
- (void)method2 {
//DO Long Work
isMethod2Complete = YES;
dispatch_async(dispatch_get_main_queue(), ^(void){
[self method3];
}
}
- (void)method3 {
if (isMethod1Complete && isMethod2Complete) {
//DO Work once
}
}
Current status
I have created a custom NSOperation object and I want to update some data when it is cancelled.
I've followed what said in this answer and I didn't override the cancel method.
Here is my header:
// MyOperation.h
#interface MyOperation : NSOperation {
}
#property (nonatomic, retain) OtherDataClass *dataClass;
#end
And the implementation
// MyOperation.m
#implementation MyOperation
#synthesize dataClass;
- (void)main {
if ([self isCancelled]) {
[self.dataClass setStatusCanceled];
NSLog(#"Operation cancelled");
}
// Do some work here
NSLog(#"Working... working....")
[self.dataClass setStatusFinished];
NSLog(#"Operation finished");
}
#end
The question
I have several operations in a queue. I was expecting that, when I call cancelAllOperations in the queue, I'll get the "Operation cancelled" text in the log and the status updated in my other class but it is not working. The main method is not being called for the operations in the queue.
Why is this happening and how can I solve it?
Notes
I've tried to overwrite the cancel method with this:
- (void)cancel {
[super cancel];
[self.dataClass setStatusCanceled];
NSLog(#"Operation cancelled");
}
It is working but I've read that this method should not be overridden.
When you call cancelAllOperations, the operations which are already started will have isCancelled set to YES. operations which aren't already started won't start.
It seems that the function call [self updateUI]; blocked by boo.
Is boo run in another background thread or same as foo as the code below?
How can the [self updateUI]; not block by boo?
- (void)MainFunction
{
[self performSelectorInBackground#selector(foo) withObject:nil];
}
- (void)foo
{
[self performSelectorInBackground#selector(boo) withObject:nil];
//updaate UI in MainThread
[self updateUI];
}
- (void)boo
{
//function here take long time to run;
}
In your code seems that you call foo in background and so the UI is updated in the background thread that is not possible because you need to do that in the main thread. In any case, performSelectorInBackground is a little bit old...use the dispatcher in this way:
- (void)MainFunction
{
[self foo];
}
- (void)foo
{
dispatch_async(dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAUL, 0ull), ^{
[self boo];
dispatch_async(dispatch_get_main_queue(), ^{
//updaate UI in MainThread
[self updateUI];
};
};
}
- (void)boo
{
//function here take long time to run;
}
In this case updateUI wait boo, but if you want updateUI before and doesn't matter when boo finish:
- (void)foo
{
dispatch_async(dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAUL, 0ull), ^{
[self boo];
};
[self updateUI];
}
performSelectorInBackground performs the selector on a NEW thread. From Apple docs:
This method creates a new thread in your application, putting your
application into multithreaded mode if it was not already. The method
represented by aSelector must set up the thread environment just as
you would for any other new thread in your program.
If you would like to perform both functions on the SAME background thread, you'll have to declare the background thread (also called queue) as a private member of the class (so it will be accessible from both functions) and perform the selector on that queue
I've gotten in a few cases when something receives multiple refresh calls in quick succession, eg:
- ViewController receives multiple KVO notifications.
- Datamanger class that is called from setters to refresh when multiple settings change.
Ideally I would like to execute only the last refresh call from a series (drop all the intermediate ones).
Right now I'm using an isRefreshing property and a needRefresh to block excessive refreshes, eg:
- (id)init {
...
[self observeValueForKeyPath:#"isRefreshing" ....];
}
- (void)setParameter:(NSInteger)parameter {
....
[self refresh];
}
/* and many more kinds of updates require a refresh */
- (void)setAnotherProperty:(NSArray*)array {
....
[self refresh];
}
- (void)refresh {
if (self.isRefreshing) {
self.needRefresh = YES;
return;
}
self.isRefreshing = YES;
...
self.isRefreshing = NO;
}
- observeValueForKeyPath..... {
if (!self.isRefreshing && self.needsRefresh) {
self.needsRefresh = NO;
[self refresh];
}
}
Is there a better solution for this kind of problem?
You can create a NSOperationQueue with concurrency set to one and only submit a new operation to it when its operation count is zero. (Or use cancellation logic to remove pending jobs so that only one new one is queued if there's a job in progress.)
What you're doing is reasonable for a single-threaded system but would become fairly complicated for multiple threads.
Looks like you should delay refreshing for a while.
You can use different techniques to do so. It is enough only one flag.
For example you may use async block to make a delay for a one main run-loop cycle
- (void)setParameter:(NSInteger)parameter {
....
[self requestRefrhesh];
}
- (void)setAnotherProperty:(NSArray*)array {
....
[self requestRefrhesh];
}
...
-(void) requestRefrhesh {
if (self.refreshRequested) {
return;
} else {
self.refreshRequested = YES;
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run in main UI thread
//make your UI changes here
self.refreshRequested = NO;
});
}
}
I've got class:
ClassX.m
#property (assign) BOOL wasProcessed;
-(void) methodA { //<- this can be called many times in short period of time
dispatch_async(dispatch_get_main_queue(), ^{
[self methodB];
});
}
- (void) methodB {
if (!self.wasProcessed) {
self.wasProcessed = YES;
//... some code
}
}
Since dispatch_async is used so a few calls to methodB can be processed concurrently at the same time and following code needs to be atomic:
if (!self.wasProcessed) {
self.wasProcessed = YES; //e.g two calls can enter here before setting YES and it would be bad because I want to process it only one time
How can those 2 lines be made atomic (checking and setting variable)? I dont want to make atomic code that is after "self.wasProcessed = YES;" so moving whole if to #synchronize(self) won't be good solution. If there is anything wrong with my thinking please point it out as I'm not very experienced in those topics, Thank you.
Try #synchronized. While the enclosed code is being executed on a thread, it will block other threads from executing it.
- (void) methodB {
#synchronized(self) {
if (!self.wasProcessed) {
self.wasProcessed = YES;
//... some code
}
}
}
-(void) methodA {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^(){
[self methodB];
}];
});
}
Your's methodB will be only called in main thread, so it will be never performed simultaneously.