Detecting if a method is being called from a block - objective-c

I'm using AFNetworking's AFHTTPClient class to communicate with a Rails backend.
When creating objects, I want to run several API calls on the server using a batch API (I'm using batch_api in case you were wondering).
To nicely extend AFHTTPClient I was thinking of having an API that would look like this:
[[[SPRRailsClient] sharedInstance] batchOperations:^ {
NSMutableURLRequest *request = [[SPRRailsAPIClient sharedClient]
requestWithMethod:#"GET"
path:myPath
parameters:parameters];
AFHTTPRequestOperation *operation = [[SPRRailsAPIClient sharedClient]
HTTPRequestOperationWithRequest:request
success:nil
failure:nil];
}];
The trick would be to override SPRRailsClient (my AFHTTPClient subclass) so when requestWithMethod:path:parameters: and HTTPRequestOperationWithMethod:success:failure: are called within a batchOperations block if behaves in a different way (queues things, or reruns a different subclass of AFOperation.
The neat thing of this design is that it would allow me to keep existing code and only wrap it within the block in order for some calls to be executed in "batch mode".
My question is: how can I detect that a method is being called from a block? I need requestWithMethod:path:parameters: to detect that, and:
If called from a batchOperations block, behave in a different way.
If not called from a batchOperations block just call super.
I know it would be simpler to just add two additional methods to SPRRailsClient, but I thought this looked better.
Also, I figure it's possible, since some methods of UIView animations behave in a different way when called from within an animation block, and NSManangedObjectContext's performBlock: is probably doing something similar as well.

The only way I can think of would be to enqueue the batchOpperations blocks on a named queue, then check the queue name when executing the blocks. This would look something like:
In the top of your .m file, or in your method, either works.
static dispatch_queue_t myQueue;
Then within batchOpperations,
if (!myQueue){
myQueue = dispatch_queue_create("com.mydomain.myapp.myqueue", NULL);
}
dispatch_async(myQueue, batchOpperation);
Then within when your sharedClient methods are called, check the queue:
if ([[NSString stringWithCString:dispatch_queue_get_label(dispatch_get_current_queue()) encoding:NSUTF8StringEncoding] isEqualToString:#"com..."]){
something....
}
else{
[super ??];
}

Related

Convert synchronous to async Objective-C

I’m working in a new codebase and I don’t have many people who understand it, so I’m hoping I can get some help. I am updating an interface and some of the synchronous methods are now async which is making it difficult to fit into the current architecture for resolving data.
Currently we have a function map which stores these synchronous methods, then when we want the data we do “call” which executes the block/method and returns the value.
Some code below shows how it currently is.
fnMap[#“vid”] = [[Callback alloc] initWithBlock:^id(id param) {
return #([services getVisitorID]);
}];
… later, to resolve the data
id fnMapVal = [fnMap[key] call:nil];
Here is how a callback and callback block are defined.
typedef id (^CallbackBlock)(id);
#interface Callback : NSObject
#property(copy, nonatomic, readonly) CallbackBlock block;
- (instancetype)initWithBlock:(CallbackBlock)block
- (id)call:(id)param
{
return self.block(param);
}
Now the service needs to call an async method to get the ID so I had to change it to:
- (void)getVisitorID: (nullable void (^) (NSString* __nullable visitorIdentifier)) callback
{
[SDK getUserIdentifier:^(NSString * _Nullable userIdentifier) {
callback(userIdentifier);
}];
}
So the call is:
[services getVisitorID:^(NSString * _Nullable visitorIdentifier) {
}];
I haven’t been able to find a way to fit this into the current architecture. Some options I’ve explored is using a run loop to wait for the async method to finish and keep my interface synchronous but this sounds like a bad idea. I’m for some suggestions on how to fit this in as I’ve never seen something like this before.
You need to use dispatch_queues or NSOperationQueue to run your code off the main thread. Dispatch queues are very low level and good to just fire off async tasks:
// we're going to run the getVisitorID method on a background queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[services getVisitorID:^(NSString * _Nullable visitorIdentifier) {
dispatch_async(dispatch_get_main_queue(), ^(void){
// update the user interface on the main queue
});
}];
});
I prefer using NSOperationQueue because the API is cleaner, and they allow you to do more advanced things like make asynchronous task cancellable:
// create a background queue
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
[services getVisitorID:^(NSString * _Nullable visitorIdentifier) {
// get the main queue and add your UI update code to it
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// update the UI from here
}];
}];
}];
Queues are much easier to manage than messing around with run loops. For more info see here: https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html
What you need is the Future/Promise concept. Please don't reinvent the wheel, you will have to cover marathon distance trying to achieve the same functionality of requesting value and asynchronously consuming it.
As one of the favourites please consider PromiseKit. However don't be shy to explore the alternatives
You will quickly find it super delightful building chains of async tasks, mapping their results to different values, combining futures together to get a tuple of multiple resolved values once all are available - all that in a concise, well designed form, live tested in millions of applications.

Objective-c simple polling

