create a Selector SEL using block with no parameter in Xcode - ios

I am working on Blocks programming, i have created a block.
void(^paint)(void)=^(void){
NSLog(#"Process ");
};
now i want to create a nstimer using the NSinvocation and NSMethodSignatiureas below.
void(^startPainting)(id)=^(id self){
**SEL selectorToCall=#selector(paint());**
NSMethodSignature *methodSignature=[[self class] instanceMethodSignatureForSelector:selectorToCall];
NSInvocation *invocation=[NSInvocation invocationWithMethodSignature:methodSignature];
[invocation setSelector:selectorToCall];
[invocation setTarget:self];
[self setPaintTimer:[NSTimer scheduledTimerWithTimeInterval:1.0 invocation:invocation repeats:YES] ];
// self.paintTimer=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(paint:) userInfo:nil repeats:YES];
};
The problem here is i want to send and the block paint to SEL.As the current statement is giving error.Please suggest me where i am doing wrong.How i can pass this block to this selector ?
SEL selectorToCall=#selector(paint());

You could use a dispatch-queues instead of selectors, works the same way and much better than the delay caused by firing a selector:
dispatch_async(dispatch_get_main_queue(), paint);
Regards
Heider

Related

Write equivalent code for in iOS 9

In my app I have a following piece of code:
__weak __typeof(self)weakSelf = self;
_pingTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
repeats:YES
block:^(NSTimer * _Nonnull timer)
{
__strong __typeof(weakSelf)strongSelf = weakSelf;
[strongSelf pingWithBlock:nil];
}];
this works perfectly in iOS 10+, but I need the app to support iOS 9 as well. So I needed to provide a method that would work for both.
I tried this:
__weak __typeof(self)weakSelf = self;
_pingTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:weakSelf
selector:#selector(pingWithBlock:)
userInfo:nil
repeats:YES];
pingWithBlock method is defined in the same class, it's an instance method.
But this doesn't seem to work, meaning I get a bad memory access crash.
If anyone has any suggestions it will be highly appreciated.
EDIT:
thanks to #dgatwood explanations code below fixes the issue
- (void)autoPing
{
_pingTimer = [NSTimer scheduledTimerWithTimeInterval:self.autoCheckInterval
target:self
selector:#selector(pingWithBlock)
userInfo:nil
repeats:YES];
}
-(void)pingWithBlock
{
[self pingWithBlock:nil];
}
This is kind of odd. NSTimer retains its target. Maybe that doesn't happen in this case because of the __weak, but I thought it did anyway. *shrugs*
Either way, this sounds like a multithreading race condition:
Your timer isn't retaining the object, so it could go away at any time.
Something else is retaining the object.
The timer is scheduled in the runloop of the thread that was running when the timer was constructed.
That something else disposes of the reference to the object in another thread.
The timer fires in the first thread and the zeroing weak reference hasn't zeroed because the object is still halfway through destroying itself.
A crash occurs.
The best fix is to let the timer retain the target object (by removing all the weakSelf stuff). If the timer is a repeating timer, provide a method to allow the code that disposes of the enclosing object to cancel that timer, and be careful to always call it.

Why does the app crash every time i press start game?

