How to use native C types with performSelectorOnMainThread:? - objective-c

I'd like to call (void)setDoubleValue:(double)value using performSelectorOnMainThread:.
What I thought would work is:
NSNumber *progress = [NSNumber numberWithDouble:50.0];
[progressIndicator performSelectorOnMainThread:#selector(setDoubleValue:)
withObject:progress
waitUntilDone:NO];
Didn't work.
Then I implemented a helper method:
- (void)updateProgressIndicator:(NSNumber *)progress
{
[progressIndicator setDoubleValue:[progress doubleValue]];
}
Works, but not really clean.
After that I tried it with NSInvocation.
NSInvocation *setDoubleInvocation;;
SEL selector = #selector(setDoubleValue:);
NSMethodSignature *signature;
signature = [progressIndicator methodSignatureForSelector:selector];
setDoubleInvocation = [NSInvocation invocationWithMethodSignature:signature];
[setDoubleInvocation setSelector:selector];
[setDoubleInvocation setTarget:progressIndicator];
double progress = 50.0;
[setDoubleInvocation setArgument:&progress atIndex:2];
[setDoubleInvocation performSelectorOnMainThread:#selector(invoke)
withObject:nil
waitUntilDone:NO];
This solution works, but it uses a lot of code and is quite slow. (Even if I store the invocation.)
Is there any other way?

If you are on Snow Leopard, you can use Blocks:
dispatch_async(dispatch_get_main_queue(), ^{
[progressIndicator setDoubleValue: 50.0];
});

you'll need to write a custom un-boxing method to wrap setDoubleValue:.
- (void) setDoubleValueAsNumber: (NSNumber *) number {
[self setDoubleValue: [number doubleValue]];
}

Dave Dribin has a solution for this that takes the shape of a category on NSObject. His category wraps the method call in an NSInvocation and invokes that on the main thread. This way, you can use whatever method interface you like, including primitive types for your arguments.
The Amber framework also has a category on NSObject that adds a main thread proxy, where any messages sent to that proxy are executed on the main thread.

This blog post: http://www.cimgf.com/2008/03/01/does-objective-c-perform-autoboxing-on-primitives/ points out that while Cocoa won't autobox primitives, it will unbox them automatically. So numbers and BOOLs at least can be passed in as an NSNumber class, and the called function will automatically unbox it. I've been playing with using a proxy object (Uli's UKMainThreadProxy*) which works quite well, although I'm sure it has it's limitations like anything else.
http://zathras.de/programming/cocoa/UKKQueue.zip/UKKQueue/UKMainThreadProxy.m

Related

Method Swizzling in Objective-C

