Objective-C: __block not working - objective-c

I could not figure out how to change the value of results inside the success block. I use __block like some post suggests but results is forever nil. I set breakpoint inside of block and make sure that JSON is not nil, which download data as I expected.
I am using AFNetworking library if that's relevant.
+(NSArray *)eventsByCityID:(NSString *)cityID startIndex:(NSUInteger)start count:(NSUInteger)count
{
__block NSArray *results = nil;
[[DoubanHTTPClient sharedClient] getPath:#"event/list" parameters:#{#"loc":dataSingleton.cityID} success:^(AFHTTPRequestOperation *operation, id JSON) {
results = [JSON valueForKey:#"events"];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"download events error: %# \n\n",error);
}];
return results;
}

More likely than not, that [very poorly named] method getPath:parameters:success:failure: is asynchronous.
Thus, you need to tell something in the success block that the value has changed. I.e.
^{
[something yoManGotEvents:[JSON valueForKey:#"events"]];
}
(Methods shouldn't be prefixed with get outside of very special circumstances. Third party libraries with lots of API using that prefix outside of said circumstances raise question as to what other system specific patterns they may not be following.)

Related

Objective-C: Lazy loaded models pattern

I have objects, more precisely models, some properties of which are lazily loaded, i.e fetched on read, from a server. At the moment, I apply the classical technique, e.g.
#synthetize description = _description;
- (NSString *)description {
if (!_description) {
NSError *error = nil;
_description = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&error];
if (error) {
_description = nil;
// error handling
}
}
return _description;
}
However, it involves a lot of code repetition. Of course, I still can have a generic method doing this and calling this method in all the getters (that's what I do). But do you have a better idea ?
EDIT: To make the code safer as suggested in comments. Here is another suggestion:
#synthesis description = _description;
- (NSString *)descriptionWithCompletion:(void (^)(NSString *description, NSError *error))completion {
if (!_description) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error = nil;
_description = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&error];
if (error) _description = nil;
completion(_description, error);
});
}
completion(_description, nil);
}
So, you're trying to reduce the boilerplate of methods like this:
- (NSString *)prop {return [self _genericGetterForProperty:_prop];}
Using a macro is one option; it would reduce the amount of text, but you still need a line for each method, so it's not clear to me that's it's really worth the extra complexity.
There is a way you could do this via dynamic method resolution. Essentially, you implement + resolveInstanceMethod: on your class. It will be called if someone tries to call a method on your model object that you haven't supplied at compile time. You can implement it to check the selector passed to see if it matches your xxxWithCompletion: structure. If it does, you can build an implementation based on the "xxx" value and add it to your class. You could prevent the compiler from warning you about this by declaring your properties as #dynamic or by explicitly suppressing warnings via pragmas.
I don't recommend it, though. It's a complicated and tricky solution; unless you've got hundreds of these properties, I would just write the boilerplate. (Or write a script to write the boilerplate.)

Obj-C: __block variable not retaining data

