I'm creating a turn based game for the iPhone that contains animations between turns, and I want to wait on the [UIView animateWithDuration:...] method call inline in code. Is there a way to make this call synchronously instead of asynchronously? Currently what I am doing is...
// Some code...
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:0];
dispatch_sync(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:1
animations:^{
// some animation
}
completion:^(BOOL finished){
[conditionLock lock];
[conditionLock unlockWithCondition:1];
}];
});
// forces thread to wait until completion block is called
[conditionLock lockWhenCondition:1];
// More code...
Therefore in the above code, "// More code..." is only reached after the animation has completely finished. Obviously this code must run on a secondary thread, and it works as I want to. However, I have a feeling that using an NSConditionLock in combination with gcd is bad form and blocking the secondary thread in this way is not optimal for performance. Is my current code alright, or is there a better way to do this? Thanks.
Edit:
The key point is that "// more code..." is inline, and not in the completion block.
Really what I want to know, is it alright to use NSConditionLock's in combination with GCD, and if not what's the better way?
I would just put "//More code" in another method and call that method in the completion block. This will ensure that your code is only fired once your animation completes.
Related
I am developing an iPhone application where I'm trying to call particular method after certain delay. But the method is not getting called. I have no clue why its not getting called.
Here is my code
-(void)gettingCommentsList { // some stuff....
[self performSelector:#selector(callGetListInBackgroundMethod) withObject:nil afterDelay:1.0]; }
-(void)callGetListInBackgroundMethod {
isFromthread =YES;
NSLog(#"callGetListInBackground");
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Add code here to do background processing
//
//
[self gettingCommentsList];
dispatch_async( dispatch_get_main_queue(), ^{
// Add code here to update the UI/send notifications based on the
// results of the background processing
[self.commentsTbl reloadData];
});
});
}
Thanks
One potential problem is that timers do not prevent threads from exiting before the timer's fired. So if the run loop (of the thread which calls -performSelector:withObject:afterDelay:) has no other source (work), it may not continue running for one second or more and the thread will exit before your timer fires (and your custom work is executed).
That's certainly possible if you schedule the timer on a secondary thread.
I suspect the reason for this design is because timers may be configured as recurring (or not) -- a lot of people would end up with 'zombie' threads because those recurring timers would never be invalidated. That problem could easily chew up a ton of system resources.
Make sure you are not calling [NSObject cancelPreviousPerformRequestsWithTarget:self]
You seem to be calling each method from the other -
In gettingCommentList, you call
[self performSelector:#selector(callGetListInBackgroundMethod) withObject:nil afterDelay:1.0];
and in callGetListInBackgroundMethod in dispatch_async you call
[self gettingCommentList];
Edit:
Try making the call just once and see if it works. If it does, it might be that you're not done with the first call before the second one begins, hence the problem. Let me know what happens.
Edit 2:
I tried your code. The problem is the the subsequent calls to gettingCommentList are in the background thread rather than the main thread. So I did this:
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Add code here to do background processing
//
dispatch_async( dispatch_get_main_queue(), ^{
[self gettingCommentsList];
});
});
and it works. But make sure you don't call the two functions continuously as this would make them run all the time, which I'm sure you don't really want :-)
I have a task which is reading from a disk, potentially going to take quite some time, so don't want to do it in a main thread.. and what I want is to call a function X after reading from the disk. What is the best way to do this in iOS?
So far this is what I've tried:
NSInvocationOperation *processDataOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(readDisk:) object:nil];
[processDataOperation setQueuePriority:NSOperationQueuePriorityVeryHigh];
[processDataOperation setCompletionBlock:^(void){
NSMutableArray *feedItemsArray = [self generateFeedItemsFromDictionary:streamDiskData];
[self postFetchCompletedNotificationForDict:queryStringDict withFeedItems:feedItemsArray isFresh:NO];
}];
basically I am using NSInvocationOperation and then set it's completion block, however the issue is that in my completion block I need the result that is generated in readDisk. How do I access that in the completion block? It's nearly imposible right?
Using NSInvocations it is possible, but far more complicated than necessary, to achieve a trivial amount of work beyond the main thread.
Both GCD and NSOperations can be used to implement a wide array of concurrency strategies. From an object-oriented perspective, NSOperations are more highly abstracted than CGD blocks, which makes them (imo) easier to "design" with, and potentially optimized beyond the scope of where I'm implementing them. GCD is lower-level: This makes interacting with it appear slightly more complicated (it really isn't), but people who are in to that sorta stuff will tell you that it is "more efficient" and carries "less overhead".
My personal approach is to use NSOperations in scenarios where I have a designed/orchestrated concurrency pattern in my application, and use GCD for trivial concurrent/background operations.
If all I need to do is fire some arbitrary task that is not relevant to the design but needs to be done in the background, I'd use CGD. That's what I'd probably use in this case:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[self readDisk];
NSMutableArray *feedItemsArray = [weakSelf generateFeedItemsFromDictionary:streamDiskData];
dispatch_sync(dispatch_get_main_queue(), ^{
//Call back to the main thread before performing/posting anything touching UIKit
[self postFetchCompletedNotificationForDict:queryStringDict withFeedItems:feedItemsArray isFresh:NO];
})
})];
You could always use grand central dispatch to do your operation in the background instead.
Since it is a block you can just call the method normally and store the result. Then grab the main queue if you need to update any UI or do whatever you need to after completion.
dispatch_queue_t queue = dispatch_queue_create("read disc", NULL);
dispatch_async(queue, ^{
result = [self readDisc];
dispatch_async(dispatch_get_main_queue(), ^{
//update UI or do whatever you need to do with the result of readDisc
});
});
dispatch_release(queue);
Suppose you are using an asynchronous block from the ALAssetsLibrary API such as enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop). First off, Since the block is asynchronous, does this mean the system will automatically run the block on a separate thread of execution? If so, what is the best way to know when the block will be completed so I could perform some action like stopping an UIActivityIndicator from spinning or reloading a UITableView. IE like the animateWithDuration block which has a completion block that allows you to perform some action when the animation is complete. What is the pattern for doing something similar here?
A block will usually run on your main thread, the function that you are calling is the asynchronous part. The block is often used to know when the function you called asynchronously is completed. In this particular case, a quick look at the reference material tells us that:
The block to invoke using each asset in turn. When the enumeration is
done, enumerationBlock is invoked with group set to nil.
So in this case just look for that nil!
EDIT:
To check for nil try something like this
ALAssetsLibrary *lib = [ALAssetsLibrary new];
[lib enumerateGroupsWithTypes:ALAssetsGroupAll
usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if (group == nil) {
NSLog(#"Done!");
}
}
failureBlock:^(NSError *error) {
NSLog(#"Failed...");
}
];
And if you want to check if you are currenty on the main thread you can use:
NSLog("On main thread : %d",[NSThread isMainThread] ? 1:0);
In API calls like this the block in run on the main thread because they are not the "heavy lifting" part of the function. They code that could potentially lock up your UI is still done in the background. In this particular case, its more that the function is "coming back up for air" and asking you what you want done for the given group, and then it goes back in the background.
I have a toolkit that I need to work with (to interface with a remote service). This toolkit queries the remote service and asks for results. It does this asynchronously, which in most cases is good, but not for creating concise methods. I want to make methods similar to the following:
-(NSArray *)getAllAccounts {
NSString *query = #"SELECT name FROM Account";
//Sets "result" to the query response if no errors.
//queryResult:error:context: is called when the data is received
[myToolkit query:query target:self selector:#selector(queryResult:error:context:) context:nil];
//Wait?
return result.records;
}
The problem is, inside the toolkit the methods call each other using #selector, not direct calls, so getting return values is difficult. Further, the actual query uses:
NSURLConnection *connection = [[[NSURLConnection alloc] initWithRequest:aRequest delegate:self] autorelease];
Which is asynchronous. By the time the data has been received from the service, my method has long ago returned... without the information. So my question is this: Is there a way to pause execution until the data has been returned? Could I accomplish this using a second thread to get the data while the main thread rests (or using 3 threads so the main thread doesn't rest?)
I don't want to edit the toolkit to change their method (or add a new one) to be synchronous, so is there a way to make a method as I want?
You might want to consider NOT making it all synchronous, especially if the sample code in your post is run on your main application thread. If you do that, the main thread will block the UI and the application will cease to respond until the remote transaction is complete.
Therefore, if you really insist on the synchronous approach, then you should definitely do it in a background thread so that the UI does not become unresponsive, which can actually lead to your App getting killed by the OS on iphone.
To do the work in a background thread, I would strongly recommend using the Grand Central Dispatch stuff, namely NSBlockOperation. It will free you from having to actually create and manage threads and makes your code pretty neat.
To do the synchronous thing, take a look at the NSCondition class documentation. You could do something like the following:
NSCondition* condition = ...;
bool finished = NO;
-(NSArray *)getAllAccounts {
[condition lock];
NSString *query = #"SELECT name FROM Account";
//Sets "result" to the query response if no errors.
//queryResult:error:context: is called when the data is received
[myToolkit query:query target:self selector:#selector(queryResult:error:context:) context:nil];
while (!finished)
[condition wait];
[condition unlock];
return result.records;
}
Then in the method called by the toolkit to provide the results you'd do:
- (void) queryResult:error:context: {
// Deal with results
[condition lock]
finished = YES;
[condition signal];
[condition unlock];
}
You'd probably want to encapsulate the "condition" and "finished" variables in your class declaration.
Hope this helps.
UPDATE: Here is some code to offload the work to a background thread:
NSOperationQueue* queue = [NSOperationQueue new];
[queue addOperationWithBlock:^{
// Invoke getAllAccounts method
}];
Of course, you can keep the queue around for later use and move the actual queuing of the work to inside your method call to make things neater.
The way to wait is to return from your current code. Finish up doing what you want done after the wait, in the asynchronous callback method you specify. What's so difficult about that?
Any synchronous waits in the main UI thread will block the UI and make the user think your app has locked up, which is likely far worse than your thinking the code isn't concise enough.
I use the block base API for my animations on iOS.
One animation has a completion block and that block is called at the end of the animation, nice.
However, that animation can be fired multiple times when the user scrolls (the animation is on a UITableViewCell). When that happens the the completion block is called multiple times. The finished parameter of the block is always YES.
Since the animation is not actually finished (an other animation took place) I thought that the finished parameter would be NO, but it's not.
Did I miss something? How can I avoid the completion block to be called multiple times?
The completion block is called multiple times simply because, in your case, your animation is fired multiple times. What is happening is that iOS invokes your animation block each time it is told so, probably in a separate thread. Then, for each animation it tracks its completion, and upon completion it calls the associated completion block. So basically, you see your completion block firing multiple times, one for each invocation of your animation. Note that the boolean value associated to a completion block is specific of that completion block, it does not refer in any way to a different animation.
To recap, what you are experiencing is simply the effect of concurrency. If this is not your intended behavior, then you need to modify your code accordingly. If you want your animations to fire one at a time, you may use NSLock (NSConditionLock for advanced control using an associate condition variable) or, if you prefer, a mutex and the Posix pthreads library directly, to create a critical section to be executed in a mutually exclusive fashion.
Not sure when you're firing the animations and whether they loop (like a UIActivityView spinner or something) - sounds like it's every single pixel the table view is scrolling?
In any event, perhaps you could use the UIScrollView delegate methods and tell each cell to start animation on scrollViewWillBeginDragging: and tell each cell to stop at scrollViewDidEndDragging:
You could set a boolean isAnimating for your UITableViewCell and if an animation is currently underway, do nothing.
if (isAnimating) {
// ... do nothing
} else {
// Start your animation
}
Or stick with whatever you have now and use a boolean still, but only fire the animation if it's not currently animating. Then in your finished parameter just set isAnimating to NO.
if (isAnimating) {
// ... do nothing
} else {
[UIView animateWithDuration:0.3f
animations:^{
// animations...
isAnimating = YES;
}
completion:^{
isAnimating = NO;
}
];
}
I've resolved this issue by looking if the completion block is relevant at the beginning of that block.
The finished parameter is not relevant right now. I've communicated with Apple and they told me that it's fixed in iOS 4.2.