Objective-C: Calling selectors with variable arguments - ios

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
}
}
}

Related

Is it possible to intercept all method class on any UIViewController subclass

Let's say I wanted to be able to intercept any method calls to a UIViewController subclass.
First of all, I swizzle the +(instancetype)alloc method and I check if the current instance isKindOfClass:[UIViewController class]. If it is I go ahead and instantiate my proxy with the target.
///swizzled Alloc
+ (instancetype)monitoredAlloc {
id obj = [self monitoredAlloc];
if([obj isKindOfClass:[UIViewController class]]) {
id proxy = [PMGProxy proxyWithObject:obj];
return proxy;
}
return [self monitoredAlloc];
}
---------------------------------------
/// Proxy class
#implementation PMGProxy
+ (instancetype)proxyWithObject:(id)obj {
PMGProxy *proxy = [self alloc];
proxy.obj = obj;
return proxy;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation setTarget:_obj];
[invocation invoke];
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.obj methodSignatureForSelector:sel];
}
- (Class)class {
return [self.obj class];
}
The problem is that I get crashes, so I would expect the implementation of my Proxy is wrong... What am I doing wrong?
Here is the exception:
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'This coder requires that replaced objects be returned from initWithCoder:'
From the error it seems that the coder is only happy to accept a different class returned from initCoder: rather than earlier in the process at the alloc stage.
Might be worth looking up NSSecureCoding for more detail on the whole process.
While you’re at it, take a look at the stack track that resulted in your exception, it will give you a bit more perspective on just how deep this rabbit hole goes.

Swizzling NSDictionary initializer in SDK

