How to trampoline from a Cocoa Notification to the invocation of c function pointer? - objective-c

I want to set up something like this in my environment.
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self
forKeyPath:[#"values." stringByAppendingString: #"MyPreference"]
options:NSKeyValueObservingOptionNew
context:NULL];
I'm doing this from a Smalltalk environment. The particular Smalltalk can actually drive the above thru the objective-c runtime features. The problem is the "self" there as the addObserver: argument. From Smalltalk, I can create an C function pointer that will act as a callback into Smalltalk. But it can't offer it's notion of what an Object is to the ObjectiveC environment.
So I'm trying to figure out how to trampoline from some sort of object with the appropriate API that causes the function pointer I create to be executed. I've looked at NSInvokation and friends, but none of those are used to wrap around a C function pointer. And I'm not sure they'd translate the addObserver:forKeyPath:options:context: to do the right thing.
Boiled down the question is, how do you use existing Cocoa objects to register a response to a Notification that is the execution of a C function pointer without compiling any new objc code.

You'll need to create a glue class in Objective-C. You could do this:
typedef void TravisGriggsCallback();
#interface TravisGriggsGlue {
TravisGriggsCallback *_callback;
}
- (id)initWithCallback:(TravisGriggsCallback *)callback;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
#end
#implementation TravisGriggsGlue
- (id)initWithCallback:(TravisGriggsCallback *)callback
{
if (!(self = [super init]))
return nil;
_callback = callback;
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
_callback();
}
#end
If you need to pass arguments to the callback, you'll need to add instance variables to hold the arguments, and pass them to the init method.
Then you use it like this:
TravisGriggsGlue *glue = [[TravisGriggsGlue alloc]
initWithCallback:&someCallbackFunction];
[[NSUserDefaultsController sharedUserDefaultsController]
addObserver:self
forKeyPath:[#"values." stringByAppendingString: #"MyPreference"]
options:NSKeyValueObservingOptionNew
context:NULL];
[glue release]; // only needed if not using ARC

I don't know which kind of Smalltalk you're using, but in pharo/squeak you are probably using the ObjectiveCBridge plugin. If that is the case, you can extend an ObjectiveCSqueakProxy and use it as an object delegated from cocoa to your application.
hope this works for you :)

use a simple function object:
#interface MONFunctor : NSObject
- (void)performFunction;
#end
Then just specialize as needed:
#interface MONFunctorSpecialization : NSObject
{
#private
// data, state, arguments for function, function pointer...
}
- (void)performFunction;
#end
You could use the functor as a listener, but I sketched this out as though your listener would just say [functor performFunction]; in the callback.
In retrospect, there's no need for MONFunctor to be a class; a protocol would suffice.

Related

Key-Value Observer not changing on its own property

