Calling delayed performSelector: on a static instance from an NSThread? - objective-c

HI, I have a static NSMutableArray* staticArray in an NSOperation subclass (in myOperation.m) and a method:
static NSMutableArray *staticArray =
nil;
+(void) initialize {
staticArray = [[NSMutableArray alloc] init];
}
-(void) addStrToStaticArray:(NSString*)aStr {
if([staticArray indexOfObject:aStr] == NSNotFound) {
[staticArray addObject:aStr];
[staticArray performSelector:#selector(removeObject:)
withObject:aStr
afterDelay:30.];
}
}
I call the above method and after that the operation finishes execution. The problem is that aStr is never removed from the array. What am I missing ? Thanks...

Based to Justin suggestions, I can now delayed remove an object from an array invoking the method from inside a NSThread, NSOperation despite their existence at the time of the removal:
NSMethodSignature * mySignature = [NSMutableArray instanceMethodSignatureForSelector:#selector(removeObject:)];
NSInvocation * myInvocation = [NSInvocation invocationWithMethodSignature:mySignature];
[myInvocation setTarget:staticArray];
[myInvocation setSelector:#selector(removeObject:)];
[myInvocation setArgument:&aStr atIndex:2];
//At this point, myInvocation is a complete object, describing a message that can be sent.
NSTimer *timer = [NSTimer timerWithTimeInterval:90.
invocation:myInvocation
repeats:NO];
if(timer) {
NSRunLoop *mainRL = [NSRunLoop mainRunLoop];
[mainRL addTimer:timer forMode:NSDefaultRunLoopMode];
}
The aStr will be removed from staticArray after 90 seconds. For details...
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/DistrObjects/Tasks/invocations.html

use timer (CFRunLoopTimer/NSTimer) and run loop (CFRunLoop/NSRunLoop) apis to accomplish this.
in that case, you create a timer, and add it to the main run loop.
this would also require that you create a function or method for your timer to call. if you choose a method, you could use an NSInvocation instead (if that's what you prefer).
since the data is static, and the operation (presumably) won't exist, you can message via a class method.

Related

Display NSArray at specific intervals

I have an NSArray that I would like to display each object at a specific time interval, for example:
NSArray *array = [NSArray arrayWithObjects:#"foo",#"bar",#"baz",nil];
and output it:
for (object in array) {
NSLog(#"%#", object);
// wait 3 seconds then show next object
}
so it should do this:
foo
... wait three seconds
bar
... wait three seconds
baz
... wait three seconds
repeat
how can i do this?
The simplest approach would be to use Blocks...
You can explore how to do this with a Blocks using third party categories such as :
https://github.com/zwaldowski/BlocksKit
As for a solution without blocks:
#property (nonatomic, strong) NSArray *myArray;
#property (nonatomic) NSInteger iteration;
- (void)awakeFromNib //Or equivalent method called when you want to start scheduling..
{
self.myArray = [NSArray arrayWithObjects:#"foo",#"bar",#"baz",nil];
self.iteration = 0;
[NSTimer scheduledTimerWithTimeInterval:3
target:self
selector:#selector(fireEvent)
userInfo:nil
repeats:YES]; //Will Fire every 3 seconds...
}
-(void)fireEvent
{
NSLog(#"%#", [self.myArray objectAtIndex:self.iteration]);
self.iteration++;
if (self.iteration>=self.myArray.count) self.iteration=0;
}
If you with to invalidate the timer after all elements from the array are shown. Do the following :
-(void)awakeFromNib //Or equivalent method called when you want to start scheduling..
{
self.myArray = [NSArray arrayWithObjects:#"foo",#"bar",#"baz",nil];
self.iteration = 0;
[NSTimer scheduledTimerWithTimeInterval:3
target:self
selector:#selector(fireEvent:)
userInfo:nil
repeats:YES]; //Will Fire every 3 seconds...
}
-(void)fireEvent:(NSTimer*)timer
{
NSLog(#">>>> %#", [self.myArray objectAtIndex:self.iteration]);
self.iteration++;
if (self.iteration>=self.myArray.count) {
self.iteration=0;
[timer invalidate];
}
}
NOTE: The latter code only adds a ':' to the #selector(fireEvent:). And reads the NSTimer reference in the fireEvent as a parameter.
Depending on the surrounding context, you might be able to do what you want by letting an NSRunLoop run for 3 seconds. Alternatively, and perhaps a bit safer and saner, you could use an NSTimer to run an action every 3 seconds.
I'd originally suggested breaking up the loop, logging the first element, then perform the same action on the rest of the array after a 3-second delay, using either something like performSelector:withObject:afterDelay: or dispatch_after, but as danielbeard pointed out, NSTimer provides a much more straight-forward way to cancel the action (cancellation is possible with the other methods, but requires planning and manual work).
Edit: If I were going to do this with an NSTimer, I'd probably use this NSTimer+Blocks category and do something like (untested code):
__block NSUInteger index = 0;
NSTimer *timer;
timer = [NSTimer scheduledTimerWithTimeInterval:3.0 block:^{
NSLog(#"%#", array[index]);
index++;
if (index >= [array count]) {
[timer invalidate];
}
} repeats:YES];

passing a block in #selector()

How do I pass a block, and what would it look like, in the method incrementCount:completion to get the property self.count returned after its increment in the CounterClass? I'm not sure if the way I defined the block parameter (void(^)(void))callback; in the method is correct i.e. should it also have a return value?
ViewController
[NSTimer scheduledTimerWithTimeInterval:3.0
target:self.counterClass
selector:#selector(incrementCount:completion:)
userInfo:nil
repeats:YES];
CounterClass
-(void)incrementCount:(NSTimer *)timer completion:(void(^)(void))callback;
{
self.count += 1;
}
NSTimer expects to call a method which takes zero or one parameters, if there is a parameter it should be the timer instance itself.
So, you can't have a method with 2 parameters where one is a block.
Instead, remove the second parameter and simply call another method or block in the method implementation. The block could be stored as an #property of the class.
You can used dispatch_after.
ViewController:
[self.counterClass incrementCountWithCompletion:^{
// Your block code
NSLog(#"block code");
}];
CounterClass:
-(void)incrementCountWithCompletion:(void(^)(void))block;
{
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
dispatch_queue_t queue = dispatch_get_main_queue(); // Choose whatever queue is approapriate to you
//Beware of retain cycles and use weak self pattern appropriately
dispatch_after(delay, queue, ^{
self.count += 1;
block();
[self incrementCountWithCompletion:block];
});
}
You can add your block to the userInfo:
[NSTimer scheduledTimerWithTimeInterval:3.0 target:self.counterClass selector:#selector(incrementCount:) userInfo:#{#"completion" : [yourBlock copy]} repeats:YES];
CounterClass
- (void)incrementCount:(NSTimer *)timer {
self.count += 1;
void (^completion)(void) = timer.userInfo[#"completion"];
}
For more on storing a block in a dictionary: blocks in nsdictionary?

Can I pass delegate as a parameter objective-c

I am working with an NSOperationQueue and I want to add new NSOperations to the NSOperationQueue. It is a queue that lives in a singleton instance of a class I have. It would make things a lot easier if I could move everything into the static class by passing the delegate.
Here is my code now as it lives in - this is in a cellForRowAtIndexPath
NSString *key = [NSString stringWithFormat:#"%#%#",cell.dataItem.ItemID, cell.dataItem.ManufacturerID];
if (![self.imgOperationInQueue valueForKey:key]) {
ImageOperation *imgOp = [[ImageOperation alloc] initWithItemID:cell.dataItem.ItemID withManufacturerID:cell.dataItem.ManufacturerID withReurnType:kThumbnail];
imgOp.identifier = [NSString stringWithFormat:#"%i", cell.tag];
imgOp.delegate = self;
[[SharedFunctions sharedInstance] addImageOperationToQueue:imgOp];
[imgOp release];
// store these in the dictionary so we don;t queue up requests more than once
[self.imgOperationInQueue setValue:cell.dataItem.ItemID forKey:key];
}
If I could add the delegate as a parameter I could put all of this code into the shared singleton class and call it from anywhere in my app.
I suppose that I could use an NSNotification - or can I use a block of some sort?
Just create the appropriate init method that passes in the delegate.
- (id)initWithItemID:(NSString *)itemID
withManufacturerID:(NSString *)manufacturerID
withReurnType:(NSInteger)type
delegate:(id<YourDelegate>)theDelegate
{
self = [super init];
if (self)
{
.... // Other assignments
self.delegate = theDelegate;
}
return self;
}

Create a SEL with an object passed into it

I know how to use:
[self method:object];
But is it possible to get a SEL object of this?
SEL method = #selector(method:object);
Doesn't work.
Thanks :)
A SEL is just the selector - the name of the message that's sent. To capture a specific instance of that message, its arguments, and its return value as an object, you need to use NSMethodSignature and NSInvocation. An example, based on your hypothetical -method:object above:
NSMethodSignature *sig = [SomeClass instanceMethodSignatureForSelector:#selector(method:)];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sig];
// Assume that someObject is an instance of SomeClass
[inv setTarget:someObject];
// Assume an "id object" declared elsewhere.
// Also note that self & _cmd are at indices 0 & 1, respectively
[inv setArgument:&object atIndex:2]
// Some time later...
[inv invoke];
Note that, because an NSInvocation is an object, it doesn't have to be invoked immediately. It can be stored for later use, and usually is - there are far easier ways to send a message if one wants to do so immediately. Cocoa's standard undo/redo machinery, for example, is based on storing and invoking NSInvocations.
A #selector is something that is of another method or function.
Take this for an example:
-(IBAction)timerStart {
timer = [NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(targetMethod:)
userInfo:nil
repeats:NO];
}
-(void)targetMethod:(id)sender {
[timer invalidate];
timer = nil;
}
As you can see, the selector (targetMethod:) is being called to action after two seconds of the NSTimer is run. The targetMethod: is a (void)function:(id)sender and therefore that is run.
In your case, what I think you're trying to accomplish is
[self performSelector:#selector(methodName:)];

Accessing Instance Variables from NSTimer selector

Firstly newbie question: What's the difference between a selector and a method?
Secondly newbie question (who would have thought): I need to loop some code based on instance variables and pause between loops until some condition (of course based on instance variables) is met. I've looked at sleep, I've looked at NSThread. In both discussions working through those options many asked why don't I use NSTimer, so here I am.
Ok so it's simple enough to get a method (selector? ) to fire on a schedule. Problem I have is that I don't know how to see instance variables I've set up outside the timer from within the code NSTimer fires. I need to see those variables from the NSTimer selector code as I 1) will be updating their values and 2) will set labels based on those values.
Here's some code that shows the concept… eventually I'd invalidate the timers based on myVariable too, however I've excluded that for code clarity.
MyClass *aMyClassInstance = [MyClass new];
[aMyClassInstance setMyVariable:0];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(doStuff) userInfo:nil repeats:YES];
[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(doSomeOtherStuff) userInfo:nil repeats:YES];
- (void) doStuff {
[aMyClassInstance setMyVariable:11]; // don't actually have access to set aMyClassInstance.myVariable
[self updateSomeUILabel:[NSNumber numberWithInt:aMyClassInstance.myVariable]]; // don't actually have access to aMyClassInstance.myVariable
}
- (void) doSomeOtherStuff {
[aMyClassInstance setMyVariable:22]; // don't actually have access to set aMyClassInstance.myVariable
[self updateSomeUILabel:[NSNumber numberWithInt:aMyClassInstance.myVariable]]; // don't actually have access to aMyClassInstance.myVariable
}
- (void) updateSomeUILabel:(NSNumber *)arg{
int value = [arg intValue];
someUILabel.text = [NSString stringWithFormat:#"myVariable = %d", value]; // Updates the UI with new instance variable values
}
You can use the userInfo parameter to transmit arbitrary object. In this case, you pass aMyClassInstance as userInfo:
MyClass *aMyClassInstance = [MyClass new];
[aMyClassInstance setMyVariable:0];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(doStuff) userInfo:aMyClassInstance repeats:YES];
In the timer callback (which MUST takes a parameter), you get back the userInfo from the timer and cast it:
- (void) doStuff:(NSTimer *)timer {
MyClass *instance = (MyClass *)[timer userInfo];
[instance setMyVariable:11];
[self updateSomeUILabel:[NSNumber numberWithInt:instance.myVariable]];
}
The neat thing is that the timer retains the userInfo parameter.
One of your questions was asking about the difference between a selector and a method.
A selector “selects” the method to use from an object. Imagine you had some animal classes, say Dog, Cat, and Bird, all subclasses of Animal. They all implement a method called makeSound. Each class will have its own implementation of makeSound, otherwise all of the animals will sound the same. So, all animals have a different method for making a sound, but you get each animal to make its sound using the same selector. You are selecting the makeSound method of an animal, in other words.
You do have access to instance variables if you set the instance as the target of the timer like so:
[NSTimer scheduledTimerWithTimeInterval:1.0 target:aMyClassInstance selector:#selector(doStuff) userInfo:nil repeats:YES];
The instance (which you've referred to as aMyClassInstance) will be self.
Alternatively you can put aMyClassInstance and any other objects in the userInfo dictionary. You would do that like so:
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
aMyClassInstance, #"a",
bMyClassInstance, #"b",
cMyClassInstance, #"c",
nil];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(doStuff:) userInfo:userInfo repeats:YES];
Then, in the doStuff: selector, you can get them back out like so:
-(void) doStuff:(NSTimer*)timer;
{
MyClass* aMyClassInstance = [[timer userInfo] objectForKey:#"a"];
MyClass* bMyClassInstance = [[timer userInfo] objectForKey:#"b"];
MyClass* cMyClassInstance = [[timer userInfo] objectForKey:#"c"];
//do whatever you want here
}