How to stub method block in Kiwi? - objective-c

I want to stub a method which takes a block as a parameter using Kiwi. Here is the full explanation with code:
I have a class named TestedClass which has a method testedMethod which dependent on class NetworkClass which calls via AFNetworking to a server, and return its response via block. Translating to code:
#interface TestedClass : NSObject
-(void)testMethod;
#end
-(void)testMethod
{
NetworkClass *networkClass = [[NetworkClass alloc] init];
[networkClass networkMethod:^(id result)
{
// code that I want to test according to the block given which I want to stub
...
}];
}
typedef void (^NetworkClassCallback)(id result);
#interface NetworkClass : NSObject
-(void)networkMethod:(NetworkClassCallback)handler;
#end
-(void) networkMethod:(NetworkClassCallback)handler
{
NSDictionary *params = #{#"param":#", #"value"};
NSString *requestURL = [NSString stringWithFormat:#"www.someserver.com"];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURLURLWithString:requestURL]];
NSURLRequest *request = [httpClient requestWithMethod:#"GET" path:requestURL parameters:params];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
handler(responseObject);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
handler(nil);
}];
[operation start];
}
How can I use Kiwi to stub networkMethod with block in order to unit test testMethod?
UPDATE: Found how to do this in Kiwi, see my answer below.

Here is how you do this in Kiwi:
First, you must dependency inject NetworkClass to TestedClass (if it's not clear how, please add a comment and I'll explain; this can be done as a property for simplicity. This is so that you can operate on a mock object for the NetworkClass)
Then your spec, create the mock for the network class and create your class that you want to unit test:
SPEC_BEGIN(TestSpec)
describe(#"describe goes here", ^{
it(#"should test block", ^{
NetworkClass *mockNetworkClass = [NetworkClass mock];
KWCaptureSpy *spy = [mockNetworkClass captureArgument:#selector(networkMethod:) atIndex:0];
TestedClass testClass = [TestedClass alloc] init];
testClass.networkClass = mockNetworkClass;
[testClass testMethod];
NetworkClassCallback blockToRun = spy.argument;
blockToRun(nil);
// add expectations here
});
});
SPEC_END
To explain what's going on here:
You are creating TestedClass and calling testMethod. However, before that, we are creating something called Spy - its job is to capture the block in the first parameter when networkMethod: is called. Now, it's time to actually execute the block itself.
It's easy to be confused here so I'll emphasize this: the order of calls is important; you first declare the spy, then call the tested method, and only then you're actually calling and executing the block!
This will give you the ability to check what you want as you're the one executing the block.
Hope it helps for other, as it took me quite sometime to understand this flow.

Related

Nested completionBlock is not getting called objc