I read an article about "Method Swizzling in Objective-C". In this article the meaning of "Method Swizzing" is to exchange the implementations of two methods. The sample is as below shows:
- (void) logged_viewDidAppear:(BOOL)animated {
[self logged_viewDidAppear:animated];
NSLog(#"logged view did appear for %#", [self class]);
}
+ (void)load {
static dispatch_once_t once_token;
dispatch_once(&once_token, ^{
SEL viewWillAppearSelector = #selector(viewDidAppear:);
SEL viewWillAppearLoggerSelector = #selector(logged_viewDidAppear:);
Method originalMethod = class_getInstanceMethod(self, viewWillAppearSelector);
Method extendedMethod = class_getInstanceMethod(self, viewWillAppearLoggerSelector);
method_exchangeImplementations(originalMethod, extendedMethod);
});
}
- (void) logged_viewDidAppear:(BOOL)animated {
[self logged_viewDidAppear:animated];
NSLog(#"logged view did appear for %#", [self class]);
}
In the article, one sentence is
It may seem this this method makes a nonsensical recursive call to itself, but it won’t actually be doing that after we swizzle it.
But I am not very clear about this statement,does it mean that when we write the code like below:
[self viewDidAppear],
since its implementation became "logged_viewDidAppear", so the program goes to method "logged_viewDidAppear", and in that method as we can see, the first line is [self logged_viewDidAppear:animated]; for the same reason, the method becomes "viewDidAppear". Is my word correct?
Yep. When viewDidAppear is called, it actually would be swizzled to the implementation of logged_viewDidAppear at runtime, so does logged_viewDidApear being called.
Check this:http://nshipster.com/method-swizzling/ and this
https://www.bignerdranch.com/blog/inside-the-bracket-part-7-runtime-machinations/

A nice way to perform a selector on the main thread with two parameters?

I'm searching for a nice way to perform a selector on the main thread with two parameters
I really like using
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
method, except now I have two parameters.
So basically I have a delegate which I need to notify when the image is loaded:
[delegate imageWasLoaded:(UIImage *)image fromURL:(NSString *)URLString;
But the method where I do this might be invoked in the background thread, and the delegate will use this image to update the UI, so this needs to be done in the main thread. So I really want the delegate to be notified in the main thread as well.
So I see one option - I can create a dictionary, this way I have only one object, which contains two parameters I need to pass.
NSDictionary *imageData = [NSDictionary dictionaryWithObjectsAndKeys:image, #"image", URLString, #"URLstring", nil];
[(NSObject *)delegate performSelectorOnMainThread:#selector(imageWasLoaded:) withObject: imageData waitUntilDone:NO];
But this approach does not seem right to me. Is there more elegant way to do this? Perhaps using NSInvocation?
Thanks in advance.
Using an NSDictionary to pass multiple parameters is the right way to go about it in this case.
However, a more modern method is to use GCD and blocks, this way you can send messages to an object directly. Also, it looks as if your delegate method might be doing something UI updates; which you are correctly handling on the main thread. With GCD you can do this easily, and asynchronously like this:
dispatch_async(dispatch_get_main_queue(), ^{
[delegate imageWasLoaded:yourImage fromURL:yourString;
});
Replace your performSelector:withObject call with this, and you won't have to mess around with changing your method signatures.
Make sure you:
#import <dispatch/dispatch.h>
to bring in GCD support.
Since you don't have access to GCD, NSInvocation is probably your best choice here.
NSMethodSignature *sig = [delegate methodSignatureForSelector:selector];
NSInvocation *invoke = [NSInvocation invocationWithMethodSignature:sig];
[invoke setTarget:delegate]; // argument 0
[invoke setSelector:selector]; // argument 1
[invoke setArgument:&arg1 atIndex:2]; // arguments must be stored in variables
[invoke setArgument:&arg2 atIndex:3];
[invoke retainArguments];
/* since you're sending this object to another thread, you'll need to tell it
to retain the arguments you're passing along inside it (unless you pass
waitUntilDone:YES) since this thread's autorelease pool will likely reap them
before the main thread invokes the block */
[invoke performSelectorOnMainThread:#selector(invoke) withObject:nil waitUntilDone:NO];
Following method can also be used:
- (id)performSelector:(SEL)aSelector withObject:(id)anObject withObject:(id)anotherObject
As per the docs of this method-
Invokes a method of the receiver on the current thread using the default mode after a delay.
Yes, you've got the right idea: you need to encapsulate all the data you want to pass to the delegate on the main thread into one single object which gets passed along via performSelectorOnMainThread. You can pass it along as a NSDictionary object, or a NSArray object, or some custom Objective C object.

Does nsinvocation invoked?

I'am a newby in objective c and iphone developing. I confused. I trying to create button that are created at the runtime,after clicking another button,and application doesn't know it:
-(void)button4Pushed{
NSLog(#"Button 4 pushed\n");
Class cls = NSClassFromString(#"UIButton");//if exists {define class},else cls=nil
id pushButton5 = [[cls alloc] init];
CGRect rect =CGRectMake(20,220,280,30);
NSValue *rectValue = [NSValue valueWithCGRect:rect];
//--------------1st try set frame - work,but appears at wrong place
//[pushButton5 performSelector:#selector(setFrame:) withObject:rectValue];
//--------------2nd try set frame + this work correctly
[pushButton5 setFrame: CGRectMake(20,220,280,30)];
//this work correct [pushButton5 performSelector:#selector(setTitle:forState:) withObject:#"5th Button created by 4th" withObject:UIControlStateNormal];
//but i need to use invocation to pass different parameters:
NSMethodSignature *msignature;
NSInvocation *anInvocation;
msignature = [pushButton5 methodSignatureForSelector:#selector(setTitle:forState:)];
anInvocation = [NSInvocation invocationWithMethodSignature:msignature];
[anInvocation setTarget:pushButton5];
[anInvocation setSelector:#selector(setTitle:forState:)];
NSNumber* uicsn =[NSNumber numberWithUnsignedInt:UIControlStateNormal];
NSString *buttonTitle = #"5thbutton";
[anInvocation setArgument:&buttonTitle atIndex:2];
[anInvocation setArgument:&uicsn atIndex:3];
[anInvocation retainArguments];
[anInvocation invoke];
[self.view addSubview:(UIButton*)pushButton5];
}
What am I doing wrong? Invocation is invoked, but there is no result...
I know that I can create it this way:
UIButton *pushButton3 = [[UIButton alloc] init];
[pushButton3 setFrame: CGRectMake(20, 140, 280, 30)];
[pushButton3 setTitle:#"I'm 3rd button!created by 2nd" forState:UIControlStateNormal];
[self.view addSubview:pushButton3];
But I need to use invocation, and don't know why does it not working?
Thanks for your help.
Your setting an NSNumber * as your second argument, but the method you are trying to invoke (effectively) requires an int. Use your exact same code but try these lines instead:
UIControlState uicsn = UIControlStateNormal;
// then
[anInvocation setArgument:&uicsn atIndex:3];
Why do you need to use invocation?
Update: Based on the use case, unless you don't control the other classes, I would instead use a protocol with a method matching this signature and collect objects on which calling this is legal.
NSInvocation is a runtime building block/last-resort class; you are supposed to use the other tools available to you, like protocols (if an object has a method) and Blocks or function pointers (if you just want the disembodied function) if you can at all control the surrounding objects.
Perception's answer solves the technical problem, but you might be making things a lot more complicated for yourself.

Warning about making pointer from integer without a cast -- explanation needed

I have this code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic
NSLog(#"didSelectRowAtIndexPath");
//The hud will dispable all input on the view
HUD = [[MBProgressHUD alloc] initWithView:self.view];
// Add HUD to screen
[self.view addSubview:HUD];
// Regisete for HUD callbacks so we can remove it from the window at the right time
HUD.delegate = self;
HUD.labelText = #"Loading Events, Please Wait..";
int i = indexPath.row;
//Show the HUD while the provided method executes in a new thread
[HUD showWhileExecuting:#selector(loadData:) onTarget:self withObject:i animated:YES];
}
And I get this warning:
warning: passing argument 3 of 'showWhileExecuting:onTarget:withObject:animated:' makes pointer from integer without a cast
Can somebody please explain what I'm doing wrong here? Could someone also briefly explain the situation with ints in Objective-C, coming from Java I find it odd that they are so confusing to use.
The problem is that showWhileExecuting:onTarget:withObject:animated: takes an object as its third argument. To get aroung this, you can wrap integers as objects using the NSNumber class
[NSNumber numberWithInt:i]
You will then have to unwrap the argument in the loadData: method by calling
[argument intValue]
The method takes an object as a third argument (withObject), but you passed an int instead.
Apparently, you provided an integer(int i) instead of an object pointer(type of id). It is not safe. Use NSNumber instead.
int i;
...
NSNumber * numberI = [NSNumber numberWithInt:i];
[HUD showWhileExecuting:#selector(loadData:) onTarget:self withObject:i animated:YES];
All of the answers above are the "correct" ones. I.e. be a good boy and use and NSNumber to pass the value.
However, … the following will work
"damn you, compiler, i'm smarter than you are:"
(cast your integer, totally not a valid object, to id)
[HUD showWhileExecuting:#selector(loadData:)
onTarget:self
withObject:(id)i
animated:YES];
i'm guessing (you didn't say), that your load data method looked like this:
- (void)loadData:(int)i { …
you will see code like this, which is the only reason i mentioned it.
you should be familiar with it.
someone thinks that saving 1 object allocation is going to make their code efficient; don't sweat object allocations, and wrap it up in an NSNumber as shown above
most C compilers will handle this correctly, but it's not guaranteed

How to use performSelector:withObject:afterDelay: with primitive types in Cocoa?

The NSObject method performSelector:withObject:afterDelay: allows me to invoke a method on the object with an object argument after a certain time. It cannot be used for methods with a non-object argument (e.g. ints, floats, structs, non-object pointers, etc.).
What is the simplest way to achieve the same thing with a method with a non-object argument? I know that for regular performSelector:withObject:, the solution is to use NSInvocation (which by the way is really complicated). But I don't know how to handle the "delay" part.
Thanks,
Here is what I used to call something I couldn't change using NSInvocation:
SEL theSelector = NSSelectorFromString(#"setOrientation:animated:");
NSInvocation *anInvocation = [NSInvocation
invocationWithMethodSignature:
[MPMoviePlayerController instanceMethodSignatureForSelector:theSelector]];
[anInvocation setSelector:theSelector];
[anInvocation setTarget:theMovie];
UIInterfaceOrientation val = UIInterfaceOrientationPortrait;
BOOL anim = NO;
[anInvocation setArgument:&val atIndex:2];
[anInvocation setArgument:&anim atIndex:3];
[anInvocation performSelector:#selector(invoke) withObject:nil afterDelay:1];
Just wrap the float, boolean, int or similar in an NSNumber.
For structs, I don't know of a handy solution, but you could make a separate ObjC class that owns such a struct.
DO NOT USE THIS ANSWER. I HAVE ONLY LEFT IT FOR HISTORICAL PURPOSES. SEE THE COMMENTS BELOW.
There is a simple trick if it is a BOOL parameter.
Pass nil for NO and self for YES. nil is cast to the BOOL value of NO. self is cast to the BOOL value of YES.
This approach breaks down if it is anything other than a BOOL parameter.
Assuming self is a UIView.
//nil will be cast to NO when the selector is performed
[self performSelector:#selector(setHidden:) withObject:nil afterDelay:5.0];
//self will be cast to YES when the selector is performed
[self performSelector:#selector(setHidden:) withObject:self afterDelay:10.0];
Perhaps NSValue, just make sure your pointers are still valid after the delay (ie. no objects allocated on stack).
I know this is an old question but if you are building iOS SDK 4+ then you can use blocks to do this with very little effort and make it more readable:
double delayInSeconds = 2.0;
int primitiveValue = 500;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self doSomethingWithPrimitive:primitiveValue];
});
PerformSelector:WithObject always takes an object, so in order to pass arguments like int/double/float etc..... You can use something like this.
//NSNumber is an object..
[self performSelector:#selector(setUserAlphaNumber:) withObject: [NSNumber numberWithFloat: 1.0f]
afterDelay:1.5];
-(void) setUserAlphaNumber: (NSNumber*) number{
[txtUsername setAlpha: [number floatValue] ];
}
Same way you can use [NSNumber numberWithInt:] etc.... and in the receiving method you can convert the number into your format as [number int] or [number double].
Blocks are the way to go. You can have complex parameters, type safety, and it's a lot simpler and safer than most of the old answers here. For example, you could just write:
[MONBlock performBlock:^{[obj setFrame:SOMETHING];} afterDelay:2];
Blocks allow you to capture arbitrary parameter lists, reference objects and variables.
Backing Implementation (basic):
#interface MONBlock : NSObject
+ (void)performBlock:(void(^)())pBlock afterDelay:(NSTimeInterval)pDelay;
#end
#implementation MONBlock
+ (void)imp_performBlock:(void(^)())pBlock
{
pBlock();
}
+ (void)performBlock:(void(^)())pBlock afterDelay:(NSTimeInterval)pDelay
{
[self performSelector:#selector(imp_performBlock:)
withObject:[pBlock copy]
afterDelay:pDelay];
}
#end
Example:
int main(int argc, const char * argv[])
{
#autoreleasepool {
__block bool didPrint = false;
int pi = 3; // close enough =p
[MONBlock performBlock:^{NSLog(#"Hello, World! pi is %i", pi); didPrint = true;} afterDelay:2];
while (!didPrint) {
[NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeInterval:0.1 sinceDate:NSDate.date]];
}
NSLog(#"(Bye, World!)");
}
return 0;
}
Also see Michael's answer (+1) for another example.
I would always recomend that you use NSMutableArray as the object to pass on. This is because you can then pass several objects, like the button pressed and other values. NSNumber, NSInteger and NSString are just containers of some value. Make sure that when you get the object from the array
that you refer to to a correct container type. You need to pass on NS containers. There you may test the value. Remember that containers use isEqual when values are compared.
#define DELAY_TIME 5
-(void)changePlayerGameOnes:(UIButton*)sender{
NSNumber *nextPlayer = [NSNumber numberWithInt:[gdata.currentPlayer intValue]+1 ];
NSMutableArray *array = [[NSMutableArray alloc]initWithObjects:sender, nil];
[array addObject:nextPlayer];
[self performSelector:#selector(next:) withObject:array afterDelay:DELAY_TIME];
}
-(void)next:(NSMutableArray*)nextPlayer{
if(gdata != nil){ //if game choose next player
[self nextPlayer:[nextPlayer objectAtIndex:1] button:[nextPlayer objectAtIndex:0]];
}
}
I also wanted to do this, but with a method that receives a BOOL parameter. Wrapping the bool value with NSNumber, FAILED TO PASS THE VALUE. I have no idea why.
So I ended up doing a simple hack. I put the required parameter in another dummy function and call that function using the performSelector, where withObject = nil;
[self performSelector:#selector(dummyCaller:) withObject:nil afterDelay:5.0];
-(void)dummyCaller {
[self myFunction:YES];
}
I find that the quickest (but somewhat dirty) way to do this is by invoking objc_msgSend directly. However, it's dangerous to invoke it directly because you need to read the documentation and make sure that you're using the correct variant for the type of return value and because objc_msgSend is defined as vararg for compiler convenience but is actually implemented as fast assembly glue. Here's some code used to call a delegate method -[delegate integerDidChange:] that takes a single integer argument.
#import <objc/message.h>
SEL theSelector = #selector(integerDidChange:);
if ([self.delegate respondsToSelector:theSelector])
{
typedef void (*IntegerDidChangeFuncPtrType)(id, SEL, NSInteger);
IntegerDidChangeFuncPtrType MyFunction = (IntegerDidChangeFuncPtrType)objc_msgSend;
MyFunction(self.delegate, theSelector, theIntegerThatChanged);
}
This first saves the selector since we're going to refer to it multiple times and it would be easy to create a typo. It then verifies that the delegate actually responds to the selector - it might be an optional protocol. It then creates a function pointer type that specifies the actual signature of the selector. Keep in mind that all Objective-C messages have two hidden first arguments, the object being messaged and the selector being sent. Then we create a function pointer of the appropriate type and set it to point to the underlying objc_msgSend function. Keep in mind that if the return value is a float or struct, you need to use a different variant of objc_msgSend. Finally, send the message using the same machinery that Objective-C uses under the sheets.
You Could just use NSTimer to call a selector:
[NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(yourMethod:) userInfo:nil repeats:NO]
Calling performSelector with an NSNumber or other NSValue will not work. Instead of using the value of the NSValue/NSNumber, it will effectively cast the pointer to an int, float, or whatever and use that.
But the solution is simple and obvious. Create the NSInvocation and call
[invocation performSelector:#selector(invoke) withObject:nil afterDelay:delay]
Pehaps...ok, very likely, I'm missing something, but why not just create an object type, say NSNumber, as a container to your non-object type variable, such as CGFloat?
CGFloat myFloat = 2.0;
NSNumber *myNumber = [NSNumber numberWithFloat:myFloat];
[self performSelector:#selector(MyCalculatorMethod:) withObject:myNumber afterDelay:5.0];