discuss-gnustep
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: @dynamic property synthesis


From: Ivan Vučica
Subject: Re: @dynamic property synthesis
Date: Mon, 6 Aug 2012 10:51:19 +0200

Hi David!

On 5. 8. 2012., at 23:08, David Chisnall <theraven@sucs.org> wrote:

Hi Ivan,

You seem to be very, very confused and none of the documentation that you cite says what you seem to think it says.

@dynamic does not mean run-time synthesized, it means the exact opposite, as I said.  @dynamic is just a hint to the compiler that you are providing the implementation of the property (possibly in a superclass, possibly in a category) and that it should not complain if it can't see one, nor should it try to synthesise one if you are in a synthesis-by-default mode (recent versions of clang default to synthesising properties if they don't see implementations or @dynamic).

And, as I suspected, you are confusing properties with KVC (Key-Value Coding), or, perhaps more relevantly, with KVO (Key-Value Observing).  CoreAnimation makes heavy use of both KVC and KVO for animatable properties.  Note that the CoreAnimation documentation occasionally uses the term 'properties' in the traditional way, rather than referring to declared properties.

For a property to be used for animation, it must be KVC-compliant, so that CA can set it, and KVO compliant, so that CA can receive notifications when it changes.  Again, this does not require any method synthesis at run time.  

If you read the two links you posted carefully, then you will see that they say nothing like what you seem to believe that they say.  They explicitly deal with the case where methods do not exist (@dynamic is used to ensure that it does not), and so the first path in Core Animation fails.  Trying to animate a property with no corresponding setters and getters, CoreAnimation will enter into one of the fall-back mechanisms, either one provided by KVC/KVO or its own, which is very similar but a bit less general (and therefore, in theory at least, faster).  

As I said before, you do not need to add methods at run time.  You simply need to implement the case when you try to set a property that does not have a corresponding method.  The Omni link explicitly tells you that this stuff is all done using KVC...

The examples use declared properties for simplicity, because they are using it as a way of providing type information to KVC.  I don't think our KVC implementation actually makes use of property metadata in this way, so that's something that you could look at fixing, although I think property introspection only works with clang and the GNUstep runtime...

David


Thanks for all the hints, however, I'm now even more confused. 

Some phrases such as this excerpt from Apple documentation (emphasis added) indicate that accessor methods are indeed synthesized at runtime:
Will now only synthesize property accessor methods for properties marked @dynamic in their class implementation. For backwards compatibility we retain the previous behavior for executables linked on OS versions prior to 10.6.

I'd follow your advice and implement these properties via KVC/KVO, storing these additional properties in a dictionary, however that doesn't seem to be enough. Here's some quickly whipped-together test code.

#import "ISAppDelegate.h"

@interface LayerSubclass : CALayer
@property (nonatomicretainNSString * prop;
@end

@implementation LayerSubclass
@dynamic prop;
@end

@interface ObjectSubclass : NSObject
@property (nonatomicretainNSString * prop;
@end
@implementation ObjectSubclass
@dynamic prop;
- (id)valueForKey:(NSString*)key
{
    if([key isEqualToString:@"prop"])
        return @"ok";
    return [super valueForKey:key];
}
- (void)setValue:(NSString*)value forKey:(NSString*)key
{
    if([key isEqualToString:@"prop"])
    {
        return;
    }
    
    return [super setValue:value forKey:key];
}
@end

void printMethods(id obj)
{
    unsigned int count=0;
    Method * methodList = class_copyMethodList([obj class], &count);
    NSLog(@"%d methods:", count);
    for(int i = 0; i < count; i++)
    {
        NSLog(@" - %@"NSStringFromSelector(method_getName(methodList[i])));
    }
    NSLog(@"------");
    free(methodList);
}

@implementation ISAppDelegate

- (void)dealloc
{
    [super dealloc];
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    LayerSubclass * b = [LayerSubclass new];
    printMethods(b);
    b.prop = @"5";
    [b setProp:@"5"];
    printMethods(b);
    
    ObjectSubclass * s = [ObjectSubclass new];
    s.prop = @"5";
}


@end

Here's the output:

2012-08-06 10:46:06.112 SubclassingCALayerTest[4490:303] 0 methods:
2012-08-06 10:46:06.114 SubclassingCALayerTest[4490:303] ------
2012-08-06 10:46:06.114 SubclassingCALayerTest[4490:303] 1 methods:
2012-08-06 10:46:06.115 SubclassingCALayerTest[4490:303]  - setProp:
2012-08-06 10:46:06.116 SubclassingCALayerTest[4490:303] ------
2012-08-06 10:46:06.117 SubclassingCALayerTest[4490:303] -[ObjectSubclass setProp:]: unrecognized selector sent to instance 0x101a27d80
2012-08-06 10:46:06.118 SubclassingCALayerTest[4490:303] -[ObjectSubclass setProp:]: unrecognized selector sent to instance 0x101a27d80

Obviously, CALayer has created accessor methods for the layer - even a direct message-send of setProp: works. In fact, the method appears to be created when the first attempt to access it is made.
On the other hand, no hackery seems to be happening in NSObject subclass when setting the value of "prop". "setValue:forKey:" is not called - instead, the expected behavior of attempting to use the accessor "setProp:" occurs, and fails.

Unless I'm misunderstanding again, I need to replicate the behavior of CALayer by adding accessor methods at runtime. I can do that in two ways: adding methods to the class at runtime, or intercepting an attempt to call an unknown method (via -forwardInvocation:). If I'm adding a method, it needs to be there; whether it's created in +initialize or in -forwardInvocation: doesn't really matter. No matter how I'm using forwarding mechanisms, I need to detect that it's an attempt to call an accessor.

If you can point me at mistakes I made in the test code, or my understanding of the underlying mechanisms, I'd appreciate that. :-)

--
Ivan Vučica


reply via email to

[Prev in Thread] Current Thread [Next in Thread]