Perform block inside a NSOperation - objective-c

I have a method in some class which performs some task using a block. When I execute that method using NSInvocationOperation then control never goes to the block. I tried logging inside the block but that is never called actually. But if I simply call that method with instance of that class then everything works as expected.
Don’t blocks run inside NSOperation?
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:myClassObj selector:#selector(myClassMethod:) object:obj1];
[[AppDelegate sharedOpQueue] addOperation:op];
[op release];
- (void)myClassMethod:(id)obj
{
AnotherClass *otherClass = [[AnotherClass allco] init]
[otherClass fetchXMLWithCompletionHandler:^(WACloudURLRequest* request, xmlDocPtr doc, NSError* error)
{
if(error){
if([_delegate respondsToSelector:#selector(handleFail:)]){
[_delegate handleFail:error];
}
return;
}
if([_delegate respondsToSelector:#selector(doSomeAction)]){
[_delegate doSomeAction];
}
}];
}
- (void) fetchXMLWithCompletionHandler:(WAFetchXMLHandler)block
{
_xmlBlock = [block copy];
[NSURLConnection connectionWithRequest:request delegate:self];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if(_xmlBlock) {
const char *baseURL = NULL;
const char *encoding = NULL;
xmlDocPtr doc = xmlReadMemory([_data bytes], (int)[_data length], baseURL, encoding, (XML_PARSE_NOCDATA | XML_PARSE_NOBLANKS));
NSError* error = [WAXMLHelper checkForError:doc];
if(error){
_xmlBlock(self, nil, error);
} else {
_xmlBlock(self, doc, nil);
}
xmlFreeDoc(doc);
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
if(_xmlBlock) {
_xmlBlock(self, nil, error);
}
}

You are performing your NSConnection asynchronously (which you don't need to do in an NSOperation because you should already be on a background thread).
After your call to fetchXMLWithCompletionHandler, your method ends. This signals that the NSOperation is finished and it gets released and it's thread gets either reused for something else or, more likely, released as well. This means that by the time you get your callbacks, your initial object doesn't exist anymore!
There are two solutions :
1) Use NSURLConnection synchronously. This will wait in your myClassMethod until it has got a response.
2) Learn about NSOperations's concurrent mode. I don't know if this will work with NSInvocationOperation though :( And it's fairly complicated compared to option (1).
I would use method (1) - you have already created a background thread to perform your operation in, why bother creating another one to do your connection request?

There are two ways of fixing your problem:
The easy way out
is — as Dean suggests — using +[NSURLConnection sendSynchronousRequest:returningResponse:error:], as you already are on a different thread. This has you covered — I'd say — 80-90% of the time, is really simple to implement and Just Works™.
The other way
is only slightly more complicated and has you covered for all the cases where the first method does not suffice — by visiting the root of your problem:
NSURLConnection works in conjunction with the runloop — and the threads managed by NSOperationQueue don't necessarily use (or even have!) an associated runloop.
While calling +[NSURLConnection connectionWithRequest:delegate:] will implicitly create a runloop, if needed, it does not cause the runloop to actually run!
This is your responsibility, when the NSOperationQueue you use is not the queue associated with the main thread.
To do so, change your implementation of fetchXMLWithCompletionHandler: to look similar to the following:
- (void)fetchXMLWithCompletionHandler:(WAFetchXMLHandler)block
{
self.xmlHandler = block; // Declare a #property for the block with the copy attribute set
self.mutableXMLData = [NSMutableData data]; // again, you should have a property for this...
self.currentConnection = [NSURLConnection connectionWithRequest:request delegate:self]; // having a #property for the connection allows you to cancel it, if needed.
self.connectionShouldBeRunning = YES; // ...and have a BOOL like this one, setting it to NO in connectionDidFinishLoad: and connection:didFailWithError:
NSRunLoop *loop = [NSRunLoop currentRunLoop];
NSDate *neverExpire = [NSDate distantFuture];
BOOL runLoopDidIterateRegularly = YES;
while( self.connectionShouldBeRunning && runLoopDidIterateRegularly ) {
runLoopDidIterateRegularly = [loop runMode:NSDefaultRunLoopMode beforeDate:neverExpire];
}
}
With these small changes, you're good to go. Bonus: this is really flexible and (eventually) reusable throughout all your code — if you move the XML-parsing out of that class and make your handler simply take an NSData, an NSError and (optionally) an NSURLResponse.
Since you probably don't want the clients of your loader to see and possibly mess with the properties I just suggested you should add, you can declare them in a class continuation.

Related

EXC_BAD_ACCESS upon block execution

I have a class with a single method, that uses a URLConnection to send a serialized NSDictionary to a script at a certain URL, and then calls a completion block. Here is the code for that method:
- (void)sendDictionary:(NSDictionary *)dictionary toScript:(NSString *)scriptName completion:(void (^) (id response))completionBlock
{
...Serialize data and add it to an NSURLRequest request...
H2URLConnection *connection = [[H2URLConnection alloc]initWithRequest:request];
//Define a semaphore to block execution of later statements until the signal is received.
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[connection setCompletionBlock:[^(id obj, NSError *err)
{
if (!err) {
//Catch the server response
NSString *receivedString = [[NSString alloc] initWithData:obj encoding:NSUTF8StringEncoding];
NSLog( #"ChecklistAppNetworkManager received string: %#", receivedString);
//Convert the JSON response into an NSDictionary
NSError *otherError;
id deserializedJSON = [NSJSONSerialization JSONObjectWithData:obj options:kNilOptions error:&otherError];
if (otherError) {
NSLog(#"ChecklistAppNetworkManager JSON Error: %#", otherError.description);
}
[completionBlock invoke];
NSLog(#"ChecklistAppNetworkManager JSON Response: %#", deserializedJSON);
//Dispatch the semaphore signal so that the main thread continues.
dispatch_semaphore_signal(sem);
} else {
NSLog(#"ChecklistAppNetworkManager encountered an error connecting to the server: %#", [err description]);
}
}copy]];
//Finalize and initate the connection.
[connection start];
//Since block is dispatched to main queue, stall with a loop until the semaphore signal arrives.
while (dispatch_semaphore_wait(sem, DISPATCH_TIME_NOW)) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]];
}
}
I'm trying to call this method on an instance of this class from within another class, where the completion block is defined. Here's the code where I get the EXC_BAD_ACCESS:
- (void)doSomeServerTask
{
H2ChecklistAppNetworkManager *currentNetworkManager = ((H2AppDelegate *)[[UIApplication sharedApplication]delegate]).networkManager; //Instantiate class where that method is defined
NSMutableDictionary *dictonary = [NSMutableDictionary dictionary];
...populate dictionary...
[currentNetworkManager sendDictionary:dictionary toScript:#"script.php" completion:[^(id response)
{ //THIS iS THE LINE WHERE THE BAD ACCESS OCCURS
NSLog(#"LoginViewController received response: %#", response);
} copy]];
}
Any help would be appreciated!
The completionBlock on that method takes one argument, but you call the block with the invoke method. More likely than not, the crash is because the runtime is trying to retain whatever garbage is in memory that should be that argument.
However, you really need to refactor this code entirely. Blocking the main event loop is bad. Running a sub-runloop is even worse on the MEL; it changes the way dispatch queue handling semantics work and can lead to pathologically bad performance or behavior.
You should move to a truly asynchronous model. If the app can't proceed until these queries are done, then put up a modal indicator that blocks progress.
To do that, you structure the code loosely as:
• put user interface into a "loading..." or some other modal state
• execute an asynchronous request for data with a completion handler
• in the completion handler, dispatch the "update UI" request to the main queue
• upon "update UI", tear down your modal "loading...." UI and update the display for the user
There is no need to block the main event loop to do any of this.

What makes a completion handler execute the block when your task of interest is complete?

I have been asking and trying to understand how completion handlers work. Ive used quite a few and I've read many tutorials. i will post the one I use here, but I want to be able to create my own without using someone else's code as a reference.
I understand this completion handler where this caller method:
-(void)viewDidLoad{
[newSimpleCounter countToTenThousandAndReturnCompletionBLock:^(BOOL completed){
if(completed){
NSLog(#"Ten Thousands Counts Finished");
}
}];
}
and then in the called method:
-(void)countToTenThousandAndReturnCompletionBLock:(void (^)(BOOL))completed{
int x = 1;
while (x < 10001) {
NSLog(#"%i", x);
x++;
}
completed(YES);
}
Then I sorta came up with this one based on many SO posts:
- (void)viewDidLoad{
[self.spinner startAnimating];
[SantiappsHelper fetchUsersWithCompletionHandler:^(NSArray *users) {
self.usersArray = users;
[self.tableView reloadData];
}];
}
which will reload the tableview with the received data users after calling this method:
typedef void (^Handler)(NSArray *users);
+(void)fetchUsersWithCompletionHandler:(Handler)handler {
NSURL *url = [NSURL URLWithString:#"http://www.somewebservice.com"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
[request setHTTPMethod: #"GET"];
**// We dispatch a queue to the background to execute the synchronous NSURLRequest**
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// Perform the request
NSURLResponse *response;
NSError *error = nil;
NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error) { **// If an error returns, log it, otherwise log the response**
// Deal with your error
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
NSLog(#"HTTP Error: %d %#", httpResponse.statusCode, error);
return;
}
NSLog(#"Error %#", error);
return;
}
**// So this line won't get processed until the response from the server is returned?**
NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
NSArray *usersArray = [[NSArray alloc] init];
usersArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];
// Finally when a response is received and this line is reached, handler refers to the block passed into this called method...so it dispatches back to the main queue and returns the usersArray
if (handler){
dispatch_sync(dispatch_get_main_queue(), ^{
handler(usersArray);
});
}
});
}
I can see it in the counter example, that the called method (with the passed block) will never exit the loop until it is done. Thus the 'completion' part actually depends on the code inside the called method, not the block passed into it?
In this case the 'completion' part depends on the fact that the call to NSURLRequest is synchronous. What if it was asynchronous? How would I be able to hold off calling the block until my data was populated by the NSURLResponse?
Your first example is correct and complete and the best way to understand completion blocks. There is no further magic to them. They do not automatically get executed ever. They are executed when some piece of code calls them.
As you note, in the latter example, it is easy to call the completion block at the right time because everything is synchronous. If it were asynchronous, then you need to store the block in an instance variable, and call it when the asynchronous operation completed. It is up to you to arrange to be informed when the operation completes (possibly using its completion handler).
Do be careful when you store a block in an ivar. One of your examples includes:
self.usersArray = users;
The call to self will cause the block to retain self (the calling object). This can easily create a retain loop. Typically, you need to take a weak reference to self like this:
- (void)viewDidLoad{
[self.spinner startAnimating];
__weak typeof(self) weakSelf = self;
[SantiappsHelper fetchUsersWithCompletionHandler:^(NSArray *users) {
typeof(self) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf setUsersArray:users];
[[strongSelf tableView] reloadData];
}
}];
}
This is a fairly pedantic version of the weakSelf/strongSelf pattern, and it could be done a little simpler in this case, but it demonstrates all the pieces you might need. You take a weak reference to self so that you don't create a retain loop. Then, in the completely block, you take a strong reference so that self so that it can't vanish on you in the middle of your block. Then you make sure that self actually still exists, and only then proceed. (Since messaging nil is legal, you could have skipped the strongSelf step in this particular case, and it would be the same.)
Your first example (countToTenThousandAndReturnCompletionBLock) is actually a synchronous method. A completion handler doesn't make much sense here: Alternatively, you could call that block immediately after the hypothetical method countToTenThousand (which is basically the same, just without the completion handler).
Your second example fetchUsersWithCompletionHandler: is an asynchronous method. However, it's actually quite suboptimal:
It should somehow signal the call-site that the request may have failed. That is, either provide an additional parameter to the completion handler, e.g. " NSError* error or us a single parameter id result. In the first case, either error or array is not nil, and in the second case, the single parameter result can be either an error object (is kind of NSError) or the actual result (is kind of NSArray).
In case your request fails, you miss to signal the error to the call-site.
There are code smells:
As a matter of fact, the underlying network code implemented by the system is asynchronous. However, the utilized convenient class method sendSynchronousRequest: is synchronous. That means, as an implementation detail of sendSynchronousRequest:, the calling thread is blocked until after the result of the network response is available. And this_blocking_ occupies a whole thread just for waiting. Creating a thread is quite costly, and just for this purpose is a waste. This is the first code smell. Yes, just using the convenient class method sendSynchronousRequest: is by itself bad programming praxis!
Then in your code, you make this synchronous request again asynchronous through dispatching it to a queue.
So, you are better off using an asynchronous method (e.g. sendAsynchronous...) for the network request, which presumable signals the completion via a completion handler. This completion handler then may invoke your completion handler parameter, taking care of whether you got an actual result or an error.

How can i implement the promise pattern with ReactiveCocoa?

I am new to iOS development coming from a JS background with EmberJS. I want to port my EmberJS App to an iOS App. Therefore i would like to use similiar structures in my iOS App. As EmberJS makes heavy use of promises i searched for something similar for iOS and stumbled upon ReactiveCocoa. It is said in the introduction of ReactiveCocoa that this framework can be used to implement Promises. I tried it but it does not work properly. I wanted to start with a quite simple example:
Make an asynchronous network request (to fill a UITableViewController). Return a promise from this method.
Subscribe to this promise and reload the TableView when it is finished.
I want to do it this way, because i will have to perform several things after the data has been loaded successfully. My approach works basically but i am experiencing the following issues:
My TableView does not reload immediately after the request has been finished.
I am seeing the Log Statements in my subscribeCompleted immediately after the request finished. But the TableView stays blank.
The TableView loads the data after a few seconds of waiting.
If i start scrolling the TableView after i have seen the Log output, the TableView is suddenly loaded.
I suspect this may happen because i am fetching the data in a background thread. I think the resolve of the promise (subscribeCompleted) may happen in the background thread too and Cocoa Touch may not like this. Am i right? But if this is the case, how am i supposed implement a promise?
I hope you can help me getting started with ReactiveCocoa. Thx! :-)
UPDATE:
I managed to fix it by wrapping the to reloadData in a dispatch_async(dispatch_get_main_queue(), ^{... But still i am not sure wether this is the best way to go or what is recommended by ReactiveCocoa. So i am still keen on hearing some answers :-)
// this method wants to use the promise
- (void) loadDataAndPerformActionsAfterwards{
RACSignal *signal = [self fetchObjects];
[signal subscribeCompleted:^{
NSLog(#"Entered subscribeCompleted block signal!");
NSLog(#"Number of objects: %i", self.objects.count);
[self.tableView reloadData];
}];
}
// this method returns a promise. I omitted some parts but it shows basically how i go about resolving the promise.
- (RACSignal*) fetchMoviesForCurrentFormState{
return [RACSignal createSignal:^RACDisposable*(id<RACSubscriber> subscriber) {
NSLog(#"RAC createSignal Block called");
NSString *requestURL = #"...";
NSURL *urlObj = [NSURL URLWithString: requestURL];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData* data = [NSData dataWithContentsOfURL: urlObj];
if(data){
[self performSelectorOnMainThread:#selector(fetchedData:)
withObject:data waitUntilDone:YES];
[subscriber sendCompleted];
}else{
// Not implemented yet: handle the error case
[subscriber sendCompleted];
}
});
// actually i do not know yet what i should return here. Copied from a basic example.
return nil;
}];
}
You're right that this is an issue with threading. However, you don't need to drop down to the level of GCD.
Signals can be "delivered" onto another thread, which just invokes any subscription callbacks there:
- (void) loadDataAndPerformActionsAfterwards {
[[[self
fetchObjects]
deliverOn:RACScheduler.mainThreadScheduler]
subscribeCompleted:^{
NSLog(#"Entered subscribeCompleted block signal!");
NSLog(#"Number of objects: %i", self.objects.count);
[self.tableView reloadData];
}];
}
You may take a look into RXPromise. It's an Objective-C implementation of the Promises/A+ specification with a couple more features. (I'm the author).
A solution utilizing the RXPromise library would look as follows:
- (void) loadDataAndPerformActionsAfterwards {
[self fetchMovie]
.thenOn(dispatch_queue_get_main(), ^id(id fetchedMovie) {
self.model = fetchedObjects;
[self.tableView reloadData];
}, nil);
}
This assumes, method fetchMovie returns a Promise.
How do you get this? Well, you can easily wrap any asynchronous method or operation into one that returns a Promise. This works for any signal approach: completion blocks, callback functions, delegates, KVO, Notification, etc.
For example, a simplified implementation for NSURLConnection's async convenience class method (in practice you should check the response and do better error handling):
- (RXPromise*) fetchMovie {
RXPromise* promise = [[RXPromise alloc] init];
NSMutableRequest* request = ...;
[NSURLConnection sendAsynchronousRequest:request
queue:networkQueue
completionHandler:^(NSURLResponse* response, NSData* data, NSError* error){
if (error) {
[promise rejectWithReason:error];
}
else {
[promise fulfillWithValue:data];
}
}];
return promise;
}
You might want to use an approach using the NSURLConnection delegates, or an approach utilizing a NSOperation subclass. This enables you to implement cancellation:
- (RXPromise*) fetchObjects {
RXPromise* promise = [[RXPromise alloc] init];
NSMutableRequest* request = ...;
HTTPOperation* op =
[[HTTPOperation alloc] initWithRequest:request
queue:networkQueue
completionHandler:^(NSURLResponse* response, NSData* data, NSError* error){
if (error) {
[promise rejectWithReason:error];
}
else {
[promise fulfillWithValue:data];
}
}];
promise.then(nil, ^id(NSError* error){
[op cancel];
return nil;
});
[op start];
return promise;
}
Here, the HTTPOperation object will listen to its own promise for an error signal. If it receives one, for example a cancel message send from another object to the promise, the handler then "forwards" the cancel message to the operation.
A View Controller for example can now cancel a running HTTPOperation as follows:
- (void) viewWillDisappear:(BOOL)animate {
[super viewWillDisappear:animate];
[self.fetchObjectsPromise cancel];
self.fetchObjectPromise = nil;
}

Get data out of block iOS (Objective-C)

So,here a simple method with a block
-(void)getPointsInRange:(double)radius nearByPoint:(SGPoint *)nearByPoint
{
SGStorageQuery *query = [SGStorageQuery queryWithPoint:nearByPoint layer:SimpleGeoMainLayerName];
[query setRadius:radius];
[mainClient retain];
[mainClient getRecordsForQuery:query
callback:[SGCallback callbackWithSuccessBlock:
^(id response) {
// you've got records!
// to create an array of SGStoredRecord objects...
NSArray *records = [NSArray arrayWithSGCollection:response type:SGCollectionTypeRecords];
NSLog(#"records received:%i",[records count]);
[self arrayOfPointsReceived:records];
} failureBlock:^(NSError *error) {
// handle failure
NSLog(#"getPointsInRange error:%#",[error description]);
}]];
}
the method connects to some SDK and returns an NSArray with results.
i want to find a way that the getPointsInRange method will return the NSArray.
so its signature will be -(NSArray*)getPointsInRange...
I can do it simply with delegate, but i'd like to do it all within one function.
It seems to me like you want to keep your cake and eat it, too. Or have a method that calls asynchronous code and at the same time returns the results synchronously. You can turn the method into a synchronous one, if that’s what you want:
- (void) computeSomethingAndReturnSynchronously
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self doSomeAsynchronousOperationWithCompletion:^{
// take the call results here
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
}
This will run the asynchronous code and then block the execution until the results from the async call are available. Does that help? (I should add that I would much rather keep the code asynchronous and return the NSArray in another completion block.)

How do I wait for an asynchronously dispatched block to finish?

I am testing some code that does asynchronous processing using Grand Central Dispatch. The testing code looks like this:
[object runSomeLongOperationAndDo:^{
STAssert…
}];
The tests have to wait for the operation to finish. My current solution looks like this:
__block BOOL finished = NO;
[object runSomeLongOperationAndDo:^{
STAssert…
finished = YES;
}];
while (!finished);
Which looks a bit crude, do you know a better way? I could expose the queue and then block by calling dispatch_sync:
[object runSomeLongOperationAndDo:^{
STAssert…
}];
dispatch_sync(object.queue, ^{});
…but that’s maybe exposing too much on the object.
Trying to use a dispatch_semaphore. It should look something like this:
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[object runSomeLongOperationAndDo:^{
STAssert…
dispatch_semaphore_signal(sema);
}];
if (![NSThread isMainThread]) {
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
} else {
while (dispatch_semaphore_wait(sema, DISPATCH_TIME_NOW)) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0]];
}
}
This should behave correctly even if runSomeLongOperationAndDo: decides that the operation isn't actually long enough to merit threading and runs synchronously instead.
In addition to the semaphore technique covered exhaustively in other answers, we can now use XCTest in Xcode 6 to perform asynchronous tests via XCTestExpectation. This eliminates the need for semaphores when testing asynchronous code. For example:
- (void)testDataTask
{
XCTestExpectation *expectation = [self expectationWithDescription:#"asynchronous request"];
NSURL *url = [NSURL URLWithString:#"http://www.apple.com"];
NSURLSessionTask *task = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
XCTAssertNil(error, #"dataTaskWithURL error %#", error);
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse *) response statusCode];
XCTAssertEqual(statusCode, 200, #"status code was not 200; was %d", statusCode);
}
XCTAssert(data, #"data nil");
// do additional tests on the contents of the `data` object here, if you want
// when all done, Fulfill the expectation
[expectation fulfill];
}];
[task resume];
[self waitForExpectationsWithTimeout:10.0 handler:nil];
}
For the sake of future readers, while the dispatch semaphore technique is a wonderful technique when absolutely needed, I must confess that I see too many new developers, unfamiliar with good asynchronous programming patterns, gravitate too quickly to semaphores as a general mechanism for making asynchronous routines behave synchronously. Worse I've seen many of them use this semaphore technique from the main queue (and we should never block the main queue in production apps).
I know this isn't the case here (when this question was posted, there wasn't a nice tool like XCTestExpectation; also, in these testing suites, we must ensure the test does not finish until the asynchronous call is done). This is one of those rare situations where the semaphore technique for blocking the main thread might be necessary.
So with my apologies to the author of this original question, for whom the semaphore technique is sound, I write this warning to all of those new developers who see this semaphore technique and consider applying it in their code as a general approach for dealing with asynchronous methods: Be forewarned that nine times out of ten, the semaphore technique is not the best approach when encounting asynchronous operations. Instead, familiarize yourself with completion block/closure patterns, as well as delegate-protocol patterns and notifications. These are often much better ways of dealing with asynchronous tasks, rather than using semaphores to make them behave synchronously. Usually there are good reasons that asynchronous tasks were designed to behave asynchronously, so use the right asynchronous pattern rather than trying to make them behave synchronously.
I’ve recently come to this issue again and wrote the following category on NSObject:
#implementation NSObject (Testing)
- (void) performSelector: (SEL) selector
withBlockingCallback: (dispatch_block_t) block
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self performSelector:selector withObject:^{
if (block) block();
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
}
#end
This way I can easily turn asynchronous call with a callback into a synchronous one in tests:
[testedObject performSelector:#selector(longAsyncOpWithCallback:)
withBlockingCallback:^{
STAssert…
}];
Generally don't use any of these answers, they often won't scale (there's exceptions here and there, sure)
These approaches are incompatible with how GCD is intended to work and will end up either causing deadlocks and/or killing the battery by nonstop polling.
In other words, rearrange your code so that there is no synchronous waiting for a result, but instead deal with a result being notified of change of state (eg callbacks/delegate protocols, being available, going away, errors, etc.). (These can be refactored into blocks if you don't like callback hell.) Because this is how to expose real behavior to the rest of the app than hide it behind a false façade.
Instead, use NSNotificationCenter, define a custom delegate protocol with callbacks for your class. And if you don't like mucking with delegate callbacks all over, wrap them into a concrete proxy class that implements the custom protocol and saves the various block in properties. Probably also provide convenience constructors as well.
The initial work is slightly more but it will reduce the number of awful race-conditions and battery-murdering polling in the long-run.
(Don't ask for an example, because it's trivial and we had to invest the time to learn objective-c basics too.)
Here's a nifty trick that doesn't use a semaphore:
dispatch_queue_t serialQ = dispatch_queue_create("serialQ", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQ, ^
{
[object doSomething];
});
dispatch_sync(serialQ, ^{ });
What you do is wait using dispatch_sync with an empty block to Synchronously wait on a serial dispatch queue until the A-Synchronous block has completed.
- (void)performAndWait:(void (^)(dispatch_semaphore_t semaphore))perform;
{
NSParameterAssert(perform);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
perform(semaphore);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
}
Example usage:
[self performAndWait:^(dispatch_semaphore_t semaphore) {
[self someLongOperationWithSuccess:^{
dispatch_semaphore_signal(semaphore);
}];
}];
There’s also SenTestingKitAsync that lets you write code like this:
- (void)testAdditionAsync {
[Calculator add:2 to:2 block^(int result) {
STAssertEquals(result, 4, nil);
STSuccess();
}];
STFailAfter(2.0, #"Timeout");
}
(See objc.io article for details.) And since Xcode 6 there’s an AsynchronousTesting category on XCTest that lets you write code like this:
XCTestExpectation *somethingHappened = [self expectationWithDescription:#"something happened"];
[testedObject doSomethigAsyncWithCompletion:^(BOOL succeeded, NSError *error) {
[somethingHappened fulfill];
}];
[self waitForExpectationsWithTimeout:1 handler:NULL];
Here is an alternative from one of my tests:
__block BOOL success;
NSCondition *completed = NSCondition.new;
[completed lock];
STAssertNoThrow([self.client asyncSomethingWithCompletionHandler:^(id value) {
success = value != nil;
[completed lock];
[completed signal];
[completed unlock];
}], nil);
[completed waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
[completed unlock];
STAssertTrue(success, nil);
Swift 4:
Use synchronousRemoteObjectProxyWithErrorHandler instead of remoteObjectProxy when creating the remote object. No more need for a semaphore.
Below example will return the version received from the proxy. Without the synchronousRemoteObjectProxyWithErrorHandler it will crash (trying to access non accessible memory):
func getVersion(xpc: NSXPCConnection) -> String
{
var version = ""
if let helper = xpc.synchronousRemoteObjectProxyWithErrorHandler({ error in NSLog(error.localizedDescription) }) as? HelperProtocol
{
helper.getVersion(reply: {
installedVersion in
print("Helper: Installed Version => \(installedVersion)")
version = installedVersion
})
}
return version
}
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[object blockToExecute:^{
// ... your code to execute
dispatch_semaphore_signal(sema);
}];
while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) {
[[NSRunLoop currentRunLoop]
runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0]];
}
This did it for me.
Sometimes, Timeout loops are also helpful. May you wait until you get some (may be BOOL) signal from async callback method, but what if no response ever, and you want to break out of that loop?
Here below is solution, mostly answered above, but with an addition of Timeout.
#define CONNECTION_TIMEOUT_SECONDS 10.0
#define CONNECTION_CHECK_INTERVAL 1
NSTimer * timer;
BOOL timeout;
CCSensorRead * sensorRead ;
- (void)testSensorReadConnection
{
[self startTimeoutTimer];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
while (dispatch_semaphore_wait(sema, DISPATCH_TIME_NOW)) {
/* Either you get some signal from async callback or timeout, whichever occurs first will break the loop */
if (sensorRead.isConnected || timeout)
dispatch_semaphore_signal(sema);
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate dateWithTimeIntervalSinceNow:CONNECTION_CHECK_INTERVAL]];
};
[self stopTimeoutTimer];
if (timeout)
NSLog(#"No Sensor device found in %f seconds", CONNECTION_TIMEOUT_SECONDS);
}
-(void) startTimeoutTimer {
timeout = NO;
[timer invalidate];
timer = [NSTimer timerWithTimeInterval:CONNECTION_TIMEOUT_SECONDS target:self selector:#selector(connectionTimeout) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
-(void) stopTimeoutTimer {
[timer invalidate];
timer = nil;
}
-(void) connectionTimeout {
timeout = YES;
[self stopTimeoutTimer];
}
Very primitive solution to the problem:
void (^nextOperationAfterLongOperationBlock)(void) = ^{
};
[object runSomeLongOperationAndDo:^{
STAssert…
nextOperationAfterLongOperationBlock();
}];
I have to wait until a UIWebView is loaded before running my method, I was able to get this working by performing UIWebView ready checks on main thread using GCD in combination with semaphore methods mentioned in this thread. Final code looks like this:
-(void)myMethod {
if (![self isWebViewLoaded]) {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block BOOL isWebViewLoaded = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (!isWebViewLoaded) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((0.0) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
isWebViewLoaded = [self isWebViewLoaded];
});
[NSThread sleepForTimeInterval:0.1];//check again if it's loaded every 0.1s
}
dispatch_sync(dispatch_get_main_queue(), ^{
dispatch_semaphore_signal(semaphore);
});
});
while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0]];
}
}
}
//Run rest of method here after web view is loaded
}