recycleURLs:completionHandler: with NIL makes it block until completion? - objective-c

I was sending a file to the trash with
[NSWorkSpace recycleURLs: myArrayOfOneNsurl
completionHandler: NIL]
myArrayOfOneNsurl is a NSArray created with arrayWithObject: of a single NSURL that was created for an absolute file path with fileURLWithPath:isDirectory:.
The normal way to tell if it is successful is to use the completionHandler and then check if the 2nd arg (NSError) is NIL, which means success.
Is there anyway to check success without this callback? Currently I have setup a loop after calling this, to check for file existence, if 1second has past and it still exists, I declare fail. I was wondering if I set NIL as second arg to recycleURLs:completionHandler: does it make it block until the process completes (regardless of success)? In my tests, the very first check always finds that the file is no longer at its original place (meaning it was trashed), but I'm not sure if my computer is just super fast, or it really is blocking until file operation completes.

For trashing a single file or directory, you can use NSFileManager trashItemAtURL instead. It is synchronous, so you avoid the headaches with the completion callback.
NSError * error = nil;
[[NSFileManager defaultManager] trashItemAtURL:url resultingItemURL:nil error:&error];

This API is async. Passing NULL as completionHandler only means, that you are not interested in result. The only way to know when operation finished and was it successful is using completionHandler.
If you really need to make it sync, you may use following approach (although I don't recommend that):
__block BOOL recycleFinished = NO;
__block NSError *recycleError = nil;
[[NSWorkspace sharedWorkspace] recycleURLs:myArrayOfOneNsurl
completionHandler:^(NSDictionary<NSURL *,NSURL *> * _Nonnull newURLs, NSError * _Nullable error) {
recycleFinished = YES;
recycleError = error;
}];
while (!recycleFinished) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:.5]];
}
// access recycleError
NSLog(#"%#", recycleError);

Related

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

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.

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.

NSUserScriptTask difficulties

I've been trying to make do (see this and this) with the recent NSUserScriptTask class and its subclasses and so far I've solved some problems, but some others remain to be solved. As you can see from the docs, NSUserScriptTask does not allow for the cancellation of tasks. So, I decided to create a simple executable that takes as arguments the path to the script and runs the script. That way, I can launch the helper from my main app using NSTask and call [task terminate] when necessary. However, I require:
The main app to receive output and errors from the helper it launched
The helper only terminating when the NSUserScriptTask is done
The code for the main app is simple: just launch an NSTask with the proper info. Here's what I have now (for the sake of simplicity I ignored the code for security-scoped bookmarks and the like, which are out of the problem. But don't forget this is running sandboxed):
// Create task
task = [NSTask new];
[task setLaunchPath: [[NSBundle mainBundle] pathForResource: #"ScriptHelper" ofType: #""]];
[task setArguments: [NSArray arrayWithObjects: scriptPath, nil]];
// Create error pipe
NSPipe* errorPipe = [NSPipe new];
[task setStandardError: errorPipe];
// Create output pipe
NSPipe* outputPipe = [NSPipe new];
[task setStandardOutput: outputPipe];
// Set termination handler
[task setTerminationHandler: ^(NSTask* task){
// Save output
NSFileHandle* outFile = [outputPipe fileHandleForReading];
NSString* output = [[NSString alloc] initWithData: [outFile readDataToEndOfFile] encoding: NSUTF8StringEncoding];
if ([output length]) {
[output writeToFile: outputPath atomically: NO encoding: NSUTF8StringEncoding error: nil];
}
// Log errors
NSFileHandle* errFile = [errorPipe fileHandleForReading];
NSString* error = [[NSString alloc] initWithData: [errFile readDataToEndOfFile] encoding: NSUTF8StringEncoding];
if ([error length]) {
[error writeToFile: errorPath atomically: NO encoding: NSUTF8StringEncoding error: nil];
}
// Do some other stuff after the script finished running <-- IMPORTANT!
}];
// Start task
[task launch];
Remember, I need the termination handler to only run when: (a) the task was cancelled (b) the task terminated on its own because the script finished running.
Now, on the helper side things start to get hairy, at least for me. Let's imagine for the sake of simplicity that the script is an AppleScript file (so I use the NSUserAppleScriptTask subclass - on the real world I'd have to accomodate for the three types of tasks). Here's what I got so far:
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSString* filePath = [NSString stringWithUTF8String: argv[1]];
__block BOOL done = NO;
NSError* error;
NSUserAppleScriptTask* task = [[NSUserAppleScriptTask alloc] initWithURL: [NSURL fileURLWithPath: filePath] error: &error];
NSLog(#"Task: %#", task); // Prints: "Task: <NSUserAppleScriptTask: 0x1043001f0>" Everything OK
if (error) {
NSLog(#"Error creating task: %#", error); // This is not printed
return 0;
}
NSLog(#"Starting task");
[task executeWithAppleEvent: nil completionHandler: ^(NSAppleEventDescriptor *result, NSError *error) {
NSLog(#"Finished task");
if (error) {
NSLog(#"Error running task: %#", error);
}
done = YES;
}];
// Wait until (done == YES). How??
}
return 0;
}
Now, I have three questions (which are the ones I want to ask with this SO entry). Firstly, "Finished task" never gets printed (the block never gets called) because the task never even starts executing. Instead, I get this on my console:
MessageTracer: msgtracer_vlog_with_keys:377: odd number of keys (domain: com.apple.automation.nsuserscripttask_run, last key: com.apple.message.signature)
I tried running the exact same code from the main app and it completes without a fuss (but from the main app I lose the ability to cancel the script).
Secondly, I only want to reach the end of main (return 0;) after the completion handler is called. But I have no idea how to do that.
Thridly, whenever there's an error or output from the helper I want to send that error/output back to the app, which will receive them through the errorPipe/outputPipe. Something like fprintf(stderr/stdout, "string") does the trick, but I'm not sure if it is the right way to do it.
So, in short, any help regarding the first and second problems is appreciated. The third one I just want to make sure that's how I'm supposed to do it.
Thanks
Question 1: The sub-task doesn't run because its parent exits immediately. (The log message about "odd number of keys" is a bug in NSUserScriptTask, and happens because your helper doesn't have a bundle identifier, but is otherwise harmless and irrelevant to your problem.) It exits immediately because it's not waiting for the completion block to fire, which brings us to...
Question 2: How do you wait for an asynchronous completion block? This has been answered elsewhere, including Wait until multiple networking requests have all executed - including their completion blocks, but to recap, use dispatch groups, something like this:
dispatch_group_t g = dispatch_group_create();
dispatch_group_enter(g);
[task executeWithAppleEvent:nil completionHandler:^(NSAppleEventDescriptor *result, NSError *e) {
...
dispatch_group_leave(g);
}];
dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
dispatch_release(g);
This same pattern works for any call that has a completion block you want to wait for. If you wanted another notification when the group finishes instead of waiting for it, use dispatch_group_notify instead of dispatch_group_wait.
As a side note, the way you’re testing error after allocating the NSUserAppleScriptTask is incorrect. The value of error is defined if and only if the function result is nil (or NO, or whatever indicates failure). If the function succeeds (which you know if it returns non-nil), then error may be anything -- the function may set it to nil, it may leave it undefined, it may even fill it in with a real object. (See also What's the Point of (NSError**)error?)

Using blocks within blocks in Objective-C: EXC_BAD_ACCESS

Using iOS 5's new TWRequest API, I've ran into a brick wall related with block usage.
What I need to do is upon receiving a successful response to a first request, immediately fire another one. On the completion block of the second request, I then notify success or failure of the multi-step operation.
Here's roughly what I'm doing:
- (void)doRequests
{
TWRequest* firstRequest = [self createFirstRequest];
[firstRequest performRequestWithHandler:^(NSData* responseData,
NSHTTPURLResponse* response,
NSError* error) {
// Error handling hidden for the sake of brevity...
TWRequest* secondRequest = [self createSecondRequest];
[secondRequest performRequestWithHandler:^(NSData* a,
NSHTTPURLResponse* b,
NSError* c) {
// Notify of success or failure - never reaches this far
}];
}];
}
I am not retaining either of the requests or keeping a reference to them anywhere; it's just fire-and-forget.
However, when I run the app, it crashes with EXC_BAD_ACCESS on:
[secondRequest performRequestWithHandler:...];
It executes the first request just fine, but when I try to launch a second one with a handler, it crashes. What's wrong with that code?
The methods to create the requests are as simple as:
- (TWRequest*)createFirstRequest
{
NSString* target = #"https://api.twitter.com/1/statuses/home_timeline.json";
NSURL* url = [NSURL URLWithString:target];
TWRequest* request = [[TWRequest alloc]
initWithURL:url parameters:params
requestMethod:TWRequestMethodGET];
// _twitterAccount is the backing ivar for property 'twitterAccount',
// a strong & nonatomic property of type ACAccount*
request.account = _twitterAccount;
return request;
}
Make sure you're keeping a reference/retaining the ACAccountStore that owns the ACAccount you are using to sign the TWRequests.
If you don't, the ACAccount will become invalid and then you'll get EXC_BAD_ACCESS when trying to fire a TWRequest signed with it.
I'm not familiar with TW*, so consider this a wild guess ... try sending a heap-allocated block:
[firstRequest performRequestWithHandler:[^ (NSData *responseData, ...) {
...
} copy]];
To clarify, I think the block you're sending is heap-allocated, so while TW* might be retaining it, it won't make any difference if it has already gone out of scope.