I'm new to objective-c and I'm building an app that requires a background polling to a generic API to refresh some data on my user interface.
After several hours looking for an answer/example that fits my problem I came across some solutions like the following:
long polling in objective-C
polling an external server from an app when it is launched
Poll to TCP server every hour ios
http://blog.sortedbits.com/async-downloading-of-data/
but unfortunately none of them covers my scenario which is really basic:
I need to start a polling when viewDidLoad, let's say an infinite loop, and on every iteration, let's say every 10 seconds, to call an API and when I didReceiveData I want to log that data into the console with an NSLog, obviously this can't be done on the main thread.
What I really need is a very simple example on how to do that, and with simple I mean:
I can't implement long polling/push notifications for many reasons and not looking for a way to do it, lets' just assume I cannot.
I don't want to rely on fancy frameworks like LRResty, RESTKit, AFNetworking or anything else since I don't really need them and also I can't believe that there is no SDK bulletin that can cover this basic scenario.
I don't need anything else besides what I described, so no authentication, no parameters in my request, no response handling etc. (since I'm new to objective-c and stuff not strictly needed could just be more confusing to me...)
The solution I'm looking for could be something like this (using NSOperationQueue to run my loop into a separate thread):
- (void)viewDidLoad {
[super viewDidLoad];
//To run polling on a separate thread
operationQueue = [NSOperationQueue new];
NSInvocationOperation *operation=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(doPolling) object:nil];
[operationQueue addOperation:operation];
}
-(void)doPolling {
MyDao *myDao = [MyDao new];
while (true) {
[myDao callApi];
[NSThread sleepForTimeInterval:10];
}
}
// and in MyDao
-(void)callApi {
NSMutableString *url= [NSMutableString stringWithFormat:#"%#",#"http:www.example.it"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
request.HTTPMethod = #"GET";
self.conn= [[NSURLConnection alloc] initWithRequest:request delegate:self];
if(self.conn){
[self.conn start];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"didReceiveData %s","yes");
}
but unfortunately as stated here: Asynchronous NSURLConnection with NSOperation looks like I cannot do that.
Please help, I refuse to believe that there isn't a simple straightforward solution for this basic scenario.

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.

NSOperationQueue INSIDE an NSOperation

I created an NSOperation which goal is to download a few images (like 20) from 20 URLs.
So inside this NSOperation I create 20 AFImageRequestOperation add them in an NSOperationQueue and call -waitUntilAllOperationsAreFinished on the queue.
Problem is, it doesn't wait, it returns instantly. Here is the code
- (void)main {
NSArray *array = [I have the 20 links stored in this array];
self.queue = [[NSOperationQueue alloc]init];
self.queue.maxConcurrentOperationCount = 1;
for (int i = 0; i < array.count; i++) {
NSURL *url = [NSURL URLWithString:[array objectAtIndex:i]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFImageRequestOperation *op = [AFImageRequestOperation imageRequestOperationWithRequest:request imageProcessingBlock:^UIImage *(UIImage *image) {
return image;
} success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
// SUCCESS BLOCK (not relevant)
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
// FAILURE BLOCK (not relevant)
}];
[self.queue addOperation:op];
}
[self.queue waitUntilAllOperationsAreFinished]; // Here is the problem it doesn't wait
DDLogWarn(#"-- %# FINISHED --", self);
}
In the console, the DDLogWarn(#"-- %# FINISHED --", self); prints before every operations even started, so my guess was that the waitUntilAllOperationsAreFinished didn't do its job and didn't wait, but the 20 Operations are still running after that which may means that the main didn't return yet, so I don't know what to think anymore.
EDIT 1 : Actually I'm wondering, if my DDLog inside the success and failure blocks because waitUntilAllOperationsAreFinished is blocking the thread until all operations complete. That may explain why I don't see anything in happening and everything suddenly.
I have found it useful to create an NSOperation that contains other NSOperations for similar reasons to you, i.e. I have a lot of smaller tasks that make up a bigger task and I would like to treat the bigger task as a single unit and be informed when it has completed. I also need to serialise the running of the bigger tasks, so only one runs at a time, but when each big task runs it can perform multiple concurrent operations within itself.
It seemed to me, like you, that creating an NSOperation to manage the big task was a good way to go, plus I hadn't read anything in the documentation that says not to do this.
It looks like your code may be working after all so you could continue to use an NSOperation.
Depending on your circumstances blocking the thread may be reasonable. If blocking isn't reasonable but you wanted to continue using an NSOperation you would need to create a "Concurrent" NSOperation see Concurrency Programming Guide: Configuring Operations for Concurrent Execution
If you only allow one image download at a time, you could use #jackslashs suggestion to signal the end of the operation, or if you want to allow concurrent image downloads then you could use a single NSBlockOperation as the final operation and use -[NSOperation addDependency:] to make it dependant on all the other operations so it would run last.
When you get the signal that everything is finished and you can set the isFinished and isExecuting flags appropriately as described in the documentation to finalise your main NSOperation.
Admittedly this has some level of complexity, but you may find it useful because once that complexity is hidden inside an NSOperation the code outside may be simpler as was the case for me.
If you do decide to create a Concurrent NSOperation you may find the Apple sample code LinkedImageFetcher : QRunLoopOperation useful as a starting point.
This code doesn't need to be inside an NSOperation. Instead make a class, perhaps a singleton, that has an operation queue and make a method called
-(void)getImagesFromArray:(NSArray *)array
or something like that and your code above will work fine enqueueing onto that queue. You don't need to call waitUntilAllOperationsAreFinished. Thats a blocking call. If your queue has a max operation count of 1 you can just add another operation to it once you have added all the network operations and then when it executes you know all the others have finished. You could just add a simple block operation on to the end:
//add all the network operations in a loop
[self.operationQueue addOperation:[NSBlockOperation blockOperationWithBlock:^{
//this is the last operation in the queue. Therefore all other network operations have finished
}]];

UITableView Refresh Data

I have a UITableViewController that when opened displays a table of the following object:
class {
NSString *stringVal;
int value;
}
However, whenever this controller opens, I want it to download the data from the internet and display "Connecting..." in the status bar and refresh the stringVal and value of all of the objects. I do this by refreshing the array in the UITableViewController. However, to do this the UI hangs sometimes or even displays "blank" table cells until the operation has ended. I'm doing this in an NSOperationQueue to download the data, but I'm wondering if there's a better way to refresh the data without those weird UI bugs.
EDIT:
the UI no longer displays blank cells. This was because cellForRowAtIndexPath was setting nil values for my cellText. However, it still seems somewhat laggy when tableView.reloadData is called even though I'm using NSOperationQueue.
EDIT2:
Moreover, I have two problems: 1. the scrolling prevents the UI from being updated and 2. when the scrolling does stop and the UI starts to update, it hangs a little bit. A perfect example of what I'm trying to do can be found in the native Mail app when you view a list of folders with their unread count. If you constantly scroll the tableview, the folders unread count will be updated without any hanging at all.
Based on your response in the question comments, it sounds like you are calling [tableView reloadData] from a background thread.
Do not do this. UIKit methods, unless otherwise specified, always need to be called from the main thread. Failing to do so can cause no end of problems, and you are probably seeing one of them.
EDIT: I misread your comment. It sounds like you are not updating the UI from a background thread. But my comments about the architecture (i.e. why are you updating in a background thread AFTER the download has finished?).
You state that "when the data comes back from the server, I call a background operation..." This sounds backwards. Normally you would have your NSURLConnection (or whatever you are using for the download) run on the background thread so as not to block to UI, then call out to the main thread to update the data model and refresh the UI. Alternatively, use an asynchronous NSURLConnection (which manages its own background thread/queue), e.g.:
[NSURLConnection sendAsynchronousRequest:(NSURLRequest *)
requestqueue:(NSOperationQueue *)queue
completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler];
And just make sure to use [NSOperationQueue mainQueue] for the queue.
You can also use GCD, i.e., nested dispatch_async() calls (the outer to a background queue for handling a synchronous connection, the inner on the main queue to handle the connection response).
Finally, I will note that you in principle can update your data model on the background thread and just refresh the UI from the main thread. But this means that you need to take care to make your model code thread-safe, which you are likely to mess up at least a couple times. Since updating the model is probably not a time consuming step, I would just do it on the main thread too.
EDIT:
I am adding an example of how one might use GCD and synchronous requests to accomplish this. Clearly there are many ways to accomplish non-blocking URL requests, and I do not assert that this is the best one. It does, in my opinion, have the virtue of keeping all the code for processing a request in one place, making it easier to read.
The code has plenty of rough edges. For example, creating a custom dispatch queue is not generally necessary. It blindly assumes UTF-8 encoding of the returned web page. And none of the content (save the HTTP error description) is localized. But it does demonstrate how to run non-blocking requests and detect errors (both at the network and HTTP layers). Hope this is helpful.
NSURL *url = [NSURL URLWithString:#"http://www.google.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
dispatch_queue_t netQueue = dispatch_queue_create("com.mycompany.netqueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(netQueue,
^{
// We are on a background thread, so we won't block UI events (or, generally, the main run loop)
NSHTTPURLResponse *response;
NSError *error;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
dispatch_async(dispatch_get_main_queue(),
^{
// We are now back on the main thread
UIAlertView *alertView = [[UIAlertView alloc] init];
[alertView addButtonWithTitle:#"OK"];
if (data) {
if ([response statusCode] == 200) {
NSMutableString *body = [[NSMutableString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
[alertView setTitle:#"Success"];
[alertView setMessage:body];
}
else {
[alertView setTitle:#"HTTP Error"];
NSString *status = [NSHTTPURLResponse localizedStringForStatusCode:[response statusCode]];
[alertView setMessage:status];
}
}
else {
[alertView setTitle:#"Error"];
[alertView setMessage:#"Unable to load URL"];
}
[alertView show];
[alertView release];
});
});
dispatch_release(netQueue);
EDIT:
Oh, one more big rough edge. The above code assumes that any HTTP status code != 200 is an error. This is not necessarily the case, but handling this is beyond the scope of this question.