passing float variable as parameter - ios

I am trying to write a method with float parameter and call it using performSelector` but I am getting error in doing this. Following is my code:
[sender performSelector:selector withObject:progress/total];
Here progress and total both are float variable.
I am trying to call following method in different class:
-(void) updateProgress:(float)fl {
}

You need to pass a real object, not one of the basic types like int or float.
Wrap it into an NSNumber object:
[sender performSelector:selector withObject:[NSNumber numberWithFloat:progress/total]];
-(void) updateProgress:(NSNumber *)aProgress {
float fProgress = [aProgress floatValue];
}

It's because -performSelector:withObject: only works for Objective-C objects. float isn't one of these.
Why not just use
[(TheClass*)sender updateProgress:progress/total];
?

Related

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

Reactive Cocoa Splitting a signal without code duplication?

I am trying to change the label on a button up on the selector being called.
It appears that the code is duplicated. Is there a way perhaps it's not obvious to me right now to have the signal switch after the map ? or no ?
[[[pressedStart map:^id(id value) {
UIButton* button = value;
BOOL transform = [button.titleLabel.text isEqualToString:#"Start"];
return [NSNumber numberWithBool:transform];
}] filter:^BOOL(id value) {
return [value boolValue];
}] subscribeNext:^(id x) {
self.start.titleLabel.text = #"Stop";
}];
[[[pressedStart map:^id(id value) {
UIButton* button = value;
BOOL transform = [button.titleLabel.text isEqualToString:#"Stop"];
return [NSNumber numberWithBool:transform];
}] filter:^BOOL(id value) {
return [value boolValue];
}] subscribeNext:^(id x) {
self.start.titleLabel.text = #"Start";
}];
First of all, in order to change the button's title, you have to call its setTitle:forState: method.
Also please note that using self inside the subscribeNext block is likely to create a retain cycle (and therefore a memory leak). You can read more about it in this answer. You can use #weakify / #strongify macros or, as mentioned in that answer, use rac_liftSelectors:withSignals: method (which IMHO seems to be cleaner).
Your code can be simplified as you actually don't need to split the signal at all. You can use a simple condition inside the map block and return the value which should be the button's title after it was pressed. This value will be sent as a next value of the resulting signal. You can also use startWith: operator to set the initial value (I guess it should be "Start").
RACSignal *buttonTextSignal = [[pressedStart map:^id(UIButton *buttonPressed) {
return [buttonPressed.titleLabel.text isEqualToString:#"Start"] ? #"Stop" : #"Start";
}]
startWith:#"Start"];
[self.start rac_liftSelector:#selector(setTitle:forState:) withSignals:buttonTextSignal, [RACSignal return:#(UIControlStateNormal)], nil];
What does rac_liftSelector:withSignals: do? Each time one of the signals sends its next value, it invokes the method identified by the selector (in this case setTitle:forState:). The method is invoked with next values of the signals as its parameters. So in our case it will initially call:
[self.startButton setTitle:#"Start" forState:UIControlStateNormal];
If you wanted to set a single property (let's say titleLabel.text), you could bind it with RAC macro:
RAC(self.startButton, titleLabel.text) = buttonTextSignal;
Unfortunately, it only works for setting properties, and in your case you have to call a method with two arguments, that's why you have to use rac_liftSelector:withSignals.
As I said, you could achieve the desired result using subscribeNext:
#weakify(self);
RACSignal *buttonTextSignal = [[[pressedStart map:^id(UIButton *buttonPressed) {
return [buttonPressed.titleLabel.text isEqualToString:#"Start"] ? #"Stop" : #"Start";
}]
startWith:#"Start"]
subscribeNext:^(NSString *title) {
#strongify(self);
[self.startButton setTitle:title forState:UIControlStateNormal];
}];
But as you can see, you should take extra care to avoid a retain cycle, using #weakify and #strongify macros.

Passing arguments to selector that is an argument

I have the following method
-(void)changeLevelWithTimeInterval:(NSTimeInterval)timeInterval withLevel:(NSUInteger)level{
[NSTimer scheduledTimerWithTimeInterval:timeInterval
target:self
selector:#selector(changeLevel:)
userInfo:nil
repeats:NO];
}
and I want to call the changeLevel:(NSUInteger)level method with the level argument of the method above.
I have tried to put
[self performSelector:#selector(changeLevel:) withObject:level]
and tried to pass this as an argument but it doesn't work.
performSelector:withObject: takes a pointer object (aka an id) as an argument, so there is no way to pass in an NSUInteger as an argument, as NSUInteger is just a typedeffed unsigned int. One particular workaround is to encapsulate the level in an NSNumber object, and get the intValue of the NSNumber in the changeLevel: method.
It may go as:
NSNumber *levelNumber = [NSNumber numberWithInt:(int)level];
[self performSelector:#selector(changeLevel:) withObject:levelNumber];
And change the signature and implementation of changeLevel: as follows:
-(void)changeLevel:(NSNumber*)levelNumber{
int level = [levelNumber intValue];
//do anything you want with the level..
}
For firing the method after an interval, you can try:
[self performSelector:#selector(changeLevel:) withObject:levelNumber afterDelay:timeInterval];
Instead of scheduling a timer directly.
I ended up using the following method instead of the method above to make everything work correctly.
-(void)changeLevelWithTimeInterval:(NSTimeInterval)timeInterval withLevel:(NSUInteger)level{
NSNumber *levelNumber = [NSNumber numberWithInt:(int)level];
[self performSelector:#selector(changeLevel:) withObject:levelNumber afterDelay:timeInterval];
}

Obj C and makeObjectsPerformSelector - am I overlooking something?

being still kinda new to obj-c, I was playing around with the makeObjectsPerformSelector method.
I have two arrays containing UISteppers and UITextfields respectively:
_stepper = [NSArray arrayWithObjects:
_stepMa, _stepMafree, _stepDe, _stepDefree, _stepFl, _stepFlfree,
_stepEn, _stepEnfree, _stepEnBl, _stepEnBlfree, _stepVo, _stepVofree,
_stepVe, _stepVefree, _stepIn, _stepInfree, _stepOt, _stepOtfree,
_stepIn170, _stepIn170free, _stepZy, _stepZyfree,
nil];
_fields = [NSArray arrayWithObjects:
_MaFeld, _MaFeldfree, _DeFeld, _DeFeldfree, _FlFeld, _FlFeldfree,
_EnFeld, _EnFeldfree, _EnBlFeld, _EnBlFeldfree, _VoFeld, _VoFeldfree,
_VeFeld, _VeFeldfree, _InFeld, _InFeldfree, _OtFeld, _OtFeldfree,
_InFeld170, _InFeld170free, _ZyFeld, _ZyFeldfree,
nil];
In some method I want to reset them:
- (void) resetFields
{
[_stepper enumerateObjectsUsingBlock: ^(UIStepper* stepper, NSUInteger idx, BOOL *stop)
{
stepper.value = 0;
}];
[_fields enumerateObjectsUsingBlock: ^(UITextField* field, NSUInteger idx, BOOL *stop)
{
field.text = #"0";
}];
}
which works as expected.
trying to shorten that code a bit I tried my luck with the mentioned method:
- (void) resetFields
{
[_stepper makeObjectsPerformSelector:#selector(value) withObject:0];
[_fields makeObjectsPerformSelector:#selector(text) withObject:#"0"];
}
which had no effect... I guess there is something I did not consider, but what?
Thanks!
To bypass the problem of int to be a C type and not an object, use KVC (Key-Value Coding). If you call setValue:forKey: on a NSArray object, the method setValue:forKey: is call on each of the objects of the array. And with a bonus, KVC is managing all the primitive stuff.
[_stepper setValue:#0 forKey:#"value"];
[_fields setValue:#"0" forKey:#"text"];
performSelector calls (all kinds of them) can only take Objective-C objects (ones that can be represented by id type). C types like double, BOOL, int etc. will not work, so you can not set value this way unless you change its type to NSNumber*.
For setting text property, you need to use setText: selector; text is the getter. Since this property type is Objective-C class NSString, performSelector will work.

NSArray, trying to pass the index number of the object in the array to a function

I have spent many hours trying to find a solution, so if it IS here somewhere and I missed it I am sorry ...
In .h
#property (nonatomic, strong) NSArray *physicalMan;
-(int) getManeuverRating:(int *) value;
In .m
physicalMan = [[NSArray alloc] initWithObjects:#"Grip-Hold", #"Strike", #"Fall", #"Response", nil];
NSLog(#" The second element is: %#", [physicalMan objectAtIndex:1]);
NSLog (#" This is the index location of Grip-Hold: %i", [physicalMan indexOfObject:#"Grip-Hold"]);
[self getManeuverRating:[physicalMan indexOfObject:#"Grip-Hold"]];
}
-(int) getManeuverRating:(int *) value
{
int a = *value;
return a + 1;
}
The NSLogs work fine with the proper values, which is why I am so confused as to why the function will not work.
The compiler warning says "Incompatible integer to pointer conversion sending 'NSUInteger' (aka 'unsigned int') to parameter of type 'int *'"
I have tried removing the * and I have tried to find other data types, and converting data types, and I cannot get anything to work correctly. Please help or point me in the right direction ... what am I doing wrong? what am I missing?
The indexOfObject: method of NSArray returns an NSUInteger, not an int*. Passing an int to a method that takes int* is incorrect: the value at the corresponding memory location would not be valid.
You should change the getManeuverRating: method as follows:
-(int) getManeuverRating:(NSUInteger) value
{
return value + 1;
}
You are not pointing to an int... you should make this function
-(NSInteger)getManeuverRating:(NSInteger) value
{
NSinteger a = value;
return a + 1;
}
If that is giving you issues you should also try casting the integer in the initial function...
So instead of
[self getManeuverRating:[physicalMan indexOfObject:#"Grip-Hold"]];
Do
NSInteger index = (NSInteger) [physicalMan indexOfObject:#"Grip-Hold"];
[self getManeuverRating:index];
You should be using NSInteger instead of int simply because it is good to write in objective-c syntax. But it is just a wrapper. You could also make it take and return an NSUInteger and not cast it.
Another modernization thing you could do (and this is an aside) is declare your array like this...
NSArray * physicalMan = #[#"Grip-Hold", #"Strike", #"Fall", #"Response"];

Resources