How can I cause test method to wait till delegate has finished processing? - objective-c

I have the following Objective C test code in a SenTestCase class. I get no errors, but the httpReceiveDataFinished method never gets called. Is this because the test is ended before the delegate has a chance to process the http method?
If that is the case how can I spin off a thread (or something similar) to make the test wait for a few seconds?
Thanks a million for any help. I have programmed Java for years, but Objective-C only a few days.
- (void)testExample
{
HttpClient *client = [[HttpClient alloc] init];
client.method = METHOD_GET;
client.followRedirects = YES;
[client processRequest:#"http://google.com" delegate:self];
NSLog(#"Test Over");
}
-(void) httpReceiveError:(NSError*)error {
NSLog(#"***\n%#\n***",[error description]);
}
- (void) httpReceiveDataChunk:(NSData *)data {
[self.httpResponseData appendData:data];
}
-(void) httpReceiveDataFinished {
NSString *result = [[NSString alloc]
initWithData:self.httpResponseData
encoding:NSUTF8StringEncoding];
NSLog(#"***\nRESULT: %# \n***",result);
}

First: Stanislav's link is excellent.
For myself, I needed something that was more flexible (run for long durations) but would also pass the test immediately on success. (Large file downloads and the like.)
Here's my utility function:
-(BOOL)runLooperDooper:(NSTimeInterval)timeoutInSeconds
optionalTestName:(NSString *)testName
{
NSDate* giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds];
// loop until the operation completes and sets stopRunLoop = TRUE
// or until the timeout has expired
while (!stopRunLoop && [giveUpDate timeIntervalSinceNow] > 0)
{
// run the current run loop for 1.0 second(s) to give the operation code a chance to work
NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
[[NSRunLoop currentRunLoop] runUntilDate:stopDate];
}
STAssertTrue(stopRunLoop,
#"%# failed to finish before runloop expired after %f seconds", testName, timeoutInSeconds);
return stopRunLoop;
}
Declare stopRunLoop as an ivar in your tester-class. Make sure to reset stopRunLoop to FALSE in your setUp function, and call this function prior to calling your [client processRequest:#"http://google.com" delegate:self]; Then, in your event handlers, set stopRunLoop to TRUE.
By passing it a testName, you can reuse it for multiple tests and get some meaningful error messages.
Edit: 99% sure I based the above code off of another StackOverflow post that I can't find at the moment, or I'd link it.

Related

EXC_BAD_ACCESS upon block execution

I have a class with a single method, that uses a URLConnection to send a serialized NSDictionary to a script at a certain URL, and then calls a completion block. Here is the code for that method:
- (void)sendDictionary:(NSDictionary *)dictionary toScript:(NSString *)scriptName completion:(void (^) (id response))completionBlock
{
...Serialize data and add it to an NSURLRequest request...
H2URLConnection *connection = [[H2URLConnection alloc]initWithRequest:request];
//Define a semaphore to block execution of later statements until the signal is received.
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[connection setCompletionBlock:[^(id obj, NSError *err)
{
if (!err) {
//Catch the server response
NSString *receivedString = [[NSString alloc] initWithData:obj encoding:NSUTF8StringEncoding];
NSLog( #"ChecklistAppNetworkManager received string: %#", receivedString);
//Convert the JSON response into an NSDictionary
NSError *otherError;
id deserializedJSON = [NSJSONSerialization JSONObjectWithData:obj options:kNilOptions error:&otherError];
if (otherError) {
NSLog(#"ChecklistAppNetworkManager JSON Error: %#", otherError.description);
}
[completionBlock invoke];
NSLog(#"ChecklistAppNetworkManager JSON Response: %#", deserializedJSON);
//Dispatch the semaphore signal so that the main thread continues.
dispatch_semaphore_signal(sem);
} else {
NSLog(#"ChecklistAppNetworkManager encountered an error connecting to the server: %#", [err description]);
}
}copy]];
//Finalize and initate the connection.
[connection start];
//Since block is dispatched to main queue, stall with a loop until the semaphore signal arrives.
while (dispatch_semaphore_wait(sem, DISPATCH_TIME_NOW)) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]];
}
}
I'm trying to call this method on an instance of this class from within another class, where the completion block is defined. Here's the code where I get the EXC_BAD_ACCESS:
- (void)doSomeServerTask
{
H2ChecklistAppNetworkManager *currentNetworkManager = ((H2AppDelegate *)[[UIApplication sharedApplication]delegate]).networkManager; //Instantiate class where that method is defined
NSMutableDictionary *dictonary = [NSMutableDictionary dictionary];
...populate dictionary...
[currentNetworkManager sendDictionary:dictionary toScript:#"script.php" completion:[^(id response)
{ //THIS iS THE LINE WHERE THE BAD ACCESS OCCURS
NSLog(#"LoginViewController received response: %#", response);
} copy]];
}
Any help would be appreciated!
The completionBlock on that method takes one argument, but you call the block with the invoke method. More likely than not, the crash is because the runtime is trying to retain whatever garbage is in memory that should be that argument.
However, you really need to refactor this code entirely. Blocking the main event loop is bad. Running a sub-runloop is even worse on the MEL; it changes the way dispatch queue handling semantics work and can lead to pathologically bad performance or behavior.
You should move to a truly asynchronous model. If the app can't proceed until these queries are done, then put up a modal indicator that blocks progress.
To do that, you structure the code loosely as:
• put user interface into a "loading..." or some other modal state
• execute an asynchronous request for data with a completion handler
• in the completion handler, dispatch the "update UI" request to the main queue
• upon "update UI", tear down your modal "loading...." UI and update the display for the user
There is no need to block the main event loop to do any of this.

How to convert from synchronous to asynchronous NSURLConnection

I'm trying to update an old Mac OS program I wrote in ASOC (mostly Applescript, but some ObjC objects for things like web service access). I used a synchronous connection:
NSData *resultsData = [NSURLConnection sendSynchronousRequest: req returningResponse: &response error: &err];
The server credentials were embedded in the URL. This worked fine for me since the program really could not continue to do anything while the data was being fetched. A change to the server authentication method however has forced the need for changes to this application. I have tried all the usual workarounds with a NSURLCredential but that still does not work with this service.
So it looks like I will need to change to the asynchronous call:
[[NSURLConnection alloc] initWithRequest:request
delegate:self
startImmediately:YES];
I have this working with the appropriate delegate methods, most importantly:
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
Although I'd love to just use some form of delay loop to check for when the data has finished loading (essentially making it synchronous again), I have not found a way to do this that does not actually block the connection.
I am able to use a NSTimer to wait for the data before continuing:
set theJobListTimer to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(0.05, me, "jobListTimerFired:", "", true)
on jobListTimerFired_(theTimer)
(jobListData as list) & count of jobListData
if count of jobListData ≠ 0 then
log "jobListTimerFired_ done"
tell theTimer to invalidate()
setUpJobList(jobListData)
end if
end jobListTimerFired_
but this is clumsy and does not work while I'm in a modal dialog:
set buttonReturned to current application's NSApp's runModalForWindow_(collectionWindow)
(I have a drop down in the dialog that needs to be updated with the results of the web service call). Right now, the delegate methods are blocked until the modal is dismissed.
Is there no simple way to emulate the synchronous call using the async methods?
Trying to use semaphore, I changed code to:
- (void) startConnection:(int)reqType :(NSMutableURLRequest *)request {
requestType = [NSNumber numberWithInt:reqType];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// This could be any block that is run asynchronously
void (^myBlock)(void) = ^(void) {
self.connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self
startImmediately:YES];
myBlock();
if (self.connection) {
// create an object to hold the received data
self.receivedData = [NSMutableData data];
NSLog(#"connection started %#", requestType);
}
dispatch_time_t timeOut = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);
dispatch_semaphore_wait(semaphore, timeOut);
dispatch_release(semaphore);
semaphore = NULL;
}
then in the connection handler:
- (void) connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"connectionDidFinishLoading %#", requestType);
NSString *returnData = [[NSString alloc] initWithData:receivedData
encoding:NSUTF8StringEncoding] ;
// NSLog(#"connectionDidFinishLoading %#", returnData);
[self handleData:requestType :returnData];
[self terminate];
if(semaphore) {
dispatch_semaphore_signal(semaphore);
}
}
However, the connectionDidFinishLoading handler (and for that matter the didReceiveResponse and didReceiveData handlers) do not get called until after the 10 second dispatch timeout. What am I missing here?
You can use dispatch_semaphore_wait to make any asynchronous API into a synchronous one again.
Here's an example:
__block BOOL accessGranted = NO;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// This could be any block that is run asynchronously
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
accessGranted = granted;
if(semaphore) {
dispatch_semaphore_signal(semaphore);
}
});
// This will block until the semaphore has been signaled
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
semaphore = NULL;
return accessGranted;
Found the answer here:
iOS, NSURLConnection: Delegate Callbacks on Different Thread?
I knew the connection was running on a different thread and tried various other while loops to wait for it to finish. But this was REALLY the magic line:
while(!self->finished]){
//This line below is the magic!
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}

upload files in background via ftp on iphone

i'm trying to establish an FTP connection within an app. i want to upload several files to a FTP server, all files in one directory. So at first i want to create the remote directory.
- (void) createRemoteDir {
NSURL *destinationDirURL = [NSURL URLWithString: uploadDir];
CFWriteStreamRef writeStreamRef = CFWriteStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) destinationDirURL);
assert(writeStreamRef != NULL);
ftpStream = (__bridge_transfer NSOutputStream *) writeStreamRef;
BOOL success = [ftpStream setProperty: ftpUser forKey: (id)kCFStreamPropertyFTPUserName];
if (success) {
NSLog(#"\tsuccessfully set the user name");
}
success = [ftpStream setProperty: ftpPass forKey: (id)kCFStreamPropertyFTPPassword];
if (success) {
NSLog(#"\tsuccessfully set the password");
}
ftpStream.delegate = self;
[ftpStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
// open stream
[ftpStream open];
}
This code doesn't work when executed in a background thread using the following call:
[self performSelectorInBackground: #selector(createRemoteDir) withObject: nil];
my guess is that the (background-threads) runloop isn't running?
If i send the message inside the main thread the uploading just works fine:
[self createRemoteDir];
as the runloop of the main thread is up and running.
but fairly large files are going to be uploaded; so i want to put that workload in a background thread.
but how and where do i set up the NSRunLoop, so that the whole uploading happens in a background thread? Apples documentation on NSRunLoops (especially how to start them without using a timer/input source, as in this case) didn't help me out.
I found/created a solution that at least works for me.
with the above method (createRemoteDir), the following code applied and worked for me:
NSError *error;
createdDirectory = FALSE;
/*
only 'prepares' the stream for upload
- doesn't actually upload anything until the runloop of this background thread is run
*/
[self createRemoteDir];
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
do {
if(![currentRunLoop runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]) {
// log error if the runloop invocation failed
error = [[NSError alloc] initWithDomain: #"org.mJae.FTPUploadTrial"
code: 23
userInfo: nil];
}
} while (!createdDirectory && !error);
// close stream, remove from runloop
[ftpStream close];
[ftpStream removeFromRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];
if (error) {
// handle error
}
It runs in a background thread and creates the directory on the ftp server.
I like it more than other examples where runloops are only run for an assumed small interval, say 1second.
[NSDate distantFuture]
is a date in the futur (several centuries, according to Apples documentation). But that's good as the "break-condition" is handled by my class property createdDirectory - or the occurance of an error while starting the runloop.
I can't explain why it works without me explicitly attaching an input source to the runloop (NSTimer or NSPort), but my guess is, it is sufficient that the NSOutputStream is scheduled in the runloop of the background thread (see createRemoteDir).
You could also try to use a dispatch_async call to perform your createRemoteDir in the background. It's much simpler to use and you won't have to worry about managing extra threads.
Here's what the code would look like:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self createRemoteDir];
});

Perform block inside a NSOperation

I have a method in some class which performs some task using a block. When I execute that method using NSInvocationOperation then control never goes to the block. I tried logging inside the block but that is never called actually. But if I simply call that method with instance of that class then everything works as expected.
Don’t blocks run inside NSOperation?
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:myClassObj selector:#selector(myClassMethod:) object:obj1];
[[AppDelegate sharedOpQueue] addOperation:op];
[op release];
- (void)myClassMethod:(id)obj
{
AnotherClass *otherClass = [[AnotherClass allco] init]
[otherClass fetchXMLWithCompletionHandler:^(WACloudURLRequest* request, xmlDocPtr doc, NSError* error)
{
if(error){
if([_delegate respondsToSelector:#selector(handleFail:)]){
[_delegate handleFail:error];
}
return;
}
if([_delegate respondsToSelector:#selector(doSomeAction)]){
[_delegate doSomeAction];
}
}];
}
- (void) fetchXMLWithCompletionHandler:(WAFetchXMLHandler)block
{
_xmlBlock = [block copy];
[NSURLConnection connectionWithRequest:request delegate:self];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if(_xmlBlock) {
const char *baseURL = NULL;
const char *encoding = NULL;
xmlDocPtr doc = xmlReadMemory([_data bytes], (int)[_data length], baseURL, encoding, (XML_PARSE_NOCDATA | XML_PARSE_NOBLANKS));
NSError* error = [WAXMLHelper checkForError:doc];
if(error){
_xmlBlock(self, nil, error);
} else {
_xmlBlock(self, doc, nil);
}
xmlFreeDoc(doc);
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
if(_xmlBlock) {
_xmlBlock(self, nil, error);
}
}
You are performing your NSConnection asynchronously (which you don't need to do in an NSOperation because you should already be on a background thread).
After your call to fetchXMLWithCompletionHandler, your method ends. This signals that the NSOperation is finished and it gets released and it's thread gets either reused for something else or, more likely, released as well. This means that by the time you get your callbacks, your initial object doesn't exist anymore!
There are two solutions :
1) Use NSURLConnection synchronously. This will wait in your myClassMethod until it has got a response.
2) Learn about NSOperations's concurrent mode. I don't know if this will work with NSInvocationOperation though :( And it's fairly complicated compared to option (1).
I would use method (1) - you have already created a background thread to perform your operation in, why bother creating another one to do your connection request?
There are two ways of fixing your problem:
The easy way out
is — as Dean suggests — using +[NSURLConnection sendSynchronousRequest:returningResponse:error:], as you already are on a different thread. This has you covered — I'd say — 80-90% of the time, is really simple to implement and Just Works™.
The other way
is only slightly more complicated and has you covered for all the cases where the first method does not suffice — by visiting the root of your problem:
NSURLConnection works in conjunction with the runloop — and the threads managed by NSOperationQueue don't necessarily use (or even have!) an associated runloop.
While calling +[NSURLConnection connectionWithRequest:delegate:] will implicitly create a runloop, if needed, it does not cause the runloop to actually run!
This is your responsibility, when the NSOperationQueue you use is not the queue associated with the main thread.
To do so, change your implementation of fetchXMLWithCompletionHandler: to look similar to the following:
- (void)fetchXMLWithCompletionHandler:(WAFetchXMLHandler)block
{
self.xmlHandler = block; // Declare a #property for the block with the copy attribute set
self.mutableXMLData = [NSMutableData data]; // again, you should have a property for this...
self.currentConnection = [NSURLConnection connectionWithRequest:request delegate:self]; // having a #property for the connection allows you to cancel it, if needed.
self.connectionShouldBeRunning = YES; // ...and have a BOOL like this one, setting it to NO in connectionDidFinishLoad: and connection:didFailWithError:
NSRunLoop *loop = [NSRunLoop currentRunLoop];
NSDate *neverExpire = [NSDate distantFuture];
BOOL runLoopDidIterateRegularly = YES;
while( self.connectionShouldBeRunning && runLoopDidIterateRegularly ) {
runLoopDidIterateRegularly = [loop runMode:NSDefaultRunLoopMode beforeDate:neverExpire];
}
}
With these small changes, you're good to go. Bonus: this is really flexible and (eventually) reusable throughout all your code — if you move the XML-parsing out of that class and make your handler simply take an NSData, an NSError and (optionally) an NSURLResponse.
Since you probably don't want the clients of your loader to see and possibly mess with the properties I just suggested you should add, you can declare them in a class continuation.

OCUnit test for protocols/callbacks/delegate in Objective-C

Using OCUnit, is there a way to test delegate protocols?
I'm trying this, which doesn't work.
-(void) testSomeObjDelegate {
SomeObj obj = [[SomeObj alloc] initWithDelegate:self];
[obj executeMethod];
}
-(void) someObjDelegateMethod {
//test something here
}
I'm going to try calling the obj method on a different thread and have the test sleep until the delegate is called. It just seems like there should be an easier way to test this.
Testing a delegate is trivial. Just set an ivar in the test in your callback method, and check it after what should be triggering the delegate callback.
For example, if I have a class Something that uses a delegate of protocol SomethingDelegate and sends that delegate -something:delegateInvoked: in response to some message, I can test it lik ethis:
#interface TestSomeBehavior : SenTestCase <SomethingDelegate>
{
Something *_object;
BOOL _callbackInvoked;
}
#end
#implementation TestSomeBehavior
- (void)setUp {
[super setUp];
_object = [[Something alloc] init];
_object.delegate = self;
}
- (void)tearDown {
_object.delegate = nil;
[_object release];
[super tearDown];
}
- (void)testSomeBehaviorCallingBack {
[_object doSomethingThatShouldCallBack];
STAssertTrue(_callbackInvoked,
#"Delegate should send -something:delegateInvoked:");
}
- (void)something:(Something *)something delegateInvoked:(BOOL)invoked {
_callbackInvoked = YES;
}
#end
I think you already understand this, however, from the way you've phrased your question. (I'm mostly posting this for other readers.) I think you're actually asking a more subtle question: How do I test something that may occur later such as something that spins the runloop. My cue is your mention of sleeping and threading.
First off, you should not just arbitrarily invoke a method on another thread. You should only do so if it's documented to be safe to use in that way. The reason is that you don't know what the internals of the class do. For example, it might schedule events on the run loop, in which case running the method on a different thread will make them happen on a different run loop. This would then screw up the class's internal state.
If you do need to test something that may take a little time to happen, you can do this just by running the current run loop. Here's how I might rewrite the individual test method above to do that:
- (void)testSomeBehaviorCallingBack {
NSDate *fiveSecondsFromNow = [NSDate dateWithTimeIntervalSinceNow:5.0];
[_object doSomethingThatShouldCallBack];
[[NSRunLoop currentRunLoop] runUntilDate:fiveSecondsFromNow];
STAssertTrue(_callbackInvoked,
#"Delegate should send -something:delegateInvoked:");
}
This will spin the current run loop in the default mode for 5 seconds, under the assumption that -doSomethingThatShouldCallBack will schedule its work on the main run loop in the default mode. This is usually OK because APIs that work this way often let you specify a run loop to use as well as a mode to run in. If you can do that, then you can use -[NSRunLoop runMode:beforeDate:] to run the run loop in just that mode instead, making it more likely that the work you're expecting to be done will be.
Please, review Unit Testing Asynchronous Network Access. I think can help you.
In short what it does is:
Add the following method which will take care of the synchronization
between the unit test code and the asynchronous code under test:
- (BOOL)waitForCompletion:(NSTimeInterval)timeoutSecs {
NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs];
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate];
if([timeoutDate timeIntervalSinceNow] < 0.0)
break;
} while (!done);
return done;
}