This is my nested block, please take have a look :
- (void)getVideoList:(NSDictionary*)videoData
completionBlock:(void (^)(NSMutableArray *))
completionBlock {
NSArray *videos = (NSArray*)[videoData objectForKey:#"items"];
NSMutableArray* videoList = [[NSMutableArray alloc] init];
for (NSDictionary *videoDetail in videos) {
if (videoDetail[#"id"][#"videoId"]){
[self initializeDictionary:videoDetail completionBlock:^(YoutubeVideo * utubeVideo) {
[videoList addObject:utubeVideo];
// NSLog(#"zuuudo %#", utubeVideo.channelProfileImageURL);
}];
}
}
completionBlock(videoList);
}
- (void)initializeDictionary:(NSDictionary *)dictionary completionBlock:(void (^)(YoutubeVideo *))
completionBlock {
YoutubeVideo *youtubeVideo = [[YoutubeVideo alloc] init];
youtubeVideo.videoTitle = dictionary[#"snippet"][#"title"];
youtubeVideo.videoID = dictionary[#"id"][#"videoId"];
youtubeVideo.channelID = dictionary[#"snippet"][#"channelId"];
[self getChannelProfilePictureForChannelID:youtubeVideo.channelID completionBlock:^(NSMutableArray *channelList) {
NSLog(#"[channelList objectAtIndex:0] %#", [channelList objectAtIndex:0]);
youtubeVideo.channelProfileImageURL = [channelList objectAtIndex:0];
}];
youtubeVideo.channelTitle = dictionary[#"snippet"][#"channelTitle"];
youtubeVideo.videoDescription = dictionary[#"snippet"][#"description"];
youtubeVideo.pubDate = [self dateWithJSONString:dictionary[#"snippet"][#"publishedAt"]];
youtubeVideo.thumbnailURL = dictionary[#"snippet"][#"thumbnails"]
[#"high"][#"url"];
completionBlock(youtubeVideo);
}
- (void)getChannelProfilePictureForChannelID:(NSString*)channelID completionBlock:(void (^)(NSMutableArray *))completionBlock
{
NSString *URL = [NSString stringWithFormat:#"https://www.googleapis.com/youtube/v3/channels?part=snippet&fields=items/snippet/thumbnails/default&id=%#&key=%#", channelID, apiKey];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:[URL stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]]];
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithRequest:request
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if (!error){
[self getChannelProfileImageList:[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] completionBlock:
^(NSMutableArray * channelList) {
// return the final list
completionBlock(channelList);
}];
}
else {
// TODO: better error handling
NSLog(#"error = %#", error);
}
}] resume];
}
- (void)getChannelProfileImageList:(NSDictionary*)channelData
completionBlock:(void (^)(NSMutableArray *))
completionBlock {
NSArray *channels = (NSArray*)[channelData objectForKey:#"items"];
NSMutableArray *channelList = [[NSMutableArray alloc] init];
for (NSDictionary *channelDetail in channels) {
[self initializeDictionaryForChannelProfileImage:channelDetail completionBlock:^(NSString *chnlProfileImageURL) {
[channelList addObject:chnlProfileImageURL];
}];
//[channelList addObject:[self initializeDictionaryForChannelProfileImage:channelDetail]];
//[channelList addObject:[[YoutubeVideo alloc] initWithDictionaryForChannelProfileImage:channelDetail]];
}
completionBlock(channelList);
}
- (void)initializeDictionaryForChannelProfileImage:(NSDictionary *)dictionary completionBlock:(void (^)(NSString *))
completionBlock
{
_channelProfileImageURL = dictionary[#"snippet"][#"thumbnails"]
[#"default"][#"url"];
completionBlock(_channelProfileImageURL);
}
Problem is in this - (void)initializeDictionary:(NSDictionary *)dictionary completionBlock:(void (^)(YoutubeVideo *))
completionBlock {
} block, has the below block
[self getChannelProfilePictureForChannelID:youtubeVideo.channelID completionBlock:^(NSMutableArray *channelList) {
NSLog(#"[channelList objectAtIndex:0] %#", [channelList objectAtIndex:0]);
youtubeVideo.channelProfileImageURL = [channelList objectAtIndex:0];
}];
Where these line of code is not executing when the block return value NSSting value.
youtubeVideo.channelProfileImageURL = _channelProfileImageURL;
NSLog(#"youtubeVideo.channelProfileImageURL %#", youtubeVideo.channelProfileImageURL);
It is getting called after executing rest of the code:
youtubeVideo.channelTitle = dictionary[#"snippet"][#"channelTitle"];
youtubeVideo.videoDescription = dictionary[#"snippet"][#"description"];
youtubeVideo.pubDate = [self dateWithJSONString:dictionary[#"snippet"][#"publishedAt"]];
youtubeVideo.thumbnailURL = dictionary[#"snippet"][#"thumbnails"]
[#"high"][#"url"];
So the value is not inserting in my object model.
Please give me a suggestion. Thanks in advance.
Have a good day.
It is getting called after executing rest of the code
You are mixing up asynchronous execution with an expectation that code will be executed synchronously:
- (void)initializeDictionary:(NSDictionary *)dictionary
completionBlock:(void (^)(YoutubeVideo *))completionBlock
{
This is a typical declaration for an asynchronous method where the completionBlock argument should be called asynchronously after the all the work of initializeDictionary has been completed.
YoutubeVideo *youtubeVideo = [[YoutubeVideo alloc] init];
youtubeVideo.videoTitle = dictionary[#"snippet"][#"title"];
youtubeVideo.videoID = dictionary[#"id"][#"videoId"];
youtubeVideo.channelID = dictionary[#"snippet"][#"channelId"];
Three synchronous assignments.
[self getChannelProfilePictureForChannelID:youtubeVideo.channelID
completionBlock:^(NSMutableArray *channelList)
{
NSLog(#"[channelList objectAtIndex:0] %#", [channelList objectAtIndex:0]);
youtubeVideo.channelProfileImageURL = [channelList objectAtIndex:0];
}
];
This is a nested call to another asynchronous method, which will call its completion block after it has finished. At the point it returns it probably has not yet called its competition block.
youtubeVideo.channelTitle = dictionary[#"snippet"][#"channelTitle"];
youtubeVideo.videoDescription = dictionary[#"snippet"][#"description"];
youtubeVideo.pubDate = [self dateWithJSONString:dictionary[#"snippet"][#"publishedAt"]];
youtubeVideo.thumbnailURL = dictionary[#"snippet"][#"thumbnails"]
[#"high"][#"url"];
Four more synchronous assignments...
completionBlock(youtubeVideo);
And then you call the completion block of initializeDictionary: before you know that getChannelProfilePictureForChannelID: has completed and called its completion block.
}
If you are writing an asynchronous method which itself needs to call an asynchronous method then you have to complete your method in the nested asynchronous method's completion...
Yes that's a bit confusing in words! Let's rearrange your method:
- (void)initializeDictionary:(NSDictionary *)dictionary
completionBlock:(void (^)(YoutubeVideo *))completionBlock
{
YoutubeVideo *youtubeVideo = [[YoutubeVideo alloc] init];
youtubeVideo.videoTitle = dictionary[#"snippet"][#"title"];
youtubeVideo.videoID = dictionary[#"id"][#"videoId"];
youtubeVideo.channelID = dictionary[#"snippet"][#"channelId"];
youtubeVideo.channelTitle = dictionary[#"snippet"][#"channelTitle"];
youtubeVideo.videoDescription = dictionary[#"snippet"][#"description"];
youtubeVideo.pubDate = [self dateWithJSONString:dictionary[#"snippet"][#"publishedAt"]];
youtubeVideo.thumbnailURL = dictionary[#"snippet"][#"thumbnails"]
[#"high"][#"url"];
Do all the synchronous assignments first, then do the nested asynchronous call:
[self getChannelProfilePictureForChannelID:youtubeVideo.channelID
completionBlock:^(NSMutableArray *channelList)
{
NSLog(#"[channelList objectAtIndex:0] %#", [channelList objectAtIndex:0]);
youtubeVideo.channelProfileImageURL = [channelList objectAtIndex:0];
At this point the completion block of getChannelProfilePictureForChannelID has done what you want it to, now do any remaining work that initializeDictionary: needs to do after getChannelProfilePictureForChannelID completes. This is not much in this case, just call initializeDictionary: competition:
completionBlock(youtubeVideo);
}
];
}
HTH
Addendum
From your comments I think you are misunderstanding how asynchronous chaining needs to work. Let's see if the following helps.
The method you wrote has for format:
A - block of work to do before async nested call
B - async call
nested async completion block
C - block of work to do after nested call completes
D - second block of work
E - call our async completion block
When you call this method A, B, D & E will execute in order and then the method will return. You've no idea when C will execute and there is no guarantee it will execute before E, indeed with async network calls in all probability it will not (so you're unlikely to even get accidental correctness).
To do a sequence of async operations you need to chain them via the continuation blocks. So you can change your method to:
A - block of work to do before async nested call
B - async call
nested async completion block
C - block of work to do after nested call completes
D - second block of work
E - call our async completion block
Putting D & E into the nested completion block. Now when you call your method only A & B execute before it returns. At some later point the nested completion block is executed asynchronously and C and D are executed. Finally E is executed, the completion block of the original call, thus completing the work. You've now guaranteed correctness, E will only be executed after the nested async call has completed.
Note: What I noticed when reading your code was that block D (the set of four assignments in your code) did not seem to be required to be executed after the nested call so I rearranged your code as:
A & D - block of work to do before async nested call
B - async call
nested async completion block
C - block of work to do after nested call completes
E - call our async completion block
hoisting D to the top.
This chaining of asynchronous calls is fundamental when you have an async method which itself relies on another async method – at every stage you must use the completion block chain to execute code it the correct order.
HTH

Writing iPhone 6 altimeter readings to an array class property inside queue block?

New iOS developer here. I've been searching for an answer to this in documentation on blocks and the altimeter, but I'm coming up short. I assume there's some simple thing I'm missing, but can't figure it out.
I have a custom class called PressureSensor. Simplistically speaking, the class has a property:
#property (nonatomic, strong, readwrite) NSMutableArray *pressure;
I load NSNumber values from the altimeter into this array.
The initializer for the class is:
- (instancetype)init
{
self = [super init];
if (self)
{
if (self.altimeterIsAvailable)
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[self.altimeter startRelativeAltitudeUpdatesToQueue:queue withHandler:^(CMAltitudeData *altitudeData, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^ {
[self.pressure addObject:altitudeData.pressure];
NSLog(#"Pressure 1: %#", [self.pressure lastObject]);
});
}];
NSLog(#"Pressure 2: %#", [self.pressure lastObject]);
}
}
return self;
}
When I run the app on my phone, I assume that pressure is successfully added to the self.pressure array, because the pressure is printed to the console by the "Pressure 1" line, which accesses the lastObject of self.pressure. However, it seems that these changes don't hold outside this block, as the Pressure 2 line outputs (null) to the console, and it doesn't seem like I can do anything with self.pressure outside this block.
Am I missing something about how blocks work exactly? Do I just need a __block somewhere? I'm completely at a loss here.
Addendum: self.altimeterIsAvailable is defined elsewhere. That part of the code shouldn't have any issues.
EDIT: The error ended up being elsewhere. For future readers who browse this post, the above code should be a perfectly valid way to add to a property array in a block.
This is not an answer but I'd like to mention it.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[self.altimeter startRelativeAltitudeUpdatesToQueue:queue withHandler:^(CMAltitudeData *altitudeData, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^ {
...
});
}];
Creating a queue, and dispatch_async to the main queue. It's redundant. You can use NSOperationQueue +mainQueue method for it directly.
NSOperationQueue *queue = [NSOperationQueue mainQueue];
[self.altimeter startRelativeAltitudeUpdatesToQueue:queue withHandler:^(CMAltitudeData *altitudeData, NSError *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.

Obj-C: How to get and call a block argument from NSInvocation - stubbing Twitter account on iOS

I'm testing an iOS application using KIF and OCMock, stubbing the device's ACAccountStore to return my own representation of a Twitter account. I want to stub requestAccessToAccountsWithType, and call the passed completion handler with my own values, but I can't seem to get the block from the invocation and call it properly (EXC_BAD_ACCESS). Being new to Objective-C and iOS, I'm sure I'm doing something wrong while pulling the block out of the NSInvocation.
This is the production code. The _accountStore is injected from the test setup.
ACAccountType *twitterType = [_accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
[_accountStore requestAccessToAccountsWithType:twitterType withCompletionHandler:^(BOOL granted, NSError *error) {
NSLog(#"authorized=%i in block", granted);
// important code here
}];
Test setup code.
ACAccountStore *realStore = [[ACAccountStore alloc] init];
// Stub the store to return our stubbed Twitter Account
id mockStore = [OCMockObject partialMockForObject:realStore];
[[[mockStore stub] andDo:^(NSInvocation *invocation) {
void (^grantBlock)(BOOL granted, NSError *error) = nil;
[invocation getArgument:&grantBlock atIndex:1]; // EXC_BAD_ACCESS
grantBlock(TRUE, nil); // run the important code with TRUE
}] requestAccessToAccountsWithType:[OCMArg any] withCompletionHandler:[OCMArg any]];
// not shown: inject the mockStore into production code
I think you should be using an index of 3, not 1. Index 0 is self, and index 1 is _cmd.

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.