A pretty simple question, but I can't seem to find an answer. (Developing an iOS 5+ app).
In my AppDelegate, I have a property, let's call it #property (non atomic) BOOL aFlag;. I'd like my AppDelegate to be notified if the value changes. Here is what I tried (everything happens in the AppDelegate.m), which is the same as when I "link" two different objects with an observer :
-(BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
// Some stuff
_aFlag = YES;
[self addObserver:self
forKeyPath:#"aFlag"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
context:nil];
// Some other stuff
}
-(void)observeValueForKeyPath:(NSString*)keyPath
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context {
if ([keyPath isEqual:#"aFlag"]) {
// Do something
} else {
// Do nothing
}
}
But observeValueForKeyPath: is not called.
Where am I wrong ? Thanks.
You should implement your own setter. In that setter you know the poperty has changed.
It's more optimised that way. Much better and less expensive then doing KVO on yourself.
Your solution works, technically you can KVO yourself.
But, imagine if you started using NSNotificationCenter for triggering methods in you class from inside your class? Can it be done? Yes. Should it be? Probably not. You may have a scenario where it's okay but not in a clean object oriented solution. You should be messaging self for this.
Well it's the same here. Implement this:
- (void)setAFlag:(BOOL)flag;
For example:
- (void)setAFlag:(BOOL)flag{
BOOL valueChanged = NO;
if(_aFlag != flag){
valueChanged = YES;
}
_aFlag = flag;
if(valueChanged)
[self doSomethingWithTheNewValueOfFlag];
}
Related
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 have a method called "pauseGame:" that pauses the game and a method called "resumeGame:"
I was wondering how I would call these in my app delegate I tried a few ways but in my debugger it ended up saying "pauseGame: method not found" and i imported my class i declared them in into my app delegate I was trying to figure out how I could solve this.
Here is the code I used for it:
- (void)applicationWillResignActive:(UIApplication *)application
{
[BPGameController pauseGame:];
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[BPGameController pauseGame:];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[BPGameController resumeGame:];
}
Try this. and make sure the method resumeGame is working fine.
- (void)applicationDidBecomeActive:(UIApplication *)application {
[super applicationDidBecomeActive:application];
[BPGameController resumeGame:];
}
if resumeGame doesnt take any param it should just be
[BPGameController resumeGame];
lose the ":"
Are your pause/resume methods class methods or instance methods, in your code example it looks like you're calling them as class methods. Can you post more concrete code?
Signatures would help.
If those methods are instance methods, what you can do is add a property to YourAppDelegate, where it might look like:
#property (nonatomic, strong) BPGameController *bpgc;
And then when you instantiate the BPGameController somewhere else, set it on the appdelegate by doing
[(*YourAppDelegate)[[UIApplication sharedApplication] delegate] setBpgc:yourBPGC];
and then in the app delegate methods, you can reference your bpgc by self.bpgc and send messages like pauseGame, etc.
First your code is syntatically incorrect.AS
[BPGameController pauseGame:]; //do not call pauseGame: wihout any parameter.
instead consider calling it with nil if you do not have any value
[BPGameController pauseGame:nil];
or
If you function do not need parameters than remove from method definition.
Secondly I suspect you do not have pauseGame: declared in .h file
declare it in .h file.If not
Third if you want to call pauseGame this way[BPGameController pauseGame:nil]; than your method definition should start with + instead of - something like this.
+(void)pauseGame:(id)param{
}
If you post the definition of pauseGame: it will help more
EDIT: USE
- (void)applicationWillResignActive:(UIApplication *)application{
UIViewController *controller = self.window.rootViewController;
if([controller isKindOfClass:[UINavigationController class]]){
BPGameController *bpGameController;
for (id vc in [((UINavigationController*)controller) viewControllers])
{
if ([vc isKindOfClass:[BPGameController class]])
{
[((BPGameController *) vc) pauseGame];
break;
}
}
}
else if([controller isKindOfClass:[BPGameController class]]){
[((BPGameController *) controller) pauseGame];
}else{
//Not found
}
}
I would like to know if someone else had some issue with Reactive Cocoa listenForNotification method.
I'm experiencing a retain cycle using the following code and my viewController dealloc method is never called.
If I don't listen for that notification my viewController is properly dealloc'ed
HERE IS THE UPDATE FULL BLOCK OF CODE, in which I call the method to listen for notifications:
RACSignal *postSignal = [RACSignal empty];
postSignal = [[[[NSNotificationCenter defaultCenter] rac_addObserverForName:kNotification object:self.conversation] takeUntil:self.rac_willDeallocSignal]] bind:^RACStreamBindBlock{
return ^RACSignal *(NSNotification *note, BOOL *stop) {
XXStatus status = [note.userInfo[#"status"] longValue];
if (status == XXStatusPosted) {
*stop = YES;
return [RACSignal empty];
} else {
return [RACSignal error:note.userInfo[#"error"]];
}
};
[[[[RACSignal merge:#[uploadSignal, postSignal]] deliverOnMain] subscribeCompleted:^{
[self doSomethingX];
} error:^(NSError *error) {
[self doSomethingOnFail:error];
}] autoDispose:self];
Do you know how RAC memory management works for listening notifications? Where notification center is the default notification center in the app. And the method is included in a category extension of NSObject class.
Thanks for your answer.
That isn't really a complete code snippet (it has unbalanced delimiters), so it's possible you've accidentally left something out that might have made it more clear. But it looks like the reason you have a retain cycle is in the very last line:
}] autoDispose:self];
That reference to self doesn't appear to be a weak reference, which means the bind block has an strong ("ownership") reference to self. If so, self will never be released, so the self.rac_willDeallocSignal never sends a value. And that means that the observer is never removed from the NSNotificationCenter.
I'm having problems to make the IOS (objective-c) KVO work for a key of type int.
My class declares a property sampleValue of type int. As int doesn't automatically implement the KVO functionality I've overrided the method automaticallyNotifiesObserversforKey as this:
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:#"sampleValue"]) {
automatic = NO;
} else {
automatic=[super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
The method is called just as I would expect is to be. I also have implemented a setter method for the sampleValue property like this:
- (void) setSampleValue:(int)newSampleValue
{
[self willChangeValueForKey:#"sampleValue"];
sampleValue = newSampleValue;
[self didChangeValueForKey:#"sampleValue"];
}
Setting up the observer in the observer class is done like this (dc is the instance of the observed object):
[dc addObserver:self forKeyPath:#"sampleValue" options:NSKeyValueObservingOptionNew context:NULL];
However, when the sampleValue is updated, no notification is sent to my observer object. Updating another property of type NSDate works absolutely fine.
Can anyone help me figure out what I'm doing wrong or what I should do to make this work.
Best regards
Tomo
Maybe I'm missing something in your question, but you can observe properties of type int just as easily as other types without doing anything special.
Try removing your +automaticallyNotifiesObserversForKey: override and your -setSampleValue: setter, and just synthesize the accessors for sampleValue:
#synthesize sampleValue;
int is the type of the value that corresponds to key #"sampleValue", but it's not the thing being observed. The object being observed is dc, and it'll take care of sending the proper notification when the sampleValue property is changed.
I want to change text in UISearchBar, but I don't want to call performSearch. I could cancelled the selector in two ways.
First way: I'm using method func1.
#property (nonatomic, retain) IBOutlet UISearchBar *searchBar;
-(void) func1
{
self.searchBar.delegate = self;
self.searchBar.text = #"";
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(performSearch:) object:nil];
}
- (void)searchBar:(UISearchBar *)searchBar_ textDidChange:(NSString *)searchText
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(performSearch:) object:nil];
[self performSelector:#selector(performSearch:) withObject:nil afterDelay:1];
}
Second way: I'm using func2
-(void) func2
{
self.searchBar.delegate = nil;
self.searchBar.text = #"";
self.searchBar.delegate = self;
}
Do I have a 100% guarantee that I cancelled the selector in the func1? Is func2 better?
UPDATE: I'm looking for universal solutions, not just self.searchBar.text = #""; or self.searchBar.text = #"Restaurants";.
First method is quite complicated, you're calling methods and try to cancel them. If the cancel doesn't arrive at the right time the method will be called. So don't do this it's a nightmare if the App grows and situations get more complex.
The second solution is somewhat more robust, you are removing the delegate. So it's guaranteed that the delegate won't get called, because it isn't there. But iOS is also not able to call the delegate for other things that you maybe want to handle. Besides that; You should test this on a device, because I think iOS will remove all results when there is no delegate that returns any results. But this will depend on how you've implemented other parts of the search.
My advice: Implement the searchDisplayController:shouldReloadTableForSearchString: method from the UISearchDisplayDelegate protocol. From this method you can control if the searchbar should reload the table with search results. Right before you alter the search string yourself set a flag, so this method nows when to return what.
Example pseudocode:
BOOL ignoreChanges = NO;
-(void)editSearchString
{
ignoreChanges = YES;
self.searchBar.text = #"some_string";
}
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
BOOL shouldReload = !ignoreChanges;
ignoreChanges = NO;
return shouldReload;
}
This way you ignore the change of the search string that is done by your code, but you will respect changes made by the user. It is of course possible to place the check on the ignoreChanges boolean in another function, like the searchBar:textDidChange: method if that suits your needs better then my suggestion!
Good luck!
Try to look up Subclassing NSOperation to be concurrent and cancellable
with BJ Homer answer that explain well how to solve it
hope it helps you!
To answer the question you phrased: yes. I've never had trouble with cancelPreviousPerformRequestsWithTarget: not doing what it's supposed to do.
But I'd lean towards the ignoreChanges approach rather than posting and cancelling messages.