1

I have the following selector in a class:

    - (SoapRequest*) loginAndConnect: (id) target action: (SEL) action credentials: (WSAcredentials*) arg0 dialoutInfo: (WSAdialoutInfo*) arg1;

I'd like to be able to call it with a block rather than another selector as the action, but I can't find information on how to do this. Something like:

[service loginAndConnect:self  credentials:credentials dialoutInfo:dialoutInfo action:^(SoapRequest* aResult) {
    // more code here
}];

What's the best way to accomplish this?

UPDATE**

I almost have this working but am getting an exception in objc_release, for the completionBlock object. I'm sure it's something to do with retaining the target, but I'm not sure how to correct it. Here's the current code:

- (BOOL) checkService {

WSAcredentials* credentials = [[WSAcredentials alloc] init];
WSAdialoutInfo* dialoutInfo = [[WSAdialoutInfo alloc] init];
if (!service) {
    service = [[WSAWebSocketAdapterService alloc] init];
}
__block SoapRequest* request;
[service loginAndConnectWithCredentials:credentials dialoutInfo:dialoutInfo completionBlock:^(SoapRequestCompletionBlock completionBlock) {
        request = (SoapRequest*)completionBlock;
        if ([request isKindOfClass: [SoapFault class]]) {                
            return YES; // we got a response, that's all we care about
        }
    return NO;
}
];
return YES;
}

Here's my category, very close to what was posted below:

#import "WSAWebSocketAdapterService+BlockExtension.h"

// These objects serve as targets (with action being completed:) for the original object.
// Because we use one for each request we are thread safe.

@interface MyCustomSoapTargetAction : NSObject 

@property (copy) SoapRequestCompletionBlock block;

- (void) completed:(id)sender;

@end

@implementation MyCustomSoapTargetAction
- (void) completed:(id)sender
{
// Assuming 'sender' is your SoapRequest
if (_block != nil)
    _block(sender);
_block = nil;
}
@end

@implementation WSAWebSocketAdapterService(BlockExtension)

- (SoapRequest*) loginAndConnectWithCredentials:(WSAcredentials*) arg0
                                dialoutInfo: (WSAdialoutInfo*) arg1
                            completionBlock:(BOOL (^)(SoapRequestCompletionBlock))completionBlock
{
MyCustomSoapTargetAction *target = [[MyCustomSoapTargetAction alloc] init];
target.block = (SoapRequestCompletionBlock) completionBlock;

//
// Here we assume that target will be retained.
// If that's not the case then we will need to add it to some collection before
// the call below and have the target object remove itself from it after its
// block has been called.
//
return [self loginAndConnect:target action:@selector(completed:) credentials:arg0 dialoutInfo:arg1];
}
@end

Thanks for the help!

Janene Pappas
  • 1,366
  • 12
  • 26
  • What do you mean by "call it with a block"? –  Mar 29 '13 at 23:13
  • you simply need to pass the "action" block to your function and determine if / when it should be run inside it. – Rog Mar 29 '13 at 23:19

2 Answers2

4

You actually don't need the source code. A category will do just fine. Here is some sample code to get you started, which is even thread safe! If your object does not retain the target then there is a little more work to do to keep it around until its block is called.

// -- TheClassName+BlockExtension.h

typedef void (^SoapRequestCompletionBlock)(SoapRequest*);

@interface TheClassName(BlockExtension)
- loginAndConnectWithCredentials:(WSAcredentials*) arg0 
                     dialoutInfo: (WSAdialoutInfo*) arg1;
                 completionBlock:(SoapRequestCompletionBlock)completionBlock;
@end


// -- TheClassName+BlockExtension.m

// EDITED: If the 'target' object is not retained by the loginAndConnect... then
//         we may add it to this collection. In this implementation, a block can
//         only be used once.

static NSMutableSet *gSoapTargets = nil;

// These objects serve as targets (with action being completed:) for the original object.
// Because we use one for each request we are thread safe.

