Syntax/formatting when nesting objective-c blocks - objective-c

I'm nesting blocks, and it looks UGGGGLY. Is there a way to write this less ugly? Mostly looking for syntax suggestions, rather than structural, but I'll accept either.
My block factory method,
-(NSImage *(^)(CGFloat size, BOOL preview))resizeBlock {
return (NSImage *(^)(CGFloat size, BOOL preview))[[^(CGFloat size, BOOL preview){
// image-resizing code
return [[[NSImage alloc] init] autorelease];
} copy] autorelease];
}
Which is called from a number of functions similar to this,
-(void)queueResize:(CGFloat)targetSize toView:(NSImageView *)targetView {
NSImage*(^sizeBlock)(CGFloat,BOOL) = [self resizeBlock];
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^(void) {
NSImage *previewImage = (NSImage*)sizeBlock(targetSize,YES);
targetView.image = previewImage;
}];
[queue addOperation:bo];
}
queue is an NSOperationQueue object. It won't compile without all the (ugly ugly) casting. Amidoinitrite?
Edit:
As per Dave DeLong's answer, and http://www.cimgf.com/2008/02/16/cocoa-tutorial-nsoperation-and-nsoperationqueue/, I changed the line
targetView.image = previewImage;
to be,
[targetView performSelectorOnMainThread:#selector(setImage:) withObject:previewImage waitUntilDone:YES];

Use typedef:
typedef NSImage *(^KWResizerBlock)(CGFloat size, BOOL preview);
This makes your code become:
- (KWResizerBlock) resizeBlock {
KWResizerBlock block = ^(CGFloat size, BOOL preview){
// image-resizing code
return [[[NSImage alloc] init] autorelease];
};
return [[block copy] autorelease];
}
-(void)queueResize:(CGFloat)targetSize toView:(NSImageView *)targetView {
KWResizerBlock sizeBlock = [self resizeBlock];
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSImage *previewImage = sizeBlock(targetSize, YES);
//do something with previewImage
}];
[queue addOperation:bo];
}
One word of caution:
Your NSBlockOperation is going to be executing on a thread that's not the main thread, and so it is unsafe to manipulate any UI element from within that context. If you need to put the previewImage onto the UI, then you should dispatch_async() back to the main thread (or something functionally equivalent).
It may work right now, but it is strongly discourage and can lead to undefined behavior.

Related

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) {
...
}];

Pre-empting NSOperation on one NSOperationQueue with NSOperation placed onto a separate NSOperationQueue?

