Objective C Incompatible block pointer types sending - objective-c

I'm using parse 1.7.4 , this is y code :
+(NSArray *)getCategorieFromParse{
PFQuery *categoriesQuery = [PFQuery queryWithClassName:#"Categorie"];
[categoriesQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error){
if (!error)
return objects;
else
return [[NSArray alloc] init];
}];
}
but this is generate this error :
Incompatible block pointer types sending 'NSArray *(^)(NSArray
*__strong, NSError *__strong)' to parameter of type 'PFArrayResultBlock __nullable' (aka 'void (^)(NSArray * __nullable
__strong, NSError * __nullable __strong)')
At the return line

Your block wasn't declared with a return type, and it returns an NSArray*, there it is a block returning an NSArray*. The method you are calling expects a block returning void. Obviously your block is not acceptable.
I suspect there is some deep misunderstanding going on what this block is supposed to do. Your method getCategorieFromParse cannot return an array. It's sending an asynchronous request, and your callback block will be called long after getCategorieFromParse returns. The callback block shouldn't try to return anything; it's job is to process the array that it was given.

You cannot return values from within a code block. You should rather use a delegate (just an example I found on google) instead.

You make asynchronous calls. You can't return array synchronously.
Solution: make your method also asynchronous:
+(void) getCategorieFromParse:(void (^)(NSArray*))completion
{
PFQuery *categoriesQuery = [PFQuery queryWithClassName:#"Categorie"];
[categoriesQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error){
if (!error)
completion(objects);
else
completion([[NSArray alloc] init]);
}];
}

Related

How do I return variables captured from a block to the caller in Objective-C