@interface MyCustomSoapTargetAction
@property (copy) SoapRequestCompletionBlock block;
- (void) completed:(id)sender;
@end

@implementation MyCustomSoapTargetAction

- (id) init
{
    // Objects adds itself to the global collection
    if ((self = [super init]))
        [gSoapTargets addObject:self];
    return self;
}

- (void) completed:(id)sender
{
    // Assuming 'sender' is your SoapRequest
    if (_block != nil)
        _block(sender);

    // Object removes itself from global collection when done.
    // On return from this method it will likely be released.
    [gSoapTargets removeObject:self];
}

+ (void) load 
{
    gSoapTargets = [NSMutableSet set];
}

@end

@implementation TheClassName(BlockExtension)
- (SoapRequest*) loginAndConnectWithCredentials:(WSAcredentials*) arg0 
                                    dialoutInfo: (WSAdialoutInfo*) arg1;
                                completionBlock:(SoapRequestCompletionBlock)completionBlock
{
    MyCustomSoapTargetAction *target = [[MyCustomSoapTargetAction alloc] init];
    target.block = completionBlock;

    //
    // Here we assume that target will be retained.
    // If that's not the case then we will need to add it to some collection before
    // the call below and have the target object remove itself from it after its 
    // block has been called.
    //
    [self loginAndConnect:target action:@selector(completed:) credentials:arg0 dialoutInfo:arg1];
}
@end

* UPDATE: here is an example on using this category based on your code:

- (BOOL) serviceIsAvailable 
{
    return _serviceIsAvailable;
}

- (void) _setServiceIsAvailable:(BOOL)value 
{
    // This method should probably be private thus the _ prefix
    // Do something with the result (set some status, warn user...).
    _serviceIsAvailable = value;
}

- (void) checkService 
{
    WSAcredentials* credentials = [[WSAcredentials alloc] init];
    WSAdialoutInfo* dialoutInfo = [[WSAdialoutInfo alloc] init];

    if (_service == nil)
        _service = [[WSAWebSocketAdapterService alloc] init];

    [_service loginAndConnectWithCredentials:credentials 
                                 dialoutInfo:dialoutInfo 
                             completionBlock:^(id sender) 
    {
        [self _setServiceIsAvailable:[sender isKindOfClass:[SoapFault class]]];
    }];
}
aLevelOfIndirection
  • 3,522
  • 14
  • 18
  • I'd also set the `_block` property to nil at the end of `completed:` to avoid a potential memory leak. For a completion block, there's no reason this service should need to continue to hold on to it once it's been executed. – Christopher Pickslay Mar 30 '13 at 16:44
  • It costs nothing to set _block to nil so we may go ahead and do it although in a ARC environment it is not necessary as the block will get released when the object gets deallocated. – aLevelOfIndirection Mar 31 '13 at 08:43
  • @aLevelOfIndirection Could you explain a bit more about retaining the target? I'm almost there but am getting an exception, I'm sure related to this. I've updated the original post with the current code. Thanks! – Janene Pappas Apr 01 '13 at 21:38
  • If the object on which you are calling loginAndConnect does not retain the target the by the time your block gets called the instance of MyCustomSoapTargetAction will have been released. I'll edit my post to handle this case. – aLevelOfIndirection Apr 02 '13 at 07:16
0

Do you have access to that class's source? It would be a fairly simple rewrite to produce a similar method that accepts a block instead of a selector.

The AFNetworking library is a good style guide for that sort of stuff. Your method might look similar to this:

+ (instancetype)JSONRequestOperationWithRequest:(NSURLRequest *)urlRequest
                                        success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success
                                        failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON))failure;

Alternatively, you could add your method via a category, using the approach demonstrated here: Blocks instead of performSelector:withObject:afterDelay:

Community
  • 1
  • 1
Hal Mueller
  • 7,019
  • 2
  • 24
  • 42