简体   繁体   中英

Objective-C: Calling selectors with variable arguments

I'm facing the following problem and I already tried a lot. I have also read the others Questions in Stackoverflow like: Objective-C: Calling selectors with multiple arguments and the Cocoa Core Competencies about Selectors, but I'm searching for the best way to pass a variable of arguments to a selector.

-(void) runAllStatusDelegates : (SEL)selector
{
 for (NSValue *val in self.statusDelegates)
 {
  id<StatusDelegate> delegate = val;
  if ([delegate respondsToSelector:selector])
  { 
   [delegate performSelector:selector];
  }
 }
}

This method is responsible to call the methods inside the delegates. The parameter is a Selector. My Problem is that the selector can have 0 - 3 arguments, as shown below.

-(void) handleBluetoothEnabled:(BOOL)aEnabled
{
 if (aEnabled)
 {
  [self.statusDelegate bluetoothEnabled];
  if (_storedPenSerialNumber != nil && ![_storedSerialNumber isEqual:kUnknownPenID])
   {
    [self runAllStatusDelegates: @selector(penConnected : _storedSerialNumber : _storedFirmware:)];
   }
 }
 else
 {
  [self.statusDelegate bluetoothDisabled];
 }
}

-(void) handleChooseDevice:(BluetoothDeviceList*)aDevices
{
 NSLog(@"Handle Choose Device");
 [self runAllStatusDelegates: @selector(chooseDevice:aDevices:)];
}


-(void) handleDiscoveryStarted
{
 NSLog(@"Discovery Started");
 [self runAllStatusDelegates: @selector(searchingForBluetoothDevice)];
 [self.statusDelegate handleStatus:@"Searching for your digipen"];
}

This implementation isn't working because the performSelector is not recognizing the selector.

I also tried to implement it with @selector(penConnected::) withObject:_storedSerialNumber but then I have to implement another method with additional arguments as well and I don't want that. I'm new to objective-c so I'm not so familiar with all possibilities.

My idea is to pass a String and an Array of arguments to runAllStatusDelegates and build up the selector inside that method, but is this the best way or are there more convenient ways?

I am personally not a fan of NSInvocation for complex signatures. Its really great for enqueueing a simple function call on a queue and running it when you need it but for your case, you know the selector so you don't really need to go the invocation route. I typically find invocations are more useful if you don't actually know the selector you want to call at compile time, maybe its determined by your API etc.

So what I would do is simply pass a block into your runAllStatusDelegates method that will execute against all your delegates:

- (void)performSelector:(SEL)selector againstAllDelegatesWithExecutionBlock:(void (^)(id<StatusDelegate>))blockToExecute
{
    for (id<StatusDelegate> delegate in self.statusDelegates)
    {
        if ([delegate respondsToSelector:selector])
        {
            blockToExecute(delegate);
        }
    }
}

Then when you want to call your delegates with a function it looks like this:

[self performSelector:@selector(handleAnswerOfLifeFound)
againstAllDelegatesWithExecutionBlock:^(id<StatusDelegate> delegate){
    [delegate handleAnswerOfLifeFound];
}];

I guess the only downside might be that you could change the selector and pass a different function into the block. How I would solve this is by actually making sure not all methods are optional, or if they are optional to make the actual check inside the block, this would clean up the signature:

- (void)callAllDelegatesWithBlock:(void (^)(id<StatusDelegate>))blockToExecute
{
    for (id<StatusDelegate> delegate in self.statusDelegates)
    {
            blockToExecute(delegate);
    }
}

and then your actual usage for an optional method:

[self callAllDelegatesWithBlock^(id<StatusDelegate> delegate){
    if([delegate respondsToSelector:@selector(handleAnswerOfLifeFound)]){
        [delegate handleAnswerOfLifeFound];
    }
}];

Still error-prone but at least a bit tidier.

You can use NSInvocation for this case

SEL theSelector = @selector(yourSelector:);
NSMethodSignature *aSignature = [NSMethodSignature instanceMethodSignatureForSelector:theSelector];
NSInvocation *anInvocation = [NSInvocation invocationWithMethodSignature:aSignature];
[anInvocation setSelector:theSelector];
[anInvocation setTarget:self];
[anInvocation setArgument:&arg1 atIndex:2];
[anInvocation setArgument:&arg2 atIndex:3];
[anInvocation setArgument:&arg3 atIndex:4];
[anInvocation setArgument:&arg4 atIndex:5];
//Add more

Note that the arguments at index 0 and 1 are reserved for target and selector.

For more info http://www.cocoawithlove.com/2008/03/construct-nsinvocation-for-any-message.html

you can binding the arguments to the selector

NSDictionary *argInfo=@{@"arg1":arg1,@"arg2":arg2,...};
objc_setAssociatedObject(self,@selector(chooseDevice:aDevices:),argInfo,OBJC_ASSOCIATION_COPY)
[self runAllStatusDelegates: @selector(chooseDevice:aDevices:)];

then in the

-(void) runAllStatusDelegates : (SEL)selector
{
 for (NSValue *val in self.statusDelegates)
 {
  id<StatusDelegate> delegate = val;
  if ([delegate respondsToSelector:selector])
  { 

   NSDictionary *argInfo=objc_getAssociatedObject(self, selector);
   //call the fun use arginfo
  }
 }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM