How do you call a property setter by name? - objective-c

I've asked a related question, but thought I'd split this out into its own question. See the code below for calling a property getter.
SEL propSelector = NSSelectorFromString(propertyName);
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[[target class]instanceMethodSignatureForSelector:propSelector]];
[inv setSelector:propSelector];
[inv setTarget:target];
[inv invoke];
float value;
[inv getReturnValue:&value];
I'd like to do the same thing, but call the property SETTER. I'd also like to avoid manually crafting the setter name by building a #"setPropertyName:" string. Bottom line - is it possible to use the selector created on this line to call the setter?
SEL propSelector = NSSelectorFromString(propertyName);

Use Key-Value Coding.
I.e. [someObject setValue: anObjectValue forKey: #"foo"];

Having read your other question earlier today, this is what I can come up with:
It's not possible to use it directly as the setter. The getter does not accept any arguments, so your selector will never have the colon, so it's never gonna work.
Easiest thing to do is define your setter methods as foo: instead of setFoo:. This way you'll only have to append the colon (which is quite cheap, requires only two extra strings)

Related

Calling an initialiser using NSInvocation

I have a scenario where the initialiser to use called after allocing an object is not known until runtime and I have no control over it. It also may have various arguments. So currently I'm doing this:
...
id obj = [MyClass alloc];
return [self invokeSelectorOn:obj];
}
-(id) invokeSelectorOn:(id) obj {
SEL initSelector = ...;
NSMethodSignature *sig = [[MyClass class] instanceMethodSignatureForSelector:initSelector];
NSinvocation *inv = [NSInvocation invocationWithMethodSignature:sig];
inv.selector = initSelector;
[inv retainArguments];
// ... setting arguments ...
[inv invokeWithTarget:obj];
id returnValue;
[inv getReturnValue:&returnValue];
return returnValue;
}
The problem (I think !) I have is that because initSelector is being called by an NSInvocation, it's not returning a retain+1 object. So the result of the above is a crash when the auto release pool attempts to dealloc the object.
I've tried adding a CFBridgingRetain(...) which fixes the memory issue, however I'm not sure this is the correct solution and the static analyser tags it as a memory leak.
So my question is how can I can I call an initialiser via a NSInvocation and get back a correctly retain+1 object?
getReturnValue: simply copies the return value into the location pointed to by your pointer as plain old dumb binary data. It doesn't care what the type is, it just copies it binarywise, and does nothing else with it like memory management if it's a managed object type.
Therefore, passing it a id __strong * is not appropriate, since that type requires that when something is assigned to the thing pointed to, the previous value is released and the new value is retained (getReturnValue: doesn't do that.)
Passing it a id __unsafe_unretained * is appropriate, since that type exactly matches the behavior where assigning something to the thing pointed to doesn't do any memory management. That's why declaring returnValue to be id __unsafe_unretained (or MyClass * __unsafe_unretained) and then passing &returnValue works.
After you fix this, what you have is fine for calling normal methods. But in this case you are calling initialisers, and initialisers have different memory management rules than normal methods. Initializers consume a reference count on the reference they are called on, and return a retained reference. If the initializer returns the object it was called on (which is what most initializers do), then those cancel out and it works just like normal methods. However, initializers are also allowed to
Return a different object than the one it was called on, in which case it would release the one it was called on, and retain the new one before returning it, or
Return nil, in which case it would release the object it was called on, and return nil.
So more complex handling is needed for it to work with initializers in general. I will not go into that. If you know your initialisers always return the object it was called on, then you don't need to worry about this.
Just rename the invokeSelector method to createObjectByInvokingSelector so it fits with the naming scheme and are doesn't free the returnValue
Whoa .... I think I've stumbled on the answer. I've spent some time searching the net and reading the Clang documentation on ARC. Most of which is so full of 'if's, 'but's and 'maybe's, I think I'd have to spend quite some time to understand it. Anyway, here is the modified code:
...
id obj = [MyClass alloc];
return [self invokeSelectorOn:obj];
}
-(id) invokeSelectorOn:(id) obj {
SEL initSelector = ...;
NSMethodSignature *sig = [[MyClass class] instanceMethodSignatureForSelector:initSelector];
NSinvocation *inv = [NSInvocation invocationWithMethodSignature:sig];
inv.selector = initSelector;
[inv retainArguments];
// ... setting arguments ...
[inv invokeWithTarget:obj];
id __unsafe_unretained returnValue;
[inv getReturnValue:&returnValue];
return returnValue;
}
Somehow the addition of __unsafe_unretained to the variable that receives the response from the initialiser seems to fix the problem. I have not had the time to work through the Clang doc and figure out why this works. I'm just happy it does.
Perhaps someone with some highly technical knowledge of the memory management of ARC could explain further.

passing 2 arguments for #selector in its simplest way

SEL twoArgumentSelector = #selector(methodWithTwoArguments:and:);
[newsButton addTarget:self action:#selector(twoArgumentSelector) forControlEvents:UIControlEventTouchUpInside];
-(void)methodWIthTwoArguments:(id)argumentOne and:(id)argumentTwo;
I have seen some examples that let you use two arguments in a selector. How would any do that in the above code? ty in advance.
#selector(twoArgumentSelector:and:)
although I'm not sure how you would send two arguments with a control event...
edit:
you know that the selector isn't actually calling the method, so you can't pass the arguments with the selector. It is basically just the name for a block of code (the method). Read this. A better solution would be to have the control event call a separate method which could then determine the arguments to send to the method with 2 parameters.
UIControls events by will only send a reference to themselves if their target selector allows for one argument. This is all you get. UIButton is one such UIControl subclass.
- (void)buttonAction:(id)sender; //(reference to button)
The easiest way to accomplish what you want is to make another method on your button target (in this case self) that calls out to your two argument selector.
[newsButton addTarget:self action:#selector(buttonAction:) forControlEvents:...];
- (void)buttonAction:(id)sender
{
[self methodwithTwoArguements:sender and:otherObject];
}
This could also be solved with a UIButton subclass, but depending on what your second argument needs to be, this is the simplest way.
Unless you are subclassing the object, there is no need for this because the argument to UIControlEvents is the id of the instance sending the event. You can get all the information you could possibly need from that instance.
What you're looking for is NSInvocation.
Here's an example:
SEL mySelector; // 2 parameter selector.
// ...
NSMethodSignature *signature = [[MyClass class] instanceMethodSignatureForSelector:mySelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:myTarget];
[invocation setArgument:arg1 atIndex:0];
[invocation setArgument:arg2 atIndex:1];
[invocation invoke];
This will do exactly what you want.
It's worth noting, however, that the control event you are binding to isn't going to pass anything in for a second parameter. This will only work if, by contract, the object that is calling back actually has a second parameter to pass in.

Is it possible to set method from another class as selector property for NSInvocation object?

I have a method in ClassA which is called Selector1,
In ClassB I want to create a NSInvocation object and set the Selector1 as the object's selector. Is this possible? I don't know the proper way to set up this.
The code I am trying to use in ClassB is like this:
NSMethodSignature *signature = [ClassA methodSignatureForSelector:#selector(Selector1:)];
NSInvocation *invocationToPass = [NSInvocation invocationWithMethodSignature:signature];
invocationToPass.target = self;
invocationToPass.selector = Selector1; // How can I assign Selector1 from ClassA?
You need a SEL, just like the one you passed to methodSignatureForSelector:, not just the name of the method.
[invocationToPass setSelector:#selector(Selector1:)];
You also need to be aware that the colon is significant. If the method takes no arguments, then the name will be Selector1; if it takes one, the name is Selector:. You need to put the correct name into the #selector() operator.

getArgument of NSInvocation of current method always returns null

I want to get the name of the arguments of the current function I am in so that I can prepare loading that object from the filesystem if it's not present on the current instance. (for instance if [foo dictTest] is not available I want to load it's prior saved plist version into exactly that ivar)
I want to find the file by providing the ivar name that I provided as an argument to the current function.
This is the function code:
-(NSDictionary*)getCachedDictionary:(NSDictionary*)dict{
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:_cmd]];
NSString * firstArgument = nil;
[invocation getArgument:&firstArgument atIndex:2];
NSLog(#"Trying to get the objects ivar %#",firstArgument);
// right now during testing return nil
return nil;
}
As soon as the code reaches the NSLog I am getting a null value from firstArgument.
Why is that? Could it be possible that I would have to wait for the complete invocation of that current method I am in or is it actually better to create a proxy function that implicitly calls my class method via an invocation that eats the ivar name provided by setArgument so that I can use that argument string like I want?
Thanks a lot in advance!
P.S.: In this particular example I do not want to use KVC to identify the ivar and return it.
You've misunderstood the NSInvocation API. +[NSInvocation invocationWithMethodSignature:] creates a new NSInvocation that is keyed to accept arguments of the types defined by the method signature. It does not return an NSInvocation that corresponds to the current method invocation. This is pretty easy to see why:
- (void)doBar:(id)bip {
NSLog(#"hi there!")
}
- (void)doFoo {
NSMethodSignature *sig = [self methodSignatureForSelector:#selector(doBar:)];
NSInvocation *i = [NSInvocation invocationWithMethodSignature:sig];
}
When you create the invocation in doFoo for the doBar: method, it's obvious to see that the arguments must be empty, because doBar: hasn't been executed, and thus there is no argument. Changing #selector(doBar:) to _cmd wouldn't magically change anything.
So the next question: is there a way to get an NSInvocation for the current method invocation? Not that I know of. NSInvocation is an extremely complicated class, and constructing one from the current method would be a nightmare.
I strongly suggest finding a different approach to do whatever it is you want to do.
Even though the question is old and answered, here is a link that provides an easy and very elegant way to create an invocation instance for any selector/method that is known at compile time:
http://www.cocoawithlove.com/2008/03/construct-nsinvocation-for-any-message.html

Correct way of setting a BOOL property

I have a BOOL property that I want to set in my class initializer.
#property (assign, nonatomic) BOOL isEditMode;
- (id)init
{
. . .
[self setValue:NO forKey:isEditMode];
return self;
}
The compiler gives me an "Incompatible integer to pointer conversion" warning. What am i doing wrong here?
The Key-Value Coding method setValue:forKey: only accepts objects as arguments. To set a BOOL, you need to wrap the number in a value object with [NSNumber numberWithBool:NO]. But there's little reason to do that. Key-Value Coding is a roundabout way to accomplish this. Either do self.isEditMode = NO or just isEditMode = NO. The latter is preferable in an init method (because setters can run arbitrary code that might not be desirable before an object is fully set up).
But to elaborate on the first point: The reason Key-Value Coding works this way is because the type system can't represent an argument that's sometimes an object and at other times a primitive value. So KVC always deals with objects and just autoboxes primitive values as necessary. Similarly, if you do [yourObject valueForKey:#"isEditMode"], you'll get back an NSNumber object wrapping the real value.
The correct syntax to set a property is just
self.isEditMode = NO;
If you want to use -setValue:forKey: you'd have to write it as
[self setValue:[NSNumber numberWithBOOL:NO] forKey:#"isEditMode"];
However, there's absolutely no reason to do this in your situation.
That said, since you're in an init method, I would strongly recommend avoiding any property access whatsoever and instead using the ivar directly, as in
isEditMode = NO;
This avoids the possibility of an overridden setter being called (either in this class or a subclass) that makes the assumption that the object has already completed initialization. For this same reason you also want to avoid property access inside of -dealloc.
You can just assign the value directly:
isEditMode = NO;
I think you mean:
self.isEditMode = NO;
If your code does indeed compile (I'm pretty new to Objective-C so I don't know) setValue probably takes a pointer to a string (#"isEditMode", e.g.) and not some other type (isEditMode, e.g.).