So I'm plan to create a safe init function for NSDictionary like someone else did, but as I'm a SDK developer:
I want add a switch for it, the user can decide if he want open it or not;
I don't want to use Category to implement it.
So I create a totally new class named "ALDictionarySafeFunction.h", it has two functions, the first one is the switch function, like this:
+(void)enableSafeFunction{
[ALSwizzlingHelper swizzleSelector:#selector(initWithObjects:forKeys:count:)
ofClass:NSClassFromString(#"__NSPlaceholderDictionary")
withSwizzledSelector:#selector(safeInitWithObjects:forKeys:count:)
ofClass:[ALDictionarySafeFunction class]];
}
The ALSwizzlingHelper can help me to swizzle two functions.
The second is the safe init function, like this:
-(instancetype)safeInitWithObjects:(const id _Nonnull __unsafe_unretained *)objects forKeys:(const id _Nonnull __unsafe_unretained *)keys count:(NSUInteger)cnt {
BOOL containNilObject = NO;
for (NSUInteger i = 0; i < cnt; i++) {
if (objects[i] == nil) {
containNilObject = YES;
NSLog(#"There is a nil object(key: %#)", keys[i]);
}
}
if (containNilObject) {
//Do something to make sure that it won't cause a crash even it has some nil value
}
//There is the problem, next line
[self safeInitWithObjects:objects forKeys:keys count:cnt];
}
For the normal situation(Write the swizzled method in the Category), I need to do like I wrote to invoke the original method.
But the problem is I cannot do it here, because that the "self" object is the instance of “__NSPlaceholderDictionary”, and the "__NSPlaceholderDictionary" class doesn't have the instance method "safeInitWithObjects:forKeys:count:".
So what should I do?
Is there a way to make it?
Any advice will be appreciated.

Selector in Method over riding

In the case of method over riding in objective c how selector knows that which method needs to call via selector?
As we dont pass any arguments in slector section...
Ex:
in tmp.m file
There is 2 methods with different arguments
-(void)details
{
}
-(void)details:(NSDictionary *)result
{
}
And when m call another method with the use of selector as:
[mc detailstrac:[[NSUserDefaults standardUserDefaults] valueForKey:#"userID"] tracid:self.trac_id selector:#selector(details:)];
How selector knows to call which method !
I have checked that
-(void)details:(NSDictionary *)result
{
}
this method is called every time then what about
-(void)details
{
}
this ?
Selector will know on the basis how you call the method like from your example,
[mc detailstrac:[[NSUserDefaults standardUserDefaults] valueForKey:#"userID"] tracid:self.trac_id selector:#selector(details:)];
when you call #selector(details:) then the selector will call this method
-(void)details:(NSDictionary *)result { }
And When you call #selector(details) then the selector will call
-(void)details { }
The main difference here is #selector(details) and #selector(details:).
Hope you understand my point!
Happy Coding!

Unrecognized selector sent to instance on if(bool)

I know this questions has been posted a lot, but they are all very specific and do not apply to my problem.
[MirrorStarAV startRecording:]: unrecognized selector sent to instance 0x15561d60
I get this error when calling the following method:
- (bool) startRecording {
bool result = NO;
#synchronized(self) {
if (!_recording) { //Exception is raised on this line
result = [self setUpWriter];
startedAt = [[NSDate date] retain];
_recording = YES;
}
}
return result;
}
I call the method as following:
bool _startRecording(){
return [delegateMSA startRecording];
}
[MirrorStarAV startRecording:]: unrecognized selector sent to instance 0x15561d60
The above error message is basically saying that MirrorStarAV doesn't respond to startRecording: so when you call [delegateMSA startRecording]; it is crashing. The delegateMSA has an instance of MirrorStarAV set to it but instances of MirrorStarAV don't respond to `startRecording:
Whilst YES it would be better for you to change your method to something like
bool _startRecording(){
if ([delegateMSA respondsToSelector:#selector(startRecording)])
{
return [delegateMSA startRecording];
}
return false;
}
but this isn't your issue. Note that [MirrorStarAV startRecording:] has a colon : at the end. You are calling startRecording: somewhere over startRecording
Check before, whether the delegate responds to this method, so you can make sure that the object is also able to respond to this selector:
bool _startRecording(){
if ([delegateMSA respondsToSelector:#selector(startRecording)])
{
return [delegateMSA startRecording];
}
return false;
}
Either way, I would recommend to add this selector to the delegates methods with #required:
#protocol MyRecorderDelegate <NSObject>
#required
- (BOOL)startRecording;
#end
So, you would get a compiler warning, if your delegate was not implementing this method.
Verify if the delegateMSA is an instance of class where you have the startRecording method.
Or do something like
bool _startRecording(){
if([delegateMSA respondsToSelector:#selector(startRecording)]) {
return [delegateMSA startRecording];
}
}

Link two or more SenTestCase

I am trying to change the existent defaultTestSuite method to create a class that can pick test methods from different classes and execute them in a specific order.
However every time I import a test file in another test file i get a linker error
duplicate symbol _OBJC_METACLASS_$_TMKTestExample
Why does this happen with OCUnit, how could I fix this?
Regards
Check your TMKTestExample's Target Membership, it should not be included in both the main target and the unit test target.
I found the solution by creating my own NSInvocations in the testInvocation method that belongs to the SenTestCase.
Basically i have an empty test Class which i throw test methods that execute some action (for flow testing), which are setup in each NSInvocation, once the test runs, the methods will be executed in the same order as the exist in the testInvocation static method, and it Permits to add as many methods as possible, in something similar to the crude example below:
Extend the SenTestCase class
- (id) initWithInvocation:(NSInvocation *)anInvocation
{
id invocationTarget = anInvocation.target;
self = [super initWithInvocation:anInvocation];
if (!self) {
return nil;
}
if (invocationTarget != nil && invocationTarget != self) {
anInvocation.target = invocationTarget;
}
return self;
}
It is necessary to override the method above because the target will always be set to self in the super initWithInvocation method.
+(NSArray *) testInvocations
{
NSMutableArray *invocations = (NSMutableArray *)[super testInvocations];
TMKTestFirstViewControllerBaseAction *baseAction = [TMKTestFirstViewControllerBaseAction sharedInstance];
for (int i=0; i<4; i++) {
NSInvocation *invocation = nil;
switch (i) {
case 3:{
invocation = [NSInvocation invocationWithTarget:baseAction selector:#selector(tapChangeBackgroundButton)];
break;
}
case 2:{
invocation = [NSInvocation invocationWithTarget:baseAction selector:#selector(tapChangeBackgroundButton)];
break;
}
case 0:{
invocation = [NSInvocation invocationWithTarget:baseAction selector:#selector(tapBarButtonWithAccessibilityLabel:)];
NSString *arg = #"Second";
[invocation setArgument:&arg atIndex:2];
break;
}
case 1:{
invocation = [NSInvocation invocationWithTarget:baseAction selector:#selector(tapBarButtonWithAccessibilityLabel:)];
NSString *arg = #"First";
[invocation setArgument:&arg atIndex:2];
break;
}
default:
break;
}
[invocation retainArguments];
NSLog(#"invocation target: %d target: %#", i,invocation.target);
[invocations insertObject:invocation atIndex:i+1];
}
return invocations;
}
The example above only adds one test but it is possible to see what I mean, this was taken from examples in these websites:
parametric tests: http://briancoyner.github.io/blog/2011/09/12/ocunit-parameterized-test-case/
nsinvocation instatiation: https://github.com/aleph7/a-coding/blob/master/ObjC/Invocations/NSInvocation%2BConstructors.m
Using both of these and manipulating the order of tests, i am able to create the following for KIF:
- Classes that describe actions to test in a specific UIViewController/UIView/etc., and then reuse the methods to create flow tests, regression UI tests, from code or from scripts (still doing the latter).
- normal test classes.
I hope this helps whoever needs this very specific requirement.
Regarding the original question i haven't figured it out, i wonder if there is a way.

Resources