I am trying to get notifications for when a property called "currentTopViewPosition" changes. I used the following code to register for the changes and receive them:
[self addObserver:self
forKeyPath:#"currentTopViewPosition"
options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld|NSKeyValueObservingOptionPrior
context:NULL];
Then the receiving side:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(#"Key Path: %#\n change: %#",keyPath, change);
}
But nothing was getting logged for it.
I tested to make sure the value was actually changing by using an NSTimer to print out its value every 5ms and it was changing.
I've never seemed to get Key-value observing to work, so am I doing something wrong? missing a step?
Thanks!
The easiest way to make your property is to redeclare the property as readwrite inside your implementation file.
#property (nonatomic, readwrite, assign) ECSlidingViewControllerTopViewPosition currentTopViewPosition;
Then when setting the value, make sure you use the property setter. E.g. self.currentTopViewPosition = 1
If you are manually setting the value using an ivar directly, you will have to generate the KVO calls manually. Like this:
[self willChangeValueForKey:#"currentTopViewPosition"];
_currentTopViewPosition = 1;
[self didChangeValueForKey:#"currentTopViewPosition"];

What is best practice to interaction with object in objective-c?

My questions is next:
For example I have object A (this is data model object). Assume that object A have some property (for example request property). Also I have object B (this is my view object).
So my problem is next: when my data model will be changed (the value for request property changed) I want to know about this events in my view (object B)
How to create this interaction between object.
For example in request is written to "some_value" and after this object B immediately know about it.
Thanks for response!
You can use delegation pattern, NSNotifications, callback blocks and even KVO. Choice depends on situation, in your case delegate or callback block would work.
I would use Key Value Observing. Your view controller (not the view itself) would set itself up as an observer for the data model object and when it gets observer notifications, it would update the view.
[myDataObject addObserver: myViewController
forKeyPath: #"request"
options: NSKeyValueObservingOptionNew
context: nil];
// in the view controller you need
-(void) observeValueForKeyPath: (NSString*) path
ofObject: (id) aDataObject
change: (NSDictionary*) changeDictionary
context: (void*) context]
{
if (aDataObject == myDataObject
&& [path isEqualToString: #"request"])
{
// change you are interested in
}
// Call suoer implementation of this method if it implements it
}
Don't forget to remove the observer when you are done with it.
Also, be careful in a threaded environment. Observations are notified on the same thread that the change happens on. If this is not the main thread, you'll need to use -performSelectorOnMainThread:withObject:waitUntilDone: to make any changes to the UI.
If you just want object B to know whats up I would suggest using delegation.
If maybe later you want object C, D and E to know too what happend in object A i would suggest using NSNotification.
For example I have class DataModel. In this step I add observer for my property str. For object I will send my view controller.
.h
#import <Foundation/Foundation.h>
#interface DataModel : NSObject
#property (strong, nonatomic) NSString *str;
- (void)setUpObserver:(id)object;
#end
.m
#import "DataModel.h"
#implementation DataModel
#synthesize str;
- (void)setUpObserver:(id)object
{
[self addObserver:object forKeyPath: #"str" options: NSKeyValueObservingOptionNew context: nil];
}
#end
In my view controller
#import "DataModel.h"
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
dm = [[DataModel alloc] init];
[dm setUpObserver:self];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if (object == dm && [keyPath isEqualToString: #"str"])
{
NSLog(#"it's work");
}
}
- (IBAction)changeValue:(id)sender {
dm.str = #"test change value";
}
#end
This is my realization of KVO. Thanks JeremyP for explanation.

KVO With NSMutableArray

I have a NSMutableArray property in my AppDelegate called blocks.
I would like to observe whenever an object is added to this array.
I've read other posts, but I can't understand why this isn't working.
In my app delegate class, I implement
- (void)insertObject:(id)obj inBlocksAtIndex:(NSInteger)index
{
[blocks insertObject:obj atIndex:index];
}
In my view controller's init method, I add an observer to my AppDelegate reference.
boardModel = [[UIApplication sharedApplication] delegate];
[boardModel addObserver:self forKeyPath:#"blocks" options:0 context:NULL];
In my view controller's viewDidLoad method, I try invoking the KVO Indexed array accessor I implemented previously,
[boardModel insertObject:[[Block alloc] init] inBlocksAtIndex:0];
Then I implement my observeValueForKeyPath method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"blocks"])
{
NSLog(#"ADDED");
}
}
I've tried adding an NSLog statement before the if statement in observeValueForKeyPath, and it seems as if it's never being called.
I've also tried NSLogging [[boardModel blocks] count], and it says the count is 1 (the object is being added).
I must be missing something.
The catch is that NSArrays don't respect KVO, so observing the key path count won't work.
If this is MacOSX, use NSArrayController. otherwise implement a wrapper class for the array that triggers the KVO calls when adding/removing contents of the array, and passes across all other calls.
You're observing the blocks property of the app delegate, not the blocks array itself. Hopefully the following example will make the difference clear:
// This will fire KVO as you're changing the app delegate's `blocks` property.
appDelegate.blocks = [NSMutableArray array];
// This will not fire KVO as the app delegate's `blocks` property still points
// to the same object; from the app delegate's perspective, nothing's happened.
[appDelegate.blocks addObject:#"Object"];
If you want to be notified when the contents of the blocks array changes, observe a property on the array itself—something like count. Updating your code:
[boardModel.blocks addObserver:self forKeyPath:#"count" options:0 context:NULL];
Did you try
- (void)insertObject:(id)obj inBlocksAtIndex:(NSInteger)index
{
[[self mutableArrayValueForKey:#"blocks"] insertObject:obj atIndex:index];
}
I just open-sourced a very small Objective-C library that adds a delegate to NSMutableArray. It might help you achieve what you were trying to do. Check out FCMutableArray on GitHub
It's not that NSMutableArray is not KVO-compliant in some respects, it's that all KVO-compliant properties must be NSObjects. "count" is an NSUInteger; you can't observe that property directly. If it was an NSNumber object, you could.
From the NSArray header file:
#interface NSArray<__covariant ObjectType> : NSObject <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>
#property (readonly) NSUInteger count;
Why was the answer accepted, by the way? It's clear the answerer did not test his answer.

Responding to setters

What is the best way to respond to data changes when property setters are called. For example, if I have a property called data, how can I react when [object setData:newData] is called and still use the synthesised setter. Instinctively, I would override the synthesised setter like so:
- (void)setData:(DataObject *)newData {
// defer to synthesised setter
[super setData:newData];
// react to new data
...
}
...but of course this doesn't make sense - I can't use super like this. So what is the best way to handle this situation? Should I be using KVO? Or something else?
There are a few different ways to do this, depending on how much control you want. One way to do it is to observe your own property:
[self addObserver:self forKeyPath:#"data" options:0 context:nil];
- (void)observeValueForKeyPath:(NSString *)path ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if(object == self && [path isEqualToString:#"data"]) {
//handle change here
} else [super observeValueForKeyPath:path ofObject:object change:change context:context];
}
Make sure you remove yourself as an observer in your dealloc or finalize method, if not before.
Another way would be to override -didChangeValueForKey:. However, this method may not be called if there are no observers on the object.
- (void)didChangeValueForKey:(NSString *)key {
[super didChangeValueForKey:key];
if([key isEqualToString:#"data"]) {
//handle change here
}
}
#synthesize creates default accessors for easy use. In case some special action is needed then it is always possible to write own accessors instead of using #synthesize. The setter and getter are not inherited from base class, they are created by the #synthesize directive. So you don't need to (neither you can) call super setData: (unless you really have created super class that support that).
Just ensure that you are managing memory correctly. Memory Management Programming Guide contains examples on how to manage memory for different types of memory policy (retain or assign or copy).
From this SO answer.
You can define a synthesized "private" property, (put this in your .m file)
#interface ClassName ()
// Declared properties in order to use compiler-generated getters and setters
#property (nonatomic, strong <or whatever>) NSObject *privateSomeObject;
#end
and then manually define a getter and setter in the "public" part of ClassName (.h and #implementation part) like this,
- (void) setSomeObject:(NSObject *)someObject {
self.privateSomeObject = someObject;
// ... Additional custom code ...
}
- (NSArray *) someObject {
return self.privateSomeObject;
}
You can now access the someObject "property" as usual, e.g. object.someObject. You also get the advantage of automatically generated retain/release/copy, compatibility with ARC and almost lose no thread-safety.

Help with Key-Value-Observing

I need a bit of help with KVO, I'm about half way there. What I'm trying to do is trigger a method when something in an Tree Controller changes.
So I 'm using this code to register as a KVO.
[theObject addObserver: self
forKeyPath: #"myKeyPath"
options: NSKeyValueObservingOptionNew
context: NULL];
But how do i trigger a method when the Key Path I am observing changes?
One extra question, when I add my self as an Observer I want the Key Path to be a Property in my Core Data model, Have I done that correctly?
Override observeValueForKeyPath:ofObject:change:context: to dispatch the method you wish to call.
#interface Foo : NSObject {
NSDictionary *dispatch;
...
}
#end
#implementation Foo
-(id)init {
if (self = [super init]) {
dispatch = [[NSDictionary dictionaryWithObjectsAndKeys:NSStringFromSelector(#selector(somethingHappenedTo:with:)),#"myKeyPath",...,nil] retain];
...
}
}
...
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
SEL msg = NSSelectorFromString([dispatch objectForKey:keyPath]);
if (msg) {
[self performSelector:msg withObject:object withObject:keyPath];
}
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
...
See "Receiving Notification of a Change" for details.
I would recommend you take a look at the Google Toolbox For Mac's GTMNSObject+KeyValueObserving.h category or at least the blog post by Michael Ash that inspired it. Basically, doing manual KVO right is very subtle and the pattern suggested by the API is not ideal. It's much better to put an other layer on the API (as GTMNSObject+KeyValueObserving) does that makes things more like the NSNotification API and hides some of the sources of subtle bugs.
Using GTMNSObject+KeyValueObserving, you would do
[theObject gtm_addObserver:self
forKeyPath:#"myKeyPath"
selector:#selector(myCallbackSelector:)
userInfo:nil
options:NSKeyValueObservingOptionNew];
and your -myCallbackSelector: will get called when the value at #"myKeyPath" changes with an argument of type GTMKeyValueChangeNotification, which encapsulates all the relevant information you might need.
This way, you don't have to have a big dispatch table in observeValueForKeyPath:ofObject:change:context (in reality one is maintained for you by the category) or have to worry about the correct way to use the context pointer to avoid conflict with super/sub classes etc.
You should implement this and it will be invoked when the keypath changes:
(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context;
More info here.
( this is a technique I learned here: http://www.bit-101.com/blog/?p=1999 )
You could pass the method in the 'context', like
[theObject addObserver:self
forKeyPath:#"myKeyPath"
options:NSKeyValueObservingOptionNew
context:#selector(doSomething)];
..then in the observeValueForKeyPath method, you cast it to the SEL selector type, and then perform it.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
SEL selector = (SEL)context;
[self performSelector:selector];
}
If you want to pass data to the doSomething method you could use the 'new' key in the 'change' dictionary, like this:
[theObject addObserver:self
forKeyPath:#"myKeyPath"
options:NSKeyValueObservingOptionNew
context:#selector(doSomething:)]; // note the colon
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
SEL selector = (SEL)context;
// send the new value of observed keyPath to the method
[self performSelector:selector withObject:[change valueForKey:#"new"]];
}
-(void)doSomething:(NSString *)newString // assuming it's a string
{
label.text = newString;
}