I'm having trouble returning an asynchronous response/error pair captured in a block back to caller. Here is the code:
- (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
{
__block NSData* b_data = nil;
__block NSURLResponse* b_response = nil;
__block NSError* b_error = nil;
dispatch_semaphore_t wait = dispatch_semaphore_create(0);
[self sendRequest:request completion:^(NSHTTPURLResponse* c_response, NSData* c_data, NSError* c_error) {
b_data = c_data;
b_response = c_response;
b_error = c_error;
dispatch_semaphore_signal(wait);
}];
dispatch_semaphore_wait(wait, DISPATCH_TIME_FOREVER);
response = &b_response; // ERROR: Assigning NSURLResponse *__strong * to NSURLResponse *__autoreleasing * changes retain/release properties of pointer
error = &b_error; // ERROR: Assigning NSError *__strong * to NSError *__autoreleasing * changes retain/release properties of pointer
return b_data;
}
Basically, I'm converting an async request to a synchronous one. I need to return the response/error pair back to the caller through a pointer-to-a-pointer. However, I'm running into some problems with ARC and blocks.
I would like to copy the response/error out of the block and return it to the caller without changing the method signature (there is a slew of legacy code that calls this method - I'm trying to gingerly replace NSURLConnection with NSURLSession).
Is there a nice way I can extract the results and return them to the caller?
Your two errors have nothing to do with the block. Those lines should be:
if (response) {
*response = b_response;
}
if (error) {
*error = b_error;
}
The if checks are required incase the caller passes nil for the parameter. You will get a crash trying to dereference a nil pointer.
It's also unnecessary to copy the response/error pair to local __block variables. You can write them directly to the parameter after checking for nil.

How to understand the type of a objective-c block?

- (void)writeImageDataToSavedPhotosAlbum:(NSData *)imageData metadata:(NSDictionary *)metadata completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock
In this function signature, the completionBlock has type ALAssetsLibraryWriteImageCompletionBlock. However, since when we are creating an anonymous function we use
^(int a){
//code here
}
It seems we've never specified anything to be the type of a block. So how to understand this unusual type here?
If you look at the docs for ALAssetsLibraryWriteImageCompletionBlock it is defined as:
typedef void (^ALAssetsLibraryWriteImageCompletionBlock)(NSURL *assetURL, NSError *error);
This is a block with no return value that has two parameters.
Your code needs to be something like:
[library writeImageDataToSavedPhotosAlbum:someData metadata:someMetaData completionBlock:^(NSURL *assetURL, NSError *error) {
// completion handler code here with access to assetURL and error
}];

Is my understanding of completionHandler blocks correct?

Ive read quite bit about blocks by now, Apple's Guide, Cocoabuilder, 3 articles on SO and ive used examples in my code that I basically got from online tutorials. Im still trying to understand one specific question. So I decided to make an app with nothing more than a completionHandler example to understand better. This is what I came up with:
ViewController
- (void)viewDidLoad
[SantiappsHelper fetchUsersWithCompletionHandler:^(NSArray *users) {
self.usersArray = [NSMutableArray array];
for (NSDictionary *userDict in users) {
[self.usersArray addObject:[userDict objectForKey:#"username"]];
}
//WHILE TESTING postarray method, comment this out...
//[self getPoints];
[self.tableView reloadData];
}];
}
SantiappsHelper.h/m
typedef void (^Handler)(NSArray *users);
+(void)fetchUsersWithCompletionHandler:(Handler)handler {
NSString *urlString = [NSString stringWithFormat:#"http://www.myserver.com/myapp/getusers.php"];
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
[request setHTTPMethod: #"GET"];
__block NSArray *usersArray = [[NSArray alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//dispatch_async(dispatch_get_main_queue(), ^{
// Peform the request
NSURLResponse *response;
NSError *error = nil;
NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error) {
// Deal with your error
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
return;
}
NSLog(#"Error %#", error);
return;
}
NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
usersArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];
if (handler){
//dispatch_sync WAITS for the block to complete before returning the value
//otherwise, the array is returned but gets zeroed out
dispatch_sync(dispatch_get_main_queue(), ^{
handler(usersArray);
});
}
});
}
Here is what I understand...
I call fetchUsersWithCompletionHandler from my VC & pass it this completion block. That block takes an NSArray users parameter & returns void.
Meanwhile in the SantiappsHelper Class we have a variable called handler of type ^block which it receives from VC.
The fetchUsersWithCompletionHandler method runs, taking that CompletionBlock parameter, which itself takes the NSArray users parameter? a little confusing.
The webfetch is dispatch_async so it wont block the main thread. So execution on the main thread continues. That new thread executes the fetch synchronously that new thread will stall until the response is returned. Once that new thread receives the response, it fills in the NSHTTPURLResponse. Once it returns, it fills in usersArray with the NSJSONSerialized data.
Finally it reaches the if test and it checks for the existence of the PARAMETER handler that was passed in? A little confusing...the parameter passed in was the completionBlock. Wouldnt that handler parameter always and obviously exist since it was passed in?
Once the handler !NULL then execution is returned to the main thread passing back the NSArray users expected by the block back in the VC.
But if I change the second dispatch to async, the usersArray is properly populated but once handler(usersArray) is sent back to the main thread, its empty or nil! Why?
Correct. The best way to say/think about this is to say that you are invoking a method called fetchUsersWithCompletionHandler:. This method will go away and do some work and at some point in the future it may execute the code you have declared in the block literal and pass in an array of users.
The method takes an argument called handler of type void (^)(NSArray *users). This type represents a block of code that when invoked should receive and array and return no result.
The fetchUsersWithCompletionHandler: does some work and at some point may invoke the block passed in with an array of users as the blocks argument.
Correct
The if (handler) { checks to see if the handler arguments is not nil. In most cases this would be the case especially if you always invoke the fetchUsersWithCompletionHandler: with a block literal, but you could always invoke the method with [self fetchUsersWithCompletionHandler:nil]; or invoked it passing along a variable from somewhere else as the completion, which may be nil. If you try to dereference nil to invoke it then you will crash.
Execution is not "passed back" to the main thread, you are simply enqueueing a block of work to be performed on the main thread. You are doing this with dispatch_sync call which will block this background thread until the block completes - this isn't really required.
The array being nil could be a consequence of you declaring the usersArray with __block storage. This is not required as you are not modifying what usersArray is pointing to at any point you are simply calling methods on it.

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)

Objective-C: __block not working

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.)