I have a method that calls a long running process. the long running process and I use AFNetworking which itself uses blocks and returns success block and failure block. So I am trying to test my method and the tests will fail before the success block is called. I thought I would try to get my method to also use blocks. I have another method that I managed to change to use blocks but that only uses bool isFinished and the return value is void. The method I have difficult with needs to return NSDecimalNumber* and takes an NSString.
Method signature
(NSDecimalNumber*) getRate:(NSString*) rateCode;
I would like to be able to able to add a completion block with a BOOL that I set when the AFNetworking method enters the success block
I would also like to be able to call the method and within it's completion block access the NSDecimalNumber* value it returned
Possible? If so please show me how
You probably have to split it apart.
You can have a fetchRate: method that takes a completion block:
- (void)fetchRate:(NSString*)rateCode completion:(void (^)(NSDecimalNumber *))completion;
Then call like this:
void (^completion)(NSDecimalNumber *) = ^(NSDecimalNumber * rate){
// this is called when rate is returned from your webservice
}
// call fetchRate: now, results will arrive later...
[ myObj fetchRate:<rate code> completion:completion ];
// code here runs immediately; the results come back later.
-fetchRate: looks something like...
- (void)fetchRate:(NSString *)rateCode completion:(void (^)(NSDecimalNumber *))completion
{
void (^asiCompletionBlock)(/*args*/) = ^(/*...args...*/){
// called after ASI request completes
NSDecimalNumber * answer = /* get answer from ASI HTTP response */
// call our completion block that was passed in:
completion( answer );
};
// do your asi HTTP request here, pass asiCompletionBlock for completion arg
}
Related
I have a method that inits the object and it has a completion block: typedef void(^initCompletionHandler)(BOOL succesful);
In this method I want to call the handler but I am not sure how to do it because if I call it before the return the object won't be finished initialising which is used immediately in the next line. I also obviously can't call the handler after the return. i,e:
if(haveError){
handler(NO);
}
else{
handler(YES);
}
return self;
Is there any way I can return and call the handler at the same time?
A couple of observations:
I'm unclear as to why you say "because ... the return object won't be finished initialising." You're doing the initialization, so just ensure it finishes all of the associated initialization before calling that handler. If the issue is that the caller won't have a valid reference to that object yet, you could always include a reference to it in the parameter of the block, e.g.
typedef void(^initCompletionHandler)(MyObject object, BOOL succesful);
and then supply that parameter, e.g.:
if (haveError){
handler(self, NO);
} else {
handler(self, YES);
}
Also, you say "I obviously can't call the handler after the return". But you can. You could just do a dispatch_async, if you wanted:
dispatch_async(dispatch_get_main_queue(), ^{
if (haveError){
handler(NO);
} else {
handler(YES);
}
});
return self;
That's a little inelegant, as if you call it from another thread, you have some potential race conditions that you might have to coordinate/synchronize, but you get the idea: You don't have to call the handler synchronously.
Having made both of those observations, I must confess that I'm not a fan of having init actually launching some asynchronous process and having its own completion block. I'd be inclined to make those two different steps. If you look at the Cocoa API, Apple has largely shifted away from this pattern themselves, generally having one method for instantiation, and another for starting the asynchronous process.
I make an photography app in iPhone and I have these 3 classes: ViewController, CaptureManager, and ImgProcessor.
ViewController:
-(IBAction)takePic:(id)sender{
images = [captureManager takeMultipleImagesWithCompletion:^{
//Some UI related code..
[imgProcessor process:images];
}];
}
CaptureManager:
-(NSArray *)takeMultipleImagesWithCompletion:^(void)completionHandler{
// take picture codes...
completionHandler();
return arrayOfImagesTaken;
}
So far it works as desired: imgProcessor processes the images taken by captureManager. But I don't quite get the idea how this works. Bcos I called completionHandler before I return the array of images taken. How did this code executed? Is there a better solution to this?
Thanks!
You don't need to return the value images. You can pass it as an argument for the cmpletionHandler block.
-(void)takeMultipleImagesWithCompletion:(void (^)(NSArray *images))completionHnadler{
// take picture codes...
completionHnadler(arrayOfImagesTaken);
}
You can call it like this :
-(IBAction)takePic:(id)sender{
[captureManager takeMultipleImagesWithCompletion:^(NSArray *images){
[imgProcessor process:images];
}];
}
How it works ?
Here the block is used as a callback, it defines the code to be executed when a task completes. When the takeMultipleImagesWithCompletion is finished running, the block completionHnadler will be called.
Since your takeMultipleImagesWithCompletion executes the completion block synchronously, it doesn't need to take a completion block. It can just return the arrayOfImagesTaken and the caller can do whatever it wants with it.
I want to use blocks as callback handler, but I am not sure what I am doing is proper or not because my app is crashing.
Here is what I am doing:
In my FirstViewController I am calling method of class FirstModel to get data from server as follows:
//In FirstViewController.m
[aFirstModelObj retreiveDataWithCallBackHandler:^(NSDictionary *responseDict){
//Data is received so we can proceed...
}];
//In FirstModel.m
typedef void(^newBlock)(NSDictionary *);
newBlock theBlock;
-(void)retreiveDataWithCallBackHandler:(void(^)(NSDictionary *))aBlock
{
//Saving "aBlock" for further use..
theBlock = aBlock;
//Server Processor will retrieve data using URL asynchronously,
//initializing ServerProcessor object and providing FirstModel its delegate,so that when data is received in ServerProcessor class FirstModel's receivedResponse method will get called.
serverProcessorObj.delegate = self;
}
-(void)receivedResponse:(NSDictionary *)responseDict
{
//once data is received call block,
theBlock(responseDict);
}
My app is crashing because I am loosing delegate. When I call [delegate receivedResponse:response] from ServerProcessor it says exc bad access. Can anyone tell what I am doing wrong?
Thanks in advance!
Besides checking that the block is not nil (as per comments), perhaps it has something to do with:
The above approach will only allow you to process one asynchronous request at a time. Are you trying to process concurrent requests? A subsequent request will override the block for a previous request. This could cause problems.
If you need to support concurrent requests, you'll need retain each of the blocks for a currently running request, so that the can be invoked upon completion of the request.
If you were passing on object in, such as:
retreiveDataForCustomer:(Customer*)customer onSuccess:(void (^)(NSDictionary*))success
. . . then I would suggest create a property or associative reference on the customer object to retain the block. However as your call has no parameters, you'll have to find another way of tracking which block goes with which delegate invocation.
I've corrected your code.
-(void)retreiveDataWithCallBackHandler:(void(^)(NSDictionary *))aBlock
{
//Saving "aBlock" for further use..
theBlock = Block_copy(aBlock);
//Server Processor will retrieve data using URL asynchronously,
//initializing ServerProcessor object and providing FirstModel its delegate,so that when data is received in ServerProcessor class FirstModel's receivedResponse method will get called.
serverProcessorObj.delegate = self;
}
-(void)receivedResponse:(NSDictionary *)responseDict
{
//once data is received call block,
if (theBlock)
theBlock(responseDict);
}
I have an app wherein I need to loop through and load stored data in order to generate information to save to PDF file, pages. I created a loop, but it runs too fast, so to speak. The saved pages cannot load fast enough, so the resulting data is incomplete. I think I need to create a for loop with delay or completion handler. Something like:
for (int i = 0, i < numberOfPages, doTaskWithCompletion:i++) {
arrayOfPages = [self loadDataAtIndex:i withCompletionHandler:handler];
[self writeDataToFile:arrayOfPages];
}
Not even sure how to write that pseudo-code, but basically I only want to jump to the next iteration after a completion handler has fired for the task of loading the data.
Note: Source data comes from core data, saved as PDF file. The data is pages and views for page. Each page can have 1 or unlimited views. Each view might have data too, like an image. What happens currently is that the number of pages written is correct, but the page's views that are rendered to PDF are identical, because they all read the same data, as the pages are not loaded before the PDF is written.
You may approach the problem as follows:
First define a generic completion block type. Note, param result can be anything. If it's an NSError this signals a failure, otherwise success.
typedef void (^completion_t)(id result);
Define a block type for your asynchronous task. Your task takes an index as input, and - since it is asynchronous, it takes a completion block as last parameter:
typedef void (^unary_async_t)(int index, completion_t completion);
Your task block object can be defined as follows:
unary_async_t task = ^(int index, completion_t completion) {
[self loadDataAtIndex:index withCompletion:^(id result){
if (![result isKindOfClass:[NSError class]]) {
[self writeDataToFile:result];
result = #"OK";
}
if (completion) {
completion(result);
}
}];
};
Note: your loadDataAtIndex:withCompletion: is asynchronous, and thus, it takes a completion block as last parameter. The completion block's parameter result is the result of the asynchronous task, namely the "array of pages" - or an NSError object if that fails.
In this completion block, you safe your result (the pages) to disk, invoking writeDataToFile:. (We assuming this won't fail). If that is all finished, task invokes the provided completion block completion (if not nil) passing the result of the whole operation, which is #"OK" in case of success, and an NSError in case of failure.
Now, the more interesting part: how to make this in a loop, where numerous _task_s will be executed sequentially, one after the other:
We define two helper methods - or functions:
What we finally need is a function with this signature:
void apply_each_with_range(int lowerBound, int upperBound,
unary_async_t task, completion_t completion);
This is the function which shall invoke task N times, passing it parameter index in the range from lowerBound (including) to upperBound (not including), where N equals upperBound - lowerBound and index starts with lowerBound.
It's an asynchronous function, and thus, takes a completion block as last parameter. Do I repeat myself? Well, you should recognize the pattern! ;)
Here is the implementation:
void apply_each_with_range(int lowerBound, int upperBound,
unary_async_t task, completion_t completion)
{
do_apply(lowerBound, upperBound, task, completion);
}
And the other helper - which is finally performing some sort of "for_each index in range[upperBound, lowerBound] sequentially invoke task with parameter index":
static void do_apply(int index, int upperBound,
unary_async_t task, completion_t completion)
{
if (index >= upperBound) {
if (completion)
completion(#"OK");
return;
}
task(index, ^(id result) {
if (![result isKindOfClass:[NSError class]]) {
do_apply(index + 1, upperBound, task, completion);
}
else {
// error occurred: stop iterating and signal error
if (completion) {
completion(result);
}
}
});
}
The function do_apply first checks whether the index is out of range. If, then it is finished and calls the completion handler with an #"OK".
Otherwise, it invokes task with argument index and provides a completion handler which gets invoked when task finished, which itself passes the result of the task. If that was successful, do_apply will invoke itself with argument index incremented by one. This may look like a "recursion" - but it is not. do_apply already returned, when it invokes itself.
If task returned and error, do_apply stops, "returning" the error from task in its completion handler (which is finally provided by the call-site).
Now, you just need to put these pieces together in your project - which should be fairly easy.
You can test to see if the data is saved and set a flag to see whether or not the data is saved. Then put an if statement like if the flag is set to yes then run the for loop. You can do something like that for all of your data to make sure all data is stored. Good luck!! Hope this helps!!
I'm having trouble implementing the thread-safe core data concepts outlined in this tutorial. My goal is to have a reusable portion of code that can take arguments in, do core data operations (adds, updates, deletes) and then callback asynchronously when done.
So heres the block that 'safely' modifies core data objects:
+ (void)saveDataInContext:(void(^)(NSManagedObjectContext *context))saveBlock
{
NSManagedObjectContext *context = [NSManagedObjectContext context];
[context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[defaultContext setMergePolicy:NSMergeObjectByPropertyStoreTrumpMergePolicy];
[defaultContext observeContext:context];
block(context);
if ([context hasChanges])
{
[context save];
}
}
From the way I understand it, this executes a block of code? I don't understand how the 'context' being passed in figures in. Is this part of the block's signature?
So here is the wrapper that does the operation in the background and adds a completion call:
+ (void)saveDataInBackgroundWithContext:(void(^)(NSManagedObjectContext *context))saveBlock completion:(void(^)(void))completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[self saveDataInContext:saveBlock];
dispatch_sync(dispatch_get_main_queue(), ^{
completion();
});
});
}
Here is an example using it:
NSArray *listOfPeople = ...;
[NSManagedObjectHelper saveDataInBackgroundWithContext:^(NSManagedObjectContext *localContext){
for (NSDictionary *personInfo in listOfPeople)
{
PersonEntity *person = [PersonEntity createInContext:localContext];
[person setValuesForKeysWithDictionary:personInfo];
}
} completion:^{
self.people = [PersonEntity findAll];
}];
What is the 'localContext' passed in here? I think most of my issues here revolve around not understanding blocks.
A brief look at that tutorial shows it is talking about magical record. I have never used it, so I can't speak for it.
// This declares a class method that returns void and takes a block as parameter.
// The block returns void and has one parameter, namely, a pointer to an
// NSManagedObjectContext object.
+ (void)saveDataInContext:(void(^)(NSManagedObjectContext *context))saveBlock;
You would call that method like this...
[SomeClass saveDataInContext:^(NSManagedObjectContext *context){
// Some code
}];
That means you are passing in a block of code to the function. At some point it will execute the bock of code you gave it. When it does, it's going to pass a managed object context into the block so it can do something with it.
Now, look at the implementation of that method...
+ (void)saveDataInContext:(void(^)(NSManagedObjectContext *context))saveBlock
{
// Create a MOC - note there is no concurrency type, so it will get
// NSConfinementConcurrencyType, which means it must be used exclusively
// from the thread in which it was created. Since it is a local variable
// and it gets destroyed after this function is called, that should be cool
// PROVIDED the using block does not do anything untoward with it.
NSManagedObjectContext *context = [NSManagedObjectContext context];
// Set the merge policy
[context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
// MR must set some default context...
// Some how the above context needs a persistent store to save...
[defaultContext setMergePolicy:NSMergeObjectByPropertyStoreTrumpMergePolicy];
// Probably setting up notification handler for DidSave
[defaultContext observeContext:context];
// Now, this is where the block you passed in gets called.
// Note, that the managed object context has already been setup for you.
// Now that it's setup, the block of code that you passed in is going
// to be called, and it will be given a context that it can use to execute
// code in the calling thread.
block(context);
// If you changed something to the context in your block of code, the save.
if ([context hasChanges])
{
[context save];
}
}
Let's revisit a our code that called this method...
[SomeClass saveDataInContext:^(NSManagedObjectContext *context){
// Now, the saveDataInContext method has been called. However, inside
// that method, a call was made to the block that was passed in.
// That would be this here block of code. So, if you look up in
// the method, where is calls "block(context)" this block of code will
// be executed right there. Mentally, you can cut and paste this code
// in that spot.
// The context parameter is the context that was passed to this block.
// you can use it to do any Core Data stuff...
}];
Now, this code is very similar, but it takes two blocks. One is used to execute some code on the context, and the other is a block that will get executed with the asynchronous save has completed.
saveBlock should be familiar. It's the same concept as in the above example.
completion is a block, that returns void, and takes not parameters. It will get called when all the work has been done.
+ (void)saveDataInBackgroundWithContext:(void(^)(NSManagedObjectContext *context))saveBlock completion:(void(^)(void))completion
{
// Dispatch some work on one of the global concurrent queues. It will
// get done on some thread, nobody knows which one, but it does not matter
// because the code in this block calls saveDataInContext, and passes the
// block it was given that does some modifications to the context.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[self saveDataInContext:saveBlock];
// Now, when the above work is done, we are still running in some random
// thread. I guess the library wants all callbacks to happen on the main
// thread, so this block is dispatched on the main thread. Note that it
// calls the second bock passed in as the completion block.
// So the <saveBlock> will be run on some random thread, and then, when
// it is done, the <completion> block will be called on the main thread.
dispatch_sync(dispatch_get_main_queue(), ^{
completion();
});
});
}
Like earlier, when you call that method, you can mentally replace the with the first block you pass in, and replace with the second block.
[NSManagedObjectHelper saveDataInBackgroundWithContext:^(NSManagedObjectContext *localContext){
// This is the first block. It gets executed where you see <saveBlock>
// being used in the earlier method. You are being given the already
// prepared MOC, and it's name is <localContext>. Do your managed object
// context stuff with it. Note that it will be running in some unknown thread.
for (NSDictionary *personInfo in listOfPeople)
{
PersonEntity *person = [PersonEntity createInContext:localContext];
[person setValuesForKeysWithDictionary:personInfo];
}
} completion:^{
// Now, this is the second block, which is run when all the core data saving
// has been completed. It will run on the main thread.
self.people = [PersonEntity findAll];
}];
Hopefully, that helps you understand what is happening, even though I don't know what magical record is really doing under the covers.
EDIT
In response to this comment...
I don't think I understand how these blocks work. If a block has this
method signature "+
(void)saveDataInContext:(void(^)(NSManagedObjectContext
*context))saveBlock" why is the block NOT using "context" or "saveBlock"? Which is the return value for a block and which is the
passed in value? – Mike S
First, the block does not have this signature...
+ (void)saveDataInContext:(void(^)(NSManagedObjectContext *context))saveBlock;
That is a class method. Let's break it down piece by piece. First, however, let's forget the block parameter, and use something easy, for comparison.
+ (void)foo:(NSInteger)someInteger;
That is a is a class method, foo:, which returns void and takes one argument. The type of that single argument is NSInteger. If I wanted to call it, I would do so like this:
[SomeClass foo:42];
Likewise...
+ (void)saveDataInContext:(void(^)(NSManagedObjectContext *context))saveBlock;
is a class method, saveDataInContext:, which returns void and takes one argument. The type of that single argument is void(^)(NSManagedObjectContext *context).
Now, don't let that gobbly-dee-gook fool you. It's just a type (albeit a somewhat confusing one to parse if you don't understand much C) So, what is void(^)(NSManagedObjectContext *context)
First, it is a block. If that (^) after the void were (*) it would be a function pointer.
Basically, it means that the type of that argument is a block that returns void and has one parameter, namely a pointer to a NSManagedObjectContext (with a name context).
So, if we read it out loud...
+ (void)saveDataInContext:(void(^)(NSManagedObjectContext *context))saveBlock;
is a class method, with selector saveDataInContext: which returns void and has one parameter, which has a name saveBlock and is of the type "block that returns void and has one parameter of type NSManagedObjectContext *."
Just like we call the first example like this...
[SomeClass foo:42];
we call the latter example like this...
[SomeClass saveDataInContext:^(NSManagedObjectContext *context){
// We are creating a bock of code, so stuff some code in here.
}];
Now, just like you passed the integer 42 to foo: you are passing the block in between the {} as the argument to saveDataInContext:.
Now, note that the signature of thesaveDataInContext: method wants a block that itself has a parameter. So, when you provide your block, you are basically saying, "Hey, here is a block of code for you to call, and when you do so, make sure you give me a pointer to a NSManagedObjectContext object that I can use.
What this means is that when your block is called, the calling code will call your block and provide a NSManagedObjectContext * to you with the variable name context.
Think of it like this, as a trivial example of saveDataInContext:.
+ (void)saveDataInContext:(void(^)(NSManagedObjectContext *context))saveBlock {
// Create a context to give the block we are going to call..
NSManagedObjectContext *moc = //
saveBlock(moc);
}
Now, when your code is called, you will get the moc object as your argument. Bascially, that method creates a managed object context, does all the thread safety stuff, then calls your block of code, and gives you a pointer to the managed object context that it has safely created. Your code is executed within the confines of that safe environment, using the MOC passed to it as a function (block) parameter.
I hope that didn't make it worse...