Hi I am using this library and I found the function:
- (void) queueRequest:(NSString*)urlPath completion:(void(^)(NSData*))completionWithDownloadedData;
I try to pass a simple NSData *data; and it throw an error, what really mean (void(^)(NSData*))? Is the first time that I see it.
Thanks a lot.
(void(^)(NSData*)) declares a code block.
You can call your function this way.
[obj queueRequest:urlPath completion:^(NSData* data){
/* some code */
}];
data is a parameter to your block, which you can work with. The block will be called when the queueRequest will finish, asynchronously.
The interface is asynchronous, meaning that the data will only be available sometime later. This means that the method can’t simply return the NSData* (without blocking for all the time, which is impractical). The problem is nowadays often solved with blocks, and the completion argument here is a block that takes an NSData* argument and returns void. This is how you call such a method:
[foo queueRequest:path completion:^(NSData *receivedData) {
NSLog(#"Received data: %#", receivedData);
}];
The call will return immediately and the block will be executed sometime later, when the data is available.
It's a block that accepts a NSData object as it's only argument and returns nothing.
See Apple's Blocks Programming Topics.
Related
From the documentation:
/**
A block to be executed just after an object is added to the cache. This block will be excuted within
a barrier, i.e. all reads and writes are suspended for the duration of the block.
*/
#property (copy) TMMemoryCacheObjectBlock didAddObjectBlock;
I'm trying to make use of this to print a message when objects are added to the cache. My attempt so far:
[[_timedCache memoryCache] setDidAddObjectBlock:^{
NSLog(#"added something to cache");
}];
This gives a "parameter type mismatch" compiler error however. I'm fairly new to Objective-C and am probably doing something naive here. Any suggestions?
TMemoryCahceObjectBlock has the following signature: (TMMemoryCache *cache, NSString *key, id object), which your block doesn't have. Change the signature of your block and you are golden.
I am using a NSProxy subclass and forwardInvocation: for capturing calls to my Backend API object (a shared instance).
Some Background information:
I want to capture the API calls so I can check everytime if I have to refresh my authentication token. If yes I just perform the refresh before.
The method parameters (of invocation) contain blocks.
Some simplified code:
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation setTarget:self.realAPI];
[invocation retainArguments];
// Perform refresh call and forward invocation after
// successfully refreshed
if (authenticationRefreshNeeded) {
[self.realAPI refreshWithBlock:^(NSObject *someObject) {
[invocation invokeWithTarget:self.realAPI];
}];
}
// Otherwise we just forward the invocation immediately
else {
[invocation invokeWithTarget:self.realAPI];
}
return;
}
I am already calling retainArguments so my blocks and other parameters don't get lost because of the late execution of invokeWithTarget: (refreshWithBlock: makes an async API call).
Everything works fine so far - BUT:
The return value of invocation is always nil when invokeWithTarget: is performed within the refresh block. Is there any way to retain the return value (like the arguments)?
Any hints? Suggestions?
Update
As response to #quellish:
The problem is that the return value is of type NSURLSessionDataTask (that I use to show an activity indicator) which I read directly after making the call. But the proxy does not forward the call immediately so the return value is not there - of course (I was blind).
What would be a possible workaround? Can I return a placeholder value or how can I know as caller when the method gets invoked so I can retrieve the return value later?
To perform an operation when your invocation is complete, passing the result:
if (authenticationRefreshNeeded) {
[self.realAPI refreshWithBlock:^(NSObject *someObject) {
NSURLSessionDataTask *resultTask = nil;
[invocation invokeWithTarget:self.realAPI];
[invocation getReturnValue:&resultTask];
if (completion != nil){
completion(resultTask);
}
}];
}
Where completion() is a block that takes an NSURLSessionDataTask as a parameter. Blocks can be used as callbacks, which make them well suited to what you are trying to do ("when I'm done, do this() ") Ideally, this would have been passed into the method containing the above - but since this is forwardInvocation: , that gets a little more... challenging. You could set it as a property on this proxy object and read it from there.
Another approach would be to extend UIApplication with a category or informal protocol with a method like addDataTask: which you could call instead of your block, which would hand off responsbility for the "i just added a data task" to another receiver, most likely the application's delegate (and you can extend the UIApplicationDelegate protocol with a new method, application:didAddDataTask: to handle this). It sounds like your data task and activity indicator are application-level concerns, which may make this a good fit.
That said, I have some experience with almost exactly the problems you are trying to solve (token based authorization). I would suggest taking at a look at how ACAccountStore approaches this problem , it may offer some ideas for alternative implementations.
I'm calling an asynchronous function from my controller and I pass to it a reference to an error variable, let's say:
NSError *err=nil;
[self myAsyncTask:&err];
if the controller get deallocated, the err variable does not exist anymore, so the app crash with a BAD_ACCESS error (because the function try to change the value of the err variable). How can I deal with that?
Notice that none of the built-in framework methods use pass by reference error reporting with asynchronous calls - they all use delegation or blocks to communicate error status. Using blocks, your API could be written to be used like:
[self doAsyncTaskWithErrorHandler: ^(NSError *error) {
//Handle error
}];
The method signature could look like
- (void)doAsyncTaskWithErrorHandler:(void (^)(NSError *error))errorHandler;
And in the implementation, where you used to do *error = someError; do something like:
NSError *error = ...;
if (errorHandler) {
errorHandler(error);
}
If I'm not mistaken this isn't really an issue with object lifetime though - if the stack frame is popped before the error is set then the pattern in the question would likely cause a crash as well.
How can I deal with that?
If you have an asynchronous function in process, you have to ensure that any variables that it uses remain valid until that function completes. Objective-C's memory management lends itself nicely to this -- a method like your -myAsyncTask can retain its arguments until it no longer needs them. That way, even if the controller (to use your word) is deallocated, the objects referred to by the variables will remain valid.
Another way to do it is to use a block for the async functionality. Blocks automatically retain the resources that they use, so they effectively solve this problem for you.
I am dispatching a queue to download some flickr photos on a separate thread (in viewWillAppear). When I log the contents of the array inside the block, it shows everything perfectly:
dispatch_queue_t photoDowonload=dispatch_queue_create("photoDownload", NULL);
dispatch_async(photoDowonload, ^{
NSArray *photoList=[FlickrFetcher topPlaces]; //downloads flickr data
self.listOfCities=photoList;
NSLog(#"inside block: %#", self.listOfCities); //shows contents
});
but when I try to log the array that was set inside the block outside the block, it returns null.
dispatch_queue_t photoDowonload=dispatch_queue_create("photoDownload", NULL);
dispatch_async(photoDowonload, ^{
NSArray *photoList=[FlickrFetcher topPlaces];
self.listOfCities=photoList;
});
NSLog(#"after block: %#", self.listOfCities); //returns null
What's the problem here? self.listOfCities is set up as NSArray property so once it's set in the block, it should be accessible outside of it.
The code in the block is run asynchronously. So the code after the block is run before the code in the block has had a chance to run (or certainly complete at least).
I've just started learning Objective-c, and I can be blind for some kind of issues but I'm wondering what is the impact of the _dispatch_asynch_ on executing block of code shown above.
Docs says
The dispatch_async() and dispatch_sync() functions schedule blocks for concurrent execution within the dispatch framework.
Maybe NSLog is called before execution of code block and variable is not initialized yet.
#rmaddy You was faster.
Ok I figured this out. My goal was to update the tableView with the info returned by block.
The block execution was changing the array variable but that change was not getting shown.
The trick was to detect this change in the getter for the array as follows:
-(void) setListOfCities:(NSArray *)listOfCities
{
if (_listOfCities!=listOfCities)
{
_listOfCities=listOfCities;
[self.tableView reloadData]; //<-- reloads table after change
}
}
I'm using below code to download some data from the web. Am I right that I need to retain the data like I have done? Also the NSLog statement from inside the block shows that the array has been populated, but when I run the NSLog outside the block the arrays show as (null). How would I save the data outside dispatch_async method?
__block NSArray *downloadedCareerIds;
__block NSArray *diskCareerIds;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
/* Download stuff */
downloadedCareerIds = [[CareersParser idsFrom:#"web"] retain];
diskCareerIds = [[CareersParser idsFrom:#"disk"] retain];
DLog(#"downloadedCareerIds: %#", downloadedCareerIds);
DLog(#"diskCareerIds: %#", diskCareerIds);
});
DLog(#"downloadedCareerIds: %#", downloadedCareerIds);
DLog(#"diskCareerIds: %#", diskCareerIds);
The idea of dispatch_async is that you give it a block of code to execute asynchronously, therefore giving up any control of when that code gets executed. The call to dispatch_async returns once the block has been enqueued, NOT once the block has finished executing (hence async). Therefore, the log statements inside of the block you're passing to dispatch_async will get executed, almost always, after the log statements below your call to dispatch_async.
dispatch_async is a non blocking method so it will return immediately. So when the DLog statements outside the block are called, they will mostly not have been set. Hence you don't see the values you get from the internal log statements.
If you want to act on the data within the same method, you will have to either send a blocking dispatch_sync which is pointless or you can call the methods within the block.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
....
[self doStuffWithTheArrays];
});
Once the block is executed the objects will be available provided they are instance variables or you will lose the references.