I call a heartBeats method per 10ms in a specific thread(not main thread), how to call another method at any time in this same thread?
I subclass NSThread like this
#implementation MyThread
{
NSTimeInterval _lastTimeInterval;
}
- (void)main
{
while (true) {
NSTimeInterval timeInterval = [[NSDate date] timeIntervalSince1970]*1000;
if (timeInterval - _lastTimeInterval > 10)
{
[self heartBeats];
_lastTimeInterval = timeInterval;
}
}
}
- (void)heartBeats
{
NSLog(#"heart beats thread: %#", [NSThread currentThread].description);
}
#end
and run it like this
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"main thread: %#", [NSThread currentThread].description);
MyThread *myThread = [[MyThread alloc]init];
[myThread start];
}
- (void)someMethod
{
// do somthing
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#end
Now,here is the question, how to run - (void)someMethod in myThread?
The main method of your NSThread subclass is everything that runs on that thread. You cannot interrupt it to run other code without the main method's cooperation.
What you really should do is throw out that entire loop and replace it with NSRunLoop and NSTimer.
NSRunLoop keeps the thread alive as long as there's something it will need to do, but also sleeps the thread until it needs to do something.
NSTimer does something on a repeating interval as long as it's scheduled on a run loop.
You need your thread to do two things:
send the MyThread object a heartBeats message (you're doing this)
send the view controller a someMethod message (this is what you asked about)
For the latter, you need one additional thing: A run loop source.
So, clear out your main method and replace it with the following:
Get the current NSRunLoop and store it in a local variable.
Create an NSTimer with a 10-second interval, whose target is self and selector is heartBeats. (Slightly cleaner version: Have another method that takes an NSTimer *but ignores it, so your timer calls that method and that method calls heartBeats. The proper way to set up a timer is to give it a method that expects to be called with a timer, but, in practice, giving it a method that takes no arguments works, too.)
If you didn't create the timer using scheduledTimerWith…, add it to the run loop. (The scheduledTimerWith… methods do this for you.)
Create a run loop source and add it to the run loop.
Call [myRunLoop run].
Step 4 bears explaining:
Core Foundation (but not Foundation; I don't know why) has something called “run loop sources”, which are custom objects that can be added to a run loop and will call something once signaled.
Sounds like what you want, to call your view controller method!
First, in the view controller, change myThread from a local variable in viewDidLoad to an instance variable. Rename it _myThread to make that clear.
Next, add a delegate property to MyThread. This should be weak and have type id <MyThreadDelegate>. You'll also need to define a MyThreadDelegate protocol, which should have one method taking no arguments and returning nothing (void).
You should now be able to set _myThread.delegate = self from the view controller, and implement in the view controller the method that you declared in the protocol. The view controller is now the delegate of its MyThread.
In -[MyThread main], create a version-0 CFRunLoopSource. The Create function takes a “context” structure, containing, among other things, the version (0), an “info” pointer (set this to self, i.e., the MyThread) and a Perform callback (a function, which will be called with the info pointer as its only argument).
In your perform callback, you'll need to do something like this:
MyThread *self = (__bridge MyThread *)info;
[self fireDelegateMessage];
In MyThread, implement that fireDelegateMessage method. In there, send self.delegate the message you declared in your protocol.
Next, add a public method to MyThread (i.e., declare it in MyThread.h as well as implementing it in MyThread.m) named something like “requestDelegateMessage”. In this method, call CFRunLoopSourceSignal on the run loop source. (The documentation for that function suggests that you also need to call CFRunLoopWakeUp on the thread's CFRunLoop. Try it without first.)
Lastly, when the view controller wants someMethod to be called on that thread, call [_myThread requestDelegateMessage].
So:
the view controller calls requestDelegateMessage
requestDelegateMessage signals the run loop source (and wakes up the run loop, if that is needed)
the run loop source calls the perform callback on the MyThread's thread
the perform callback calls fireDelegateMessage on the MyThread's thread
fireDelegateMessage calls the view controller's implementation of the delegate method on the MyThread's thread
the view controller calls someMethod on the MyThread's thread
Related
Sorry about the confusing title, I have this block of code running in its own thread and want to access a class variable inside another class (view controller) everytime its value changed.
To clarify my question here is a simple class structure that represent what I’m trying to accomplish.
#interface classB : NSObject
{
NSThread * _someThread;
}
+ (classB*) instance;
#property(atomic) CVPixelBufferRef pixBufB;
- (void) foo
{
while(1)
{
//decode a frame
//assign data to pixBufB
}
}
- (void) start
{
_someThread = [[NSThread alloc] initWithTarget:self selector:#selector(foo) object:nil];
}
//sampleViewController
#interface sampleViewController : UIViewController
#property(atomic) CVPixelBufferRef pixBuf;
- (void)viewDidLoad
{
[[classB instance] start];
}
- (void) bar
{
_pixBuf = [[classB instance] pixBufB];
}
At the end of each loop cycle in foo, I want to access _pixBufB inside sampleViewController class. Since foo is executed in another thread I can’t simply use a getter, does anyone know how to approach this issue?
Thanks
You can do this work by using NSOperation and NSOperationQueue. You can "task" an assignment to a subclass of NSOperation and overwrite the main method to write a task. Next you need to make an object of NSOperationQueue and add your subclass, and it will start running in the new Queue u created and you can create it as synchronous and asynchronous respectively.
Now you can add a onCompletion Block at the end of the Queue OR the NSOperation itself, and it will be performed. OR you can create another subclass of NSOperation and create your task that you want to perform (foo, here) as another task and addDependency with the First TASK, therefore the second task(foo) will be performed only after the first is finished.
For more about NSOperation and NSOperationQueue, you can visit this link
Have the thread take a block to be called at each point you want to do an update. The block can either be dispatched to the main queue to be called, or it can itself dispatch to the main queue before updating any view controller state.
A number of Cocoa Touch classes leverage a design pattern of coalescing events. UIViews, for example, have a method setNeedsLayout which causes layoutSubviews to be called in the very near future. This is especially useful in situations where a number of properties influence the layout. In the setter for each property you can call [self setNeedsLayout] which will ensure the layout will be updated, but will prevent many (potentially expensive) updates to the layout if multiple properties are changed at once or even if a single property were modified multiple times within one iteration of the run loop. Other expensive operations like the setNeedsDisplay and drawRect: pair of methods follow the same pattern.
What's the best way to implement pattern like this? Specifically I'd like to tie a number of dependent properties to an expensive method that needs to be called once per iteration of the run loop if a property has changed.
Possible Solutions:
Using a CADisplayLink or NSTimer you could get something working like this, but both seem more involved than necessary and I'm not sure what the performance implications of adding this to lots of objects (especially timers) would be. After all, performance is the only reason to do something like this.
I've used something like this in some cases:
- (void)debounceSelector:(SEL)sel withDelay:(CGFloat)delay {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:sel object:nil];
[self performSelector:sel withObject:nil afterDelay:delay];
}
This works great in situations where a user input should only trigger some event when a continuous action, or things like that. It seems clunky when we want to ensure there is no delay in triggering the event, instead we just want to coalesce calls within the same run loop.
NSNotificationQueue has just the thing you're looking for. See the documentation on Coalescing Notifications
Here a simple example in a UIViewController:
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(configureView:)
name:#"CoalescingNotificationName"
object:self];
[self setNeedsReload:#"viewDidLoad1"];
[self setNeedsReload:#"viewDidLoad2"];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setNeedsReload:#"viewWillAppear1"];
[self setNeedsReload:#"viewWillAppear2"];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self setNeedsReload:#"viewDidAppear1"];
[self setNeedsReload:#"viewDidAppear2"];
}
- (void)setNeedsReload:(NSString *)context
{
NSNotification *notification = [NSNotification notificationWithName:#"CoalescingNotificationName"
object:self
userInfo:#{#"context":context}];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification
postingStyle:NSPostASAP
coalesceMask:NSNotificationCoalescingOnName|NSNotificationCoalescingOnSender
forModes:nil];
}
- (void)configureView:(NSNotification *)notification
{
NSString *text = [NSString stringWithFormat:#"configureView called: %#", notification.userInfo];
NSLog(#"%#", text);
self.detailDescriptionLabel.text = text;
}
You can checkout the docs and play with the postingStyle to get the behavior you desired. Using NSPostASAP, in this example, will give us output:
configureView called: {
context = viewDidLoad1;
}
configureView called: {
context = viewDidAppear1;
}
meaning that back-to-back calls to setNeedsReload have been coalesced.
I've implemented something like this using custom dispatch sources. Basically, you setup a dispatch source using DISPATCH_SOURCE_TYPE_DATA_OR as such:
dispatch_source_t source = dispatch_source_create( DISPATCH_SOURCE_TYPE_DATA_OR, 0, 0, dispatch_get_main_queue() );
dispatch_source_set_event_handler( source, ^{
// UI update logic goes here
});
dispatch_resume( source );
After that, every time you want to notify that it's time to update, you call:
dispatch_source_merge_data( __source, 1 );
The event handler block is non-reentrant, so updates that occur while the event handler is running will coalesce.
This is a pattern I use a fair bit in my framework, Conche (https://github.com/djs-code/Conche). If you're looking for other examples, poke around CNCHStateMachine.m and CNCHObjectFeed.m.
This borders on "primarily opinion based", but I'll throw out my usual method of handling this:
Set a flag and then queue processing with performSelector.
In your #interface put:
#property (nonatomic, readonly) BOOL needsUpdate;
And then in your #implementation put:
-(void)setNeedsUpdate {
if(!_needsUpdate) {
_needsUpdate = true;
[self performSelector:#selector(_performUpdate) withObject:nil afterDelay:0.0];
}
}
-(void)_performUpdate {
if(_needsUpdate) {
_needsUpdate = false;
[self performUpdate];
}
}
-(void)performUpdate {
}
The double check of _needsUpdate is a little redundant, but cheap. The truly paranoid would wrap all the relevant pieces in #synchronized, but that's really only necessary if setNeedsUpdate can be invoked from threads other than the main thread. If you're going to do that you also need to make changes to setNeedsUpdate to get to the main thread before calling performSelector.
It's my understanding that calling performSelector:withObject:afterDelay: using a delay value of 0 causes the method to be called on the next pass through the event loop.
If you want your actions to be queued up until the next pass through the event loop, that should work fine.
If you want to coalesce multiple different actions and only want one "do everything that accumulated since the last pass through the event loop" call, you could add single call to performSelector:withObject:afterDelay: in your app delegate (or some other single instance object) at launch, and invoke your method again at the end of each call. You could then add an NSMutableSet of things to do, and add an entry to the set each time you trigger an action that you want to coalesce. If you created a custom action object and overrode the isEqual (and hash) methods on your action object, you could set it up so there would only ever be a single action object of each type in your set of actions. Adding the same action type multiple times in a pass through the event loop would add one and only one action of that type).
Your method might look something like this:
- (void) doCoalescedActions;
{
for (CustomActionObject *aCustomAction in setOfActions)
{
//Do whatever it takes to handle coalesced actions
}
[setOfActions removeAllObjects];
[self performSelector: #selector(doCoalescedActions)
withObject: nil
afterDelay: 0];
}
It's hard to get into details on how to do this without specific details of what you want to do.
I'm currently developing an iPad book-style app, where I'm using a view controller to manage the main window, and then I use a number of page controllers equal to the number of pages in the book. All the page controllers inherit from a base class, PageController, where I defined the main methods used in every page, as well as the variables.
So, my view controller tracks the current page using an object of PageController type, and when I want to load another page, the view controller calls a method (transitionToNextPage), and this method returns the next page controller.
For example, if the current page is number 2, its class is Page02Controller, and the next page class is Page03Controller, which is returned from the Page02Controller.
The issue I've been fighting with, and to which I'm asking for some advice, is when I need to call the transitionToNextPage method, and the method returns when it's still doing some actions, like animating the transition (loading some frames, for example). For example, in the code below, I call the method transitionToNextPage and I start a timer to load some frames. However, the function returns right after the timer starts, and it counts for about 1 second.
- (PageController *)transitionToNextPage{
if ([self.timerAnimationAngel isValid]) {
[self.timerAnimationAngel invalidate];
self.timerAnimationAngel = nil;
}
if ([self.timerAnimationFeather isValid]) {
[self.timerAnimationFeather invalidate];
self.timerAnimationFeather = nil;
}
[super hideTransitionButtons];
[NSTimer scheduledTimerWithTimeInterval:1.f/(float)self.filenamesImagesTransition.count target:self selector:#selector(updateTransitionViews:) userInfo:nil repeats:YES];
self.imageViewTransition = [[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:[NSString stringWithMainBundle:self.filenamesImagesTransition[0]]]];
self.imageViewTransition.layer.zPosition = kZPositionTransition;
[self.mainView addSubview:self.imageViewTransition];
return [[Page02Controller alloc] initPageNumber:2 withView:self.mainView withViewController:self.mainController];
}
So far, I have all the code working as I intend, but I don't think I'm doing in the best way. What I'm doing is calling a method from the super class of page controller when the timer ends, as shown in the code below:
- (void)updateTransitionViews:(NSTimer *)timer{
static int indexImageTransition = 0;
if (indexImageTransition >= self.filenamesImagesTransition.count) {
[super clearAllViewsIncludeBackground:YES];
[timer invalidate];
timer = nil;
[super loadNextPage];
}
else{
self.imageViewTransition.image = [UIImage imageWithContentsOfFile:[NSString stringWithMainBundle:self.filenamesImagesTransition[indexImageTransition]]];
indexImageTransition++;
}
}
And the [super loadNextPage] calls a method in the view controller, and is defined in PageController (the super class) as:
- (void)loadNextPage{
SEL selector = NSSelectorFromString(#"loadNextPage");
if([self.mainController respondsToSelector:selector]){
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.mainController performSelector:selector];
#pragma clang diagnostic warning "-Warc-performSelector-leaks"
}
else{
NSLog(#"Selector not found.");
}
}
The method transitionToNextPage is called from the view controller the following way:
- (IBAction)buttonNextPage:(id)sender {
NSLog(#"Next page button pressed.");
self.currentPage = [self.currentPage transitionToNextPage];
self.pageNumber = self.currentPage.pageNumber;
}
And finally, the method loadNextPage called from the PageController is defined in the view controller as:
- (void)loadNextPage{
[self.currentPage loadPage];
}
The loadPage is now called from the next page, because it was already set before in self.currentPage = [self.currentPage transitionToNextPage];.
Here I lose the reference to the previous page, but it's not yet dealloc'ed because it still has a timer running, so I'm not having any issues doing this.
In the long term, and for future reusability, maybe the code is a bit confusing, but this way I can call the methods in the correct order, only when the timer finishes.
Another way that I was doing before was using notifications. I was listening to a notification called "#loadNextPage" which was posted by the current page controller when the timer was done. That way, I wasn't calling 3 or 4 additional methods in order to load the next page, but was only calling one.
I think the main advantage I have when using notifications is that the code is much simpler, and I just need to post/listen to a notification and call a single method.
I already thought about using a delegate, but I don't think it can be applied here the way I'm doing the transitions between pages.
I constantly run tests and measure the performance about memory and processor usage, and so far it's doing great. I don't have any memory leaks, and the app runs smoothly both on an iPad 2 with iOS 8 (non-retina) and iPad 4 with iOS 7 (retina).
EDIT:
I'm going to call the next page controller from inside the animation block or timer, without passing any information to the view controller.
I was assuming that it would retain the pages and cause leaks or have bad memory management performance, but that's not true. :)
Timers and similar hacks are simply wrong here. So is blocking or polling as a way of waiting. This is the standard asynchronous pattern. Look at any of the many Cocoa asynchronous methods to see how to deal with it, such as presentViewController:animated:completion:. Instead of writing a method transitionToNextPage, you write a method transitionToNextPageWithCompletion:. It takes a block parameter. When everything is over, the method calls the block, thus calling back into your code.
TestObj class is a simple class that has a method doSomethingInBackground in which I send it performSelectorInBackground method to make itself sleep 5 seconds in background thread.
#implementation TestObj
- (void)dealloc
{
NSLog(#"%#, is main thread %u", NSStringFromSelector(_cmd), [NSThread isMainThread]) ;
}
- (void)doSomethingInBackground
{
[self performSelectorInBackground:#selector(backgroundWork) withObject:nil] ;
}
- (void)backgroundWork
{
sleep(5) ;
}
#end
I alloc and init the instance and send it doSomethingInBackground message and assign nil to it in order to release it as soon as possible.
TestObj *obj = [[TestObj alloc] init] ;
[obj doSomethingInBackground] ;
obj = nil ;
I find that the dealloc will run after about 5 seconds obj = nil;, it seems that system retains self when send it the method [self performSelectorInBackground:#selector(backgroundWork) withObject:nil] ; and after backgroundWork returned, the instance will be deallocated.
Can anyone tell me the works that system do behind this. Thanks.
-[NSObject performSelectorInBackground:withObject:] under the hood calls -[NSThread initWithTarget:selector:object:] which does retain the original receiver (here passed as target parameter)
NSThread documentation: "The objects target and argument are retained during the execution of the detached thread. They are released when the thread finally exits."
From the Docs,
The performSelectorInBackground:withObject: method creates a new detached thread and uses the specified method as the entry point for the new thread. For example, if you have some object (represented by the variable myObj) and that object has a method called doSomething that you want to run in a background thread, you could use the following code to do that:
[myObj performSelectorInBackground:#selector(doSomething) withObject:nil];
The effect of calling this method is the same as if you called the detachNewThreadSelector:toTarget:withObject: method of NSThread with the current object, selector, and parameter object as parameters. The new thread is spawned immediately using the default configuration and begins running.
In the detachNewThreadSelector:toTarget:withObject: documentation,
The objects aTarget and anArgument are retained during the execution
of the detached thread, then released. The detached thread is exited
(using the exit class method) as soon as aTarget has completed
executing the aSelector method.
Regarding the performSelector:AfterDelay: ,
This method sets up a timer to perform the aSelector message on the
current thread’s run loop. The timer is configured to run in the
default mode (NSDefaultRunLoopMode). When the timer fires, the thread
attempts to dequeue the message from the run loop and perform the
selector. It succeeds if the run loop is running and in the default
mode; otherwise, the timer waits until the run loop is in the default
mode
And
The timer maintains a strong reference to this object until it (the
timer) is invalidated.
If you do not want your Obj to be retained, you can use a weak referenced object,
TestObj *obj = [[TestObj alloc] init] ;
__weak typeof(obj) weakObj = obj;
[weakObj doSomethingInBackground] ;
obj = nil ;
While special cases like performSelector: have been discussed in other answers I think it's helpful to add the answer to the general case:
Will self be retained when send message to it
No. In both manual retain count and ARC self is never implicitly retained. You must take care that the receiver of a message is not deallocated during method execution.
While under ARC self's type is strong the object is not actually retained. See the ARC documentation.
Here is my need:
I'm making an ios app that controls a device. It has an API that lets me do things like:
turnOnLights()
turnOffLights()
rotate(degrees)
move(speed)
etc... (the api is completely objective c, im just giving an example in c synthax)
From this API, I need to build high level sequences, for example:
turn on all lights
wait 1 second
turn off all lights
wait 1 second
Or
move
rotate 30 degrees
wait 1 second
move
rotate -30 degrees
I can think of hacky ways to do these with timers, but I am wondering if ObjectiveC has a nice way that I could build some high level methods so I could for example:
ReturnValue flashLights()
ReturnValue moveAndRotate()
The idea behind this would be that, the commands needed to do the flashing action would be sent repeatedly forever, and, I can do:
stopAction(returnValue)
To stop it. (I know I'm writing in C synthax but I find it clearer to explain things).
So essentially, is there a convenient way to make a script-like thing where I can call a method that starts an action. The action makes method calls, waits some time, does more method calls, and repeats this forever until the action is stopped.
Thanks
I am not sure if I understand your question properly, but if you want to repeatedly call a set of methods with delays in between, you can use aperformSelector:withObject:afterDelay, or dispatch_after to build a loop. (And there are many ways to leave the loop)
[self performSelector:#selector(resetIsBad) withObject:nil afterDelay:0.1];
or
int delayInSecond = 10;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delayInSecond * NSEC_PER_SEC),
dispatch_get_main_queue(), ^{
//doing something
});
performSelector:withObject:afterDelay invokes a method of the receiver on the current thread using the default mode after a delay.
This method sets up a timer to perform the aSelector message on the
current thread’s run loop. The timer is configured to run in the
default mode (NSDefaultRunLoopMode). When the timer fires, the thread
attempts to dequeue the message from the run loop and perform the
selector. It succeeds if the run loop is running and in the default
mode; otherwise, the timer waits until the run loop is in the default
mode.
dispatch_after add your block to a queue and if the queue is empty, it runs immediately once being added to the queue. Else it will have to wait for other tasks in the queue to finish before it can run.
More on dispatch_after:
dispatch_after
Enqueue a block for execution at the specified time.
void dispatch_after( dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
Parameters:
when The temporal
milestone returned by dispatch_time or dispatch_walltime.
queue The
queue on which to submit the block. The queue is retained by the
system until the block has run to completion. This parameter cannot be
NULL.
block The block to submit. This function performs a Block_copy
and Block_release on behalf of the caller. This parameter cannot be
NULL.
Discussion
This function waits until the specified time and then
asynchronously adds block to the specified queue.
Passing DISPATCH_TIME_NOW as the when parameter is supported, but is
not as optimal as calling dispatch_async instead. Passing
DISPATCH_TIME_FOREVER is undefined.
Declared In dispatch/queue.h
Personally I don't think using an NSTimer would be 'hacky' as long as you implement it properly. You do need to make sure you invalidate the timer once you're finished with it though; check out this thread for more information about NSTimer best practices.
// in YourViewController.h
#property (nonatomic) BOOL flag;
#property (nonatomic, strong) NSTimer* timer;
// in YourViewController.m
-(void)viewDidLoad
{
[super viewDidLoad];
self.flag = YES;
[self flashLights];
// other code here
}
-(void)flashLights
{
CGFloat interval = 1.0f; // measured in seconds
self.timer = [NSTimer scheduledTimerWithTimeInterval:interval
target:self
selector:#selector(timerEventHandler)
userInfo:nil
repeats:YES];
}
-(void)timerEventHandler
{
// your API calls might take a while to execute, so consider running them asynchronously:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
if (self.flag) turnOnLights();
else turnOffLights();
self.flag = !self.flag;
});
}
-(void)stopAction
{
[self.timer invalidate];
}