I have an application in which a long running process (> 1 min) is placed onto an NSOperationQueue (Queue A). The UI is fully-responsive while the Queue A operation runs, exactly as expected.
However, I have a different kind of operation the user can perform which runs on a completely separate NSOperationQueue (Queue B).
When a UI event triggers the placement of an operation on Queue B, it must wait until after the currently-executing operation on Queue A finishes. This occurs on an iPod Touch (MC544LL).
What I expected to see instead was that any operation placed onto Queue B would more or less begin immediately executing in parallel with the operation on Queue A. This is the behavior I see on the Simulator.
My question is two parts:
Is the behavior I'm seeing on my device to be expected based on available documentation?
Using NSOperation/NSOperationQueue, how do I pre-empt the currently running operation on Queue A with a new operation placed on Queue B?
Note: I can get exactly the behavior I'm after by using GCD queues for Queues A/B, so I know my device is capable of supporting what I'm trying to do. However, I really, really want to use NSOperationQueue because both operations need to be cancelable.
I have a simple test application:
The ViewController is:
//
// ViewController.m
// QueueTest
//
#import "ViewController.h"
#interface ViewController ()
#property (strong, nonatomic) NSOperationQueue *slowQueue;
#property (strong, nonatomic) NSOperationQueue *fastQueue;
#end
#implementation ViewController
-(id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
self.slowQueue = [[NSOperationQueue alloc] init];
self.fastQueue = [[NSOperationQueue alloc] init];
}
return self;
}
-(void)viewDidLoad
{
NSLog(#"View loaded on thread %#", [NSThread currentThread]);
}
// Responds to "Slow Op Start" button
- (IBAction)slowOpStartPressed:(id)sender {
NSBlockOperation *operation = [[NSBlockOperation alloc] init];
[operation addExecutionBlock:^{
[self workHard:600];
}];
[self.slowQueue addOperation:operation];
}
// Responds to "Fast Op Start" button
- (IBAction)fastOpStart:(id)sender {
NSBlockOperation *operation = [[NSBlockOperation alloc] init];
[operation addExecutionBlock:^{
NSLog(#"Fast operation on thread %#", [NSThread currentThread]);
}];
[self.fastQueue addOperation:operation];
}
-(void)workHard:(NSUInteger)iterations
{
NSLog(#"SlowOperation start on thread %#", [NSThread currentThread]);
NSDecimalNumber *result = [[NSDecimalNumber alloc] initWithString:#"0"];
for (NSUInteger i = 0; i < iterations; i++) {
NSDecimalNumber *outer = [[NSDecimalNumber alloc] initWithUnsignedInteger:i];
for (NSUInteger j = 0; j < iterations; j++) {
NSDecimalNumber *inner = [[NSDecimalNumber alloc] initWithUnsignedInteger:j];
NSDecimalNumber *product = [outer decimalNumberByMultiplyingBy:inner];
result = [result decimalNumberByAdding:product];
}
result = [result decimalNumberByAdding:outer];
}
NSLog(#"SlowOperation end");
}
#end
The output I see after first pressing the "Slow Op Start" button followed ~1 second later by pressing the "Fast Op Start" button is:
2012-11-28 07:41:13.051 QueueTest[12558:907] View loaded on thread <NSThread: 0x1d51ec30>{name = (null), num = 1}
2012-11-28 07:41:14.745 QueueTest[12558:1703] SlowOperation start on thread <NSThread: 0x1d55e5f0>{name = (null), num = 3}
2012-11-28 07:41:25.127 QueueTest[12558:1703] SlowOperation end
2012-11-28 07:41:25.913 QueueTest[12558:3907] Fast operation on thread <NSThread: 0x1e36d4c0>{name = (null), num = 4}
As you can see, the second operation does not begin executing until after the first operation finishes, despite the fact that these are two separate (and presumably independent) NSOperationQueues.
I have read the Apple Concurrency Guide, but find nothing describing this situation. I've also read two SO questions on related topics (link, link), but neither seems to get to the heart of the problem I'm seeing (pre-emption).
Other things I've tried:
setting the queuePriority on each NSOperation
setting the queuePriority on each NSOperation while placing both types of operations onto the same queue
placing both operations onto the same queue
This question has undergone multiple edits, which may make certain comments/answers difficult to understand.
I suspect the problem you are having is that both operation queues are executing their blocks on the underlying default priority dispatch queue. Consequently, if several slow operations are enqueued before the fast operations then perhaps you will see this behaviour.
Why not either set the NSOperationQueue instance for the slow operations so that it only executes one operation at any given time (i.e. set maxConcurrentOperationCount to one for this queue), or if your operations are all blocks then why not use GCD queues directly? e.g.
static dispatch_queue_t slowOpQueue = NULL;
static dispatch_queue_t fastOpQueue = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
slowOpQueue = dispatch_queue_create("Slow Ops Queue", NULL);
fastOpQueue = dispatch_queue_create("Fast Ops Queue", DISPATCH_QUEUE_CONCURRENT);
});
for (NSUInteger slowOpIndex = 0; slowOpIndex < 5; slowOpIndex++) {
dispatch_async(slowOpQueue, ^(void) {
NSLog(#"* Starting slow op %d.", slowOpIndex);
for (NSUInteger delayLoop = 0; delayLoop < 1000; delayLoop++) {
putchar('.');
}
NSLog(#"* Ending slow op %d.", slowOpIndex);
});
}
for (NSUInteger fastBlockIndex = 0; fastBlockIndex < 10; fastBlockIndex++) {
dispatch_async(fastOpQueue, ^(void) {
NSLog(#"Starting fast op %d.", fastBlockIndex);
NSLog(#"Ending fast op %d.", fastBlockIndex);
});
}
As far as using the NSOperationQueue as per your comments about needing the operation cancellation facilities etc. can you try:
- (void)loadSlowQueue
{
[self.slowQueue setMaxConcurrentOperationCount:1];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"begin slow block 1");
[self workHard:500];
NSLog(#"end slow block 1");
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"begin slow block 2");
[self workHard:500];
NSLog(#"end slow block 2");
}];
[self.slowQueue addOperation:operation];
[self.slowQueue addOperation:operation2];
}
As I think the two blocks you add to the operation on the slow queue are being executed in parallel on the default queue and preventing your fast operations from being scheduled.
Edit:
If you're still finding the default GCD queue is choking, why not create an NSOperation subclass that executes blocks without using GCD at all for your slow operations, this will still give you the declarative convenience of not creating a separate subclass for each operation but use the threading model of a regular NSOperation. e.g.
#import <Foundation/Foundation.h>
typedef void (^BlockOperation)(NSOperation *containingOperation);
#interface PseudoBlockOperation : NSOperation
- (id)initWithBlock:(BlockOperation)block;
- (void)addBlock:(BlockOperation)block;
#end
And then for the implementation:
#import "PseudoBlockOperation.h"
#interface PseudoBlockOperation()
#property (nonatomic, strong) NSMutableArray *blocks;
#end
#implementation PseudoBlockOperation
#synthesize blocks;
- (id)init
{
self = [super init];
if (self) {
blocks = [[NSMutableArray alloc] initWithCapacity:1];
}
return self;
}
- (id)initWithBlock:(BlockOperation)block
{
self = [self init];
if (self) {
[blocks addObject:[block copy]];
}
return self;
}
- (void)main
{
#autoreleasepool {
for (BlockOperation block in blocks) {
block(self);
}
}
}
- (void)addBlock:(BlockOperation)block
{
[blocks addObject:[block copy]];
}
#end
Then in your code you can do something like:
PseudoBlockOperation *operation = [[PseudoBlockOperation alloc] init];
[operation addBlock:^(NSOperation *operation) {
if (!operation.isCancelled) {
NSLog(#"begin slow block 1");
[self workHard:500];
NSLog(#"end slow block 1");
}
}];
[operation addBlock:^(NSOperation *operation) {
if (!operation.isCancelled) {
NSLog(#"begin slow block 2");
[self workHard:500];
NSLog(#"end slow block 2");
}
}];
[self.slowQueue addOperation:operation];
Note that in this example any blocks that are added to the same operation will be executed sequentially rather than concurrently, to execute concurrently create one operation per block. This has the advantage over NSBlockOperation in that you can pass parameters into the block by changing the definition of BlockOperation - here I passed the containing operation, but you could pass whatever other context is required.
Hope that helps.

Why isn't multithreading working in this implementation?

Q1: Can I call a method and have it execute on a background thread from inside another method that is currently executing on the main thread?
Q2: As an extension of the above, can I call a method and have it execute on a background thread from inside another method that is currently executing on some other background thread itself?
Q3: And one final question given the above : if I initialize an instance of some object X on some thread (main/background) and then have a method Y, of that object X, executing on some other background thread, can this method Y send messages and update an int property (e.g. of that Object X, or is such communication not possible ?
The reason I'm asking this last question is because I've been going over and over it again and I can't figure what is wrong here:
The following code returns zero acceleration and zero degrees values :
MotionHandler.m
#implementation MotionHandler
#synthesize currentAccelerationOnYaxis; // this is a double
-(void)startCompassUpdates
{
locationManager=[[CLLocationManager alloc] init];
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.delegate=self;
[locationManager startUpdatingHeading];
NSLog(#"compass updates initialized");
}
-(int) currentDegrees
{
return (int)locationManager.heading.magneticHeading;
}
-(void) startAccelerationUpdates
{
CMMotionManager *motionManager = [[CMMotionManager alloc] init];
motionManager.deviceMotionUpdateInterval = 0.01;
[motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue]
withHandler:^(CMDeviceMotion *motion, NSError *error)
{
self.currentAccelerationOnYaxis = motion.userAcceleration.y;
}
];
}
#end
Tester.m
#implementation Tester
-(void)test
{
MotionHandler *currentMotionHandler = [[MotionHandler alloc] init];
[currentMotionHandler performSelectorInBackground:#selector(startCompassUpdates) withObject:nil];
[currentMotionHandler performSelectorInBackground:#selector(startAccelerationUpdates) withObject:nil];
while(1==1)
{
NSLog(#"current acceleration is %f", currentMotionHandler.currentAccelerationOnYaxis);
NSLog(#"current degrees are %i", [currentMotionHandler currentDegrees]);
}
SomeViewController.m
#implementation SomeViewController
-(void) viewDidLoad
{
[myTester performSelectorInBackground:#selector(test) withObject:nil];
}
#end
However, the following code returns those values normally :
Tester.m
#interface Tester()
{
CLLocationManager *locationManager;
double accelerationOnYaxis;
// more code..
}
#end
#implementation Tester
- (id) init
{
locationManager=[[CLLocationManager alloc] init];
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.delegate=self;
[locationManager startUpdatingHeading];
// more code..
}
-(void) test
{
CMMotionManager *motionManager = [[CMMotionManager alloc] init];
motionManager.deviceMotionUpdateInterval = 0.01;
[motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMDeviceMotion *motion, NSError *error)
{
accelerationOnYaxis = motion.userAcceleration.y;
}
];
while(1==1)
{
NSLog(#"current acceleration is %f", accelerationOnYaxis);
NSLog(#"current degrees are %i", locationManager.heading.magneticHeading);
}
}
SomeViewController.m
#implementation SomeViewController
-(void) viewDidLoad
{
[myTester performSelectorInBackground:#selector(test) withObject:nil];
}
What's wrong with the first version? I really want to use that first one because it seems much better design-wise.. Thank you for any help!
Calling performSelectorInBackground:withObject: is the same as if you called the detachNewThreadSelector:toTarget:withObject: method of NSThread with the current object, selector, and parameter object as parameters (Threading Programming Guide). No matter where you call it, a new thread will be created to perform that selector. So to answer your first two questions: yes and yes.
For your final question, as long as this Object X is the same object in both methods, any of X's properties can be updated. But, beware that this can yield unexpected results (ie. see Concurrency Programming Guide). If multiple methods are updating X's property, values can be overwritten or disregarded. But, if you are only updating it from method Y and reading it from all other methods, such problems shouldn't occur.
You should take a look at the Grand Central Dispatch documentation from Apple. It allows you to use multiple threads in a block-based structure.
2 importants function are dispatch_sync() and dispatch_async().
Some examples:
To execute a certain block of code on a background thread and wait until it is finished:
__block id someVariable = nil;
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// do some heavy work in the background
someVariable = [[NSObject alloc] init];
});
NSLog(#"Variable: %#", someVariable);
This function modifies the variable someVariable which you can use later on. Please note that the main thread will be paused to wait for the background thread. If that is not what you want, you can use dispatch_async() as follows:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// do some heavy work in the background
NSObject *someVariable = [[NSObject alloc] init];
// notify main thread that the work is done
dispatch_async(dispatch_get_main_queue(), ^{
// call some function and pass someVariable to it, it will be called on the main thread
NSLog(#"Variable: %#", someVariable);
});
});

How to pause/continue NSThread

I have an app, where i use function FSMoveObjectToTrashSync. It works in background thread. I need ability for my app, to click on button to pause it or continue(if it paused) how i can make it?
Example of code:
NSMutableArray *fileArray = [NSMutableArray array withobjects:#"file1url", #"file2", #"file3", nil];
NSMutableArray *threadArray = [[NSMutableArray alloc] init];
-(void)myFunc{
for (NSURL *url in fileArray){
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:#selector(mySelectorWith:) object:url];
[thread start];
[threadArray addObject:thread];
}
}
-(void)mySelectorWith:(NSURL *) url{
FSRef source;
FSPathMakeRef((const UInt8 *)[[url path] fileSystemRepresentation], &source, NULL);
FSMoveObjectToTrashSync(&source, NULL, kFSFileOperationDefaultOptions);
}
PS:sorry for my english, i'm from Belarus... =(
One solution would be to replace the for loop on a single thread with an NSOperation subclass. Each operation should trash exactly one object; you then create one operation for each object you want to trash and put all of the operations on an NSOperationQueue.
The operation queue will run each operation on a thread, and it can even run multiple operations on multiple threads if it sees enough computing power laying around to do it.
An operation queue can be paused and resumed at will; when you suspend the queue, any operations in that queue that are already running will finish, but no more will start until you resume the queue.
You could use an NSConditionLock. An NSConditionLock is similar to a condition variable. It has a couple of basic methods, lockWhenCondition, and unlockWithCondition, and lock. A typical usage is to have your background thread waiting on the condition lock with "lockWhenCondition:", and the in you foreground thread to set the condition, which causes the background thread to wake up. The condition is a simple integer, usually an enumeration.
Here's an example:
enum {
kWorkTodo = 1,
kNoWorkTodo = 0
}
- (id)init {
if ((self = [super init])) {
theConditionLock = [[NSConditionLock alloc] initWithCondition: kNoWorkTodo];
workItems = [[NSMutableArray alloc] init];
}
}
- (void)startDoingWork {
[NSThread detachNewThreadSelector:#selector(doBackgroundWork) toTarget:self withObject:nil];
}
- (void)doBackgroundWork:(id)arg {
while (YES) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *items = nil;
[theConditionLock lockWhenCondition:kWorkTodo]; // Wait until there is work to do
items = [NSArray arrayWithArray:workItems]
[workItems removeAllObjects];
[theConditionLock unlockWithCondition:kNoWorkTodo];
for(id item in items) {
// Do some work on item.
}
[pool drain];
}
}
- (void)notifyBackgroundThreadAboutNewWork {
[theConditionLock lock];
[workItems addObject:/* some unit of work */];
[theConditionLock unlockWithCondition:kWorkTodo];
}
In this example, when startDoingWork is called doBackgroundWork: will start on a background thread, but then stop because there isn't any work to do. Once notifyBackgroundThreadAboutNewWork is called, then doBackgroundWork: will fire up and process the new work, and then go back to sleep waiting for new work to be available, which will happen the next time notifyBackgroundThreadAboutNewWork is called.

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.