I think I might have an async problem going on here, which bites cause I thought I had solved it. Anyway, I am making a bunch of web service calls like so:
//get the client data
__block NSArray* arrClientPAs;
[dataManager getJSONData:strWebService withBlock:^(id results, NSError* error) {
if (error) {
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"Getting Client Data Error!" message:error.description delegate:nil cancelButtonTitle:NSLocalizedString(#"Okay", nil) otherButtonTitles:nil, nil];
[alert show];
} else {
arrClientPAs = results;
}
}];
and getJSONData is like so:
- (void) getJSONData : (NSString*) strQuery withBlock:(void (^)(id, NSError *))completion {
NSDictionary* dictNetworkStatus = [networkManager checkNetworkConnectivity];
NetworkStatus networkStatus = [[dictNetworkStatus objectForKey:#"Status"] intValue];
if (networkStatus != NotReachable) {
//set up the url for webservice
NSURL* url = [NSURL URLWithString:strQuery];
NSMutableURLRequest* urlRequest = [NSMutableURLRequest requestWithURL:url];
//set up the url connection
__block id results;
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[NSOperationQueue mainQueue] completionHandler:
^(NSURLResponse* response, NSData* jsonData, NSError* error) {
if (error) {
completion(nil, error);
return;
}
results = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves | NSJSONReadingAllowFragments error:&error];
completion(results, nil);
}];
} else {
//not connected to a network - data is going to have to come from coredata
}
}
In the first block, if I log arrClientData I can see the data that I am expecting but when I log arrClientData after it it is nil. I was following this SO thread - How to return a BOOL with asynchronous request in a method? (Objective-C) and a couple of others.
Obviously I am trying to get the data after the async call is made. Any help would be appreciated.
The problem lies, I think, in what "asynchronous" means. Here's a diagram:
Step One
__block result;
Step Two - do something asynchonous, including e.g. setting result
Step Three
What order do things happen in here? Step Three happens before Step Two gets finished. That is what asynchronous means: it means, "go right on with this code, don't wait for the asynchronous stuff to finish." So at the time Step Three happens, the result variable has not yet been set to anything.
So, you are just misleading the heck out of yourself with your __block result. __block or no __block, there is no way you are going to find out out what the result is afterwards, because there is no "afterwards". Your code has completed before your __block result is even set. That is why asynchronous code uses a callback (eg. your completion block) which does run afterwards, because it is sequentially part of (appended to) the asynchronous code. You can hand your result downwards through the callback, but you cannot usefully set it upwards from within the block and expect to retrieve it later.
So, your overall structure is like this:
__block NSArray* arrClientPAs; // it's nil
[call getJSONdata] = step one
[call sendAsynchronousRequest]
do the block _asynchronously_ = step two, tries to set arrClientPAs somehow
step three! This happens _before_ step two, ...
... and this entire method ends and is torn down ...
... and arrClientPAs is still nil! 🌻
I repeat: you cannot pass any information UP out of an asynchronous block. You can only go DOWN. You need your asynchronous block to call some method of some independently persistent object to hand it your result and tell it to use that result (and do it carefully, on the main thread, or you will cause havoc). You cannot use any automatic variable for this purpose, such as your declared NSArray variable arrClientPAs; there is no automatic scope any more, the method is over, the automatic variable is gone, there is no more code to run.
Check the value of the 'error 'variable after call:
results = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves | NSJSONReadingAllowFragments error:&error];
If 'error' isn't nil there is a problem with data which you get in your completion block.
You are mixing styles and confusing the purpose of __block.
Note: When you call a method that will be executed asynchronously you are creating a new execution path which will be executed at some point in the future (which includes immediately) on some thread.
In your getJSONData method you use a __block qualified variable, results, when you should not. The variable is only required within the block and should be declared there:
//set up the url connection
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[NSOperationQueue mainQueue] completionHandler:
^(NSURLResponse* response, NSData* jsonData, NSError* error)
{
if (error) {
completion(nil, error);
return;
}
id results = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves | NSJSONReadingAllowFragments error:&error];
completion(results, nil);
}];
Declaring the variable outside of the block and adding __block just adds pointless complexity. After the call to sendAsynchronousRequest, returns before the request has been performed, the value of results would not be the value assigned in the block. The call to the completion block is performed on a different execution path and probably will not even be executed until after the call to getJSONData has returned.
However what is correct about your getJSONData method is its model - it takes a completion block which sendAsynchronousRequest's own completion handler will call. This is what is incorrect about your call to getJSONData - the completion block you pass does not pass on the results to another block or pass them to some object, but instead assigns them a local variable, arrClientPAs, declared before the call. This is the same situation as described above for getJSONData and will fail for the same reasons - it is not the arrClientPAs fails to "retain the data" but that you are reading it on in the current execution path before another execution path has written any data to it.
You can address this problem the same way getJSONData does - the enclosing method (not included in your question) can take a completion block (code entered directly into answer, expect typos!):
- (void) getTheClientData: ... completionHandler:(void (^)(id))handler
{
...
//get the client data
[dataManager getJSONData:strWebService withBlock:^(id results, NSError* error) {
if (error) {
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"Getting Client Data Error!" message:error.description delegate:nil cancelButtonTitle:NSLocalizedString(#"Okay", nil) otherButtonTitles:nil, nil];
[alert show];
} else {
handler(results); // "return" the result to the handler
}
}];
There is another approach. If and only if getClientData is not executing on the main thread and you wish its behaviour to be synchronous and to return the result of the request then you can issue a sendSynchronousRequest:returningResponse:error: instead of an asynchronous one. This will block the thread getClientData is executing on until the request completes.
In general if you have an asynchronous method which you cannot replace by a synchronous one but require synchronous behaviour you can use semaphores to block your current thread until the asynchronous call completes. For an example of how to do this see this answer.
HTH

How do i deal with my response object which corresponds to node ids?

Edit wording..
I am using a 3rd party library called Drupal-IOS-SDk to connect my Drupal website with my under development IOS app. I use a method to index nodes on my website and I am just wondering if anyone has any knowledge about how to deal with the response from my website. To give a little context if I run a similar bit of code to my index method (a getNode method) which works fine and I am able to access my response perfectly as shown below:
//code for correctly working nodeGet method
NSMutableDictionary *nodeData = [NSMutableDictionary new];
[nodeData setValue:#"650" forKey:#"nid"];
[DIOSNode nodeGet:nodeData success:^(AFHTTPRequestOperation *operation, id responseObject) {
//print out responseObject
//NSLog(#"%#", responseObject);
//pull data from the responseObject
NSInteger testNumber = [responseObject objectForKey:#"changed"];
NSLog(#"%ld", (long)testNumber);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//we failed, uh-oh lets error log this.
NSLog(#"%#, %#", [error localizedDescription], [operation responseString]);
}];
This is the what gets printed by response object(I didnt include the whole thing but youll get the point):
//printed statement for correctly working nodeGet method
{
changed = 1390534644;
comment = 0;
created = 1390534644;
data = "b:0;";
"ds_switch" = "";
"field_additional_pictures" = (
);
"field_author" = {
und =
The above code gets node data and calling the "objectforkey" method on responseObject lets me access numbers or whatever else is stored in my responseObject. Where I have commented pull data from response object I get back the integer "1390534644" which correctly corresponds to the "changed" variable as you can see from the printed response above. The above section works fine. It is the next step where I get confused:
//code for index method that is in question
NSMutableDictionary *paramsDictionary = [NSMutableDictionary new];
[paramsDictionary setValue:#"books_e_books_other_books" forKey:#"type"];
[DIOSNode nodeIndexWithPage:#"0" fields:#"nid" parameters:paramsDictionary pageSize:#"20"
success:^(AFHTTPRequestOperation *operation, id responseObject) {
//print response object
NSLog(#"%#", responseObject);
NSLog(#"GREAT SUCCESS");
//HERE IS MY PROBLEM!!!
//what do I do with response object?
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//code
NSLog(#"Oh no failed when trying to get the index");
NSLog(#"%#, %#", [error localizedDescription], [operation responseString]);
}];
In this section I index all of the nodes and get fields from each instead of getting all of the information from only one node. I get a response which shows things are working correctly thus far because the response has the correct info. I am confused though because I am not sure what my response object is exactly. It is a collection of nodes each with a "nid" and "uri" as shown from the index method responseObject below. If I wanted to get the value "650" for example from my first "nid" in the below printed area how would I go about doing this? I dont think I can call "objectForKey" as I did in the first working example because each node has a "nid". If I told my app to look for a key named nid it doesnt know which one to look for. With no unique keys how can I access the number 650? I have printed my index method responseObject below so you can see what I am talking about.
//printed statement from index method, this is what i am confused about, there are no unique keys how do I access the value 650 or the first nid
{
nid = 650;
uri = "http://www.domain.com/domain_endpoint/node/650";
},
{
nid = 649;
uri = "http://www.domain.com/domain_endpoint/node/649";
},
{
nid = 647;
uri = "http://www.domain.com/domain_endpoint/node/647";
},
It's been a few months since you asked this question, so I'm not sure if you're still looking for an answer, but in case anyone in the future needs it.
The responseObject is an array of NSDictionary objects. You can access the responseObject items just like an array and do whatever you need to with them depending on what you need the data for.
For example:
NSArray *responseArray = responseObject;
for (NSDictionary *item in responseArray)
{
// Do something with your item here
// For example:
NSString *uriStr = [item valueForKey:#"uri"];
}

Need explanation for this usage of block as method parameter

This is a snippet from AFNetworking's sample code:
+ (void)globalTimelinePostsWithBlock:(void (^)(NSArray *posts, NSError *error))block {
[[AFAppDotNetAPIClient sharedClient] getPath:#"stream/0/posts/stream/global" parameters:nil success:^(AFHTTPRequestOperation *operation, id JSON) {
NSArray *postsFromResponse = [JSON valueForKeyPath:#"data"];
NSMutableArray *mutablePosts = [NSMutableArray arrayWithCapacity:[postsFromResponse count]];
for (NSDictionary *attributes in postsFromResponse) {
Post *post = [[Post alloc] initWithAttributes:attributes];
[mutablePosts addObject:post];
}
if (block) {
block([NSArray arrayWithArray:mutablePosts], nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (block) {
block([NSArray array], error);
}
}];
}
What I don't understand are:
The (void (^)(NSArray *posts, NSError *error))block part. Assuming that it is a block, does it mean the block is a parameter of the globalTimelinePostsWithBlock method?
Following the first question, can anyone explain the syntax for me? Why is there a block keyword in the end?
if you don't know how blocks work.. then don't bother trying to understand it just by looking at the code (even if you have used lambda/anonymous functions in other languages like javascript or ruby).. b/c the objective-c syntax is a class on it's own..
i'd recommend you take the time to understand block syntax in obj-c on it's own.. then take a look at examples that use them. This tutorial is excellent (two parts)..
I did what you did before.. and pulled out half my hair.. after looking at the said tutorial.. my hair grew right back up :)
just for fun i'll try to address your specific questions:
1.The (void (^)(NSArray *posts, NSError *error))block part. Assuming that it is a block, does it mean the block is a parameter of the globalTimelinePostsWithBlock method?
yes it is.. so this is a way of calling this method:
// first define the block variable
void(^block)(NSArray *posts, NSError *error) = (NSArray *posts,NSError *error) {
// block body
// posts and error would have been passed to this block by the method calling the block.
// so if you look at the code sample below..
// posts would be [NSArray arrayWithArray:mutablePosts]
// and error would just be nil
}
// call the function
[AFNetworking globalTimelinePostsWithBlock:block];
2. Following the first question, can anyone explain the syntax for me? Why is there a block keyword in the end?
basically the block keyword is the name of the argument.. notice how it's used in the body of the method:
if (block) {
block([NSArray arrayWithArray:mutablePosts], nil);
}
again to understand how/why.. i recommend you look at the above article.. learning blocks in obj-c has a bit of learning curve.. but once you master it.. it's an amazing tool. please take a look at my answer here to see some sample uses for blocks.
Here is also a sample question/answer that provides a case study of converting delegation into a block based approach, which can also illustrate how blocks work.
The block is passed into the method as something to be called when the API call succeeds. globalTimelinePostsWithBlock will call the block passed in with the data (and possibly an NSError)
block in this case isn't a keyword, it's just the name of the variable.
If you wanted to use globalTimelinePostsWithBlock, you would call it like
[ClassName globalTimelinePostsWithBlock:^(NSArray *posts, NSError *error) {
// Check error, then do something with posts
}];
(where ClassName is the name of the class globalTimelinePostsWithBlock is defined on)
Block definition are similar to C-functions.
(void (^)(NSArray *posts, NSError *error))block
The initial void defines the return type of the function.
The ^ is the block pointer. Similar to * for objects.
(NSArray *posts, NSError *error) are the parameters with variable names.
block is the variable in which this block gets stored. (Bad naming here)

have a way of AFNetworking return your callback without blocks?

I'm using AFNetworking library, and it works using blocks to handle your results, like as follow:
[httpClient HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *op, id response) {
//success clock
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//error block
}]
have a way of use it without code blocks? for example using a delegate? something like as follow:
[httpClient HTTPRequestOperationWithRequest:request delegate:self]
and in self class:
-(void)afhttpSuccess:(AFHTTPRequestOperation*)op Response:(id)response {
//success method
}
-(void)afhttpError:(AFHTTPRequestOperation*)op Response:(NSError*)error {
//error method
}
Not sure why a delegate + selector paradigm would be preferable, but I made this library, which eases the transition from ASI, which has that pattern.
Alternatively, you can always just call the success / failure methods in your success/failure callbacks.