I am trying to move an object across the screen when a button is pressed. I can't really tell what is wrong with the code. Every time I press start game the app crashes and the console says terminating with uncaught exception of type NSException. Also I have 2 warnings but don't know how to fix them.
The code is here:
-(IBAction)startGame:(id)sender{
startGame.hidden = YES;
shipMovingLeft = [NSTimer scheduledTimerWithTimeInterval:0.07 target:self selector:#selector(leftTap) userInfo:nil repeats:YES];
shipMovingRight = [NSTimer scheduledTimerWithTimeInterval:0.07 target:self selector:#selector(rightTap) userInfo:nil repeats:YES];
}
-(void)leftTap:(id)sender{
ship.center = CGPointMake(ship.center.x -1, ship.center.y);
}
-(void)rightTap:(id)sender{
ship.center = CGPointMake(ship.center.x -1, ship.center.y);
}
The warnings that I am getting are undeclared selector 'left tap' and undeclared selector 'right tap'.
Update your timer code to the following:
shipMovingLeft = [NSTimer scheduledTimerWithTimeInterval:0.07 target:self selector:#selector(leftTap:) userInfo:nil repeats:YES];
shipMovingRight = [NSTimer scheduledTimerWithTimeInterval:0.07 target:self selector:#selector(rightTap:) userInfo:nil repeats:YES];
}
You need to include the colons to indicate that their is single parameter for that selector, in this case it is the (id)sender. If there were two parameters, you would use #selector(rightTap::), etc.
For your information, the undeclared selector message is actually a warning, not an error (unless you've enabled treat warnings as errors), this should have provided a clue that there was something wrong with the selector you provided.

Keeping a strong pointer to NSTimer not working

I am having trouble with NSTimer. The below code is called from a method and it creates an NSTimer and sets it to the instance variable self.syncDelay. The if statement at the end of the method checking if the timer is valid gets called.
However when I call this method again before the timer runs out, the if statement at the top of the method doesn't get called. Which, I am guessing, is why [self.syncDelay invalidate]; doesn't invalidate the timer.
I have a property defined as: #property (nonatomic, strong) NSTimer *syncDelay;
The functionality I am looking for is the timer being invalidated when the method is called for the second time before the timer runs out and creating a new timer. Thus effectively resetting the timer.
EDIT: When the method gets called for the second the NSTimer is nil. Which is strange as I am keeping a strong pointer to it.
else {
if ([self.syncDelay isValid]) {
NSLog(#"Timer valid at start of method");
}
[self.syncDelay invalidate];
self.syncDelay = nil;
NSInvocation *timerInvocation = [NSInvocation invocationWithMethodSignature:
[self methodSignatureForSelector:#selector(testMethod:)]];
// configure invocation
[timerInvocation setSelector:#selector(testMethod:)];
[timerInvocation setTarget:self];
[timerInvocation setArgument:&className atIndex:2]; // argument indexing is offset by 2 hidden args
self.syncDelay = [NSTimer scheduledTimerWithTimeInterval:10
invocation:timerInvocation
repeats:NO];
if ([self.syncDelay isValid]) {
NSLog(#"Timer valid at end of method");
}
}

Call an action at specific intervals in iOS

I am trying to invoke a function that contains ccactioninterval in Cocos3d. I want to call that function at specific time intervals.When I tried NSTimer , i found that it works sometimes and sometimes not.
NSTimer makeTarget=[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(createTargets) userInfo:nil repeats:YES];
Here createTargets is the function that contains action events. when i run the function straightit works fine for single time. Problem comes when i try to schedule it. I ve tried different methods already explained for related questions . But nothing worked for me. . . .
Here is the code
-(void) addTargets {
NSTimer *makeTarget = [NSTimer scheduledTimerWithTimeInterval:2.0
target:self selector:#selector(createTargets) userInfo:nil repeats:YES];
}
-(void)createTargets {
CC3MeshNode *target = (CC3MeshNode*)[self getNodeNamed: #"obj1"];
int minVal=-5;
int maxVal=5;
float avgVal;
avgVal = maxVal- minVal;
float Value = ((float)arc4random()/ARC4RANDOM_MAX)*avgVal+minVal ;
[target setLocation:cc3v(Value, 5.0, 0.0)];
CCActionInterval *moveTarget = [CC3MoveBy actionWithDuration:7.0 moveBy:cc3v(0.0, -10.0, 0.0)];
CCActionInterval *removeTarget = [CCCallFuncN actionWithTarget:self selector:#selector(removeTarget:)];
[target runAction:[CCSequence actionOne:moveTarget two:removeTarget]];
}
-(void)removeTarget:(CC3MeshNode*)targ {
[self removeChild:targ];
targ=nil;
}
Without much code its hard to tell what you issues is, but here are some things to try apologies if any of this is obvious.
Are you holding onto a reference to the timer?
This might be useful for debugging. If you have a property called makeTargetTimer, then you could do this:
NSTimer * makeTargetTimer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(createTargets) userInfo:nil repeats:YES];
self.makeTargetTimer = makeTargetTimer // Save to a property for later use (or just use an iVar)
The only way to stop a re-occurring timer is to invalidate it. Therefore you could check to see if its been invalidated.
BOOL isInvalidated = [self.makeTargetTimer isValid];
Also you might want to do this in your dealloc method anyway:
- (void) dealloc {
[_makeTargetTimer invalidate]; // Stops the timer from firing (Assumes ARC)
}
Are you scrolling when the even should be received?
If you want the timer to be fired while scrolling then you need to use NSRunLoopCommonModes. There is a excellent expiation in this question.
[[NSRunLoop currentRunLoop] addTimer:makeTargetTimer forMode:NSRunLoopCommonModes];
What is your implementation of createTargets like?
Have you put NSLog statements on the body of this method. Are you certain its not being called?

NSTimer Category + Blocks implementation to replace selector

I am quite new to blocks and objective-c, and i am trying to write my first category using both. My idea is to create a category on NSTimer that will receive a block as a parameter and this block will be used in the selector call. Right now I have this.
// NSTimer+Additions.h
#import <Foundation/Foundation.h>
typedef void (^VoidBlock)();
#interface NSTimer (NSTimer_Additions)
+ (NSTimer *)scheduleTimerWithTimeInterval:(NSTimeInterval)theSeconds repeats:(BOOL)repeats actions:(VoidBlock)actions;
#end
#import "NSTimer+Additions.h"
static VoidBlock _voidBlock;
#interface NSTimer (AdditionsPrivate) // Private stuff
- (void)theBlock;
#end
#implementation NSTimer (NSTimer_Additions)
+ (NSTimer *)scheduleTimerWithTimeInterval:(NSTimeInterval)theSeconds repeats:(BOOL)repeats actions:(VoidBlock)actions {
[_voidBlock release];
_voidBlock = [actions copy];
NSTimer* timer = [[NSTimer alloc] initWithFireDate:[NSDate date]
interval:theSeconds
target:self
selector:#selector(theBlock)
userInfo:nil
repeats:repeats];
[timer fire];
return [timer autorelease];
}
- (void)theBlock {
_voidBlock();
}
#end
Gist for the code: https://gist.github.com/1065235
Everything compiles fine but i have the following error:
2011-07-05 14:35:47.068 TesteTimer[37716:903] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[NSTimer theBlock]: unrecognized selector sent to class 0x7fff70bb0a18'
How can I make this category work?
Your major flaw besides the wrong target is your use of a static variable. You won't be able to support beyond a single timer.
Using block as argument to the invoked method.
#interface NSTimer (AdditionsPrivate) // Private stuff
- (void)theBlock:(VoidBlock)voidBlock;
#end
#implementation NSTimer (NSTimer_Additions)
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)theSeconds repeats:(BOOL)repeats actions:(VoidBlock)actions {
NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:[self instanceMethodSignatureForSelector:#selector(theBlock:)]];
NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:theSeconds
invocation:invocation
repeats:repeats];
[invocation setTarget:timer];
[invocation setSelector:#selector(theBlock:)];
Block_copy(actions);
[invocation setArgument:&actions atIndex:2];
Block_release(actions);
return timer;
}
- (void)theBlock:(VoidBlock)voidBlock {
voidBlock();
}
#end
The problem with using associative references was the leak as there was no good point to release the block.
Earlier Approach using associative references
You can use associative references to attach the block to that particular instance of NSTimer.
#implementation NSTimer (NSTimer_Additions)
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)theSeconds repeats:(BOOL)repeats actions:(VoidBlock)actions {
NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:[self instanceMethodSignatureForSelector:#selector(theBlock)]];
NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:theSeconds
invocation:invocation
repeats:repeats];
[invocation setTarget:timer];
[invocation setSelector:#selector(theBlock)];
objc_setAssociatedObject(timer, #"Block", actions, OBJC_ASSOCIATION_COPY);
return timer;
}
- (void)theBlock {
VoidBlock _voidBlock = (VoidBlock)objc_getAssociatedObject(self, #"Block");
_voidBlock();
}
#end
What about leveraging userInfo to carry your block? (this is done with ARC)
void (^callback)(void) = ^{
NSLog(#"do stuff");
}
NSTimer *timer = [NSTimer timerWithTimeInterval:10.0 target:self selector:#selector(handleTimeout:) userInfo:[NSDictionary dictionaryWithObject:[callback copy] forKey:#"block"] repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
And then add the static selector of:
+ (void)handleTimeout:(NSTimer *)timer
{
void (^callback)(void) = [timer.userInfo objectForKey:#"block"];
callback();
[timer invalidate];
timer = nil;
};
This should work:
NSTimer* timer = [[NSTimer alloc] initWithFireDate:[NSDate date]
interval:theSeconds
target:timer
selector:#selector(theBlock)
userInfo:nil
repeats:repeats];
The problem is that you're setting the target of the new NSTimer instance to be self. However, in the context of + scheduleTimerWithTimeInterval:repeats:actions: (notice the +), self is NSTimer, and not (as you probably thought) your newly-created NSTimer instance.
As you can see from the error message, your app is crashing because NSTimer doesn't respond to the class method + theBlock, which is of course correct since you only defined the instance method - theBlock.

Resources