KVG generic enough?

  • Okay, after reading some of the documentation on KVC coding, I understand (I think) that the point is to allow me to specify a property of an object with an NSString, then set/get that property value using KVC (i.e. valueForKey: or setValue:forKey:). But it seems like the fact that there's no way to specify (or grab) the value of the property in a generic-enough manner would be a problem - anyone using it would still have to know the data type of the property they want to access, and thus they might as well just use the accessors. I can see this would work if ObjC had a generic data type (like the 'variant' data type in VB/REALbasic), but AFAIK, ObjC doesn't have such, and valueForKey: returns an id, right?
      How can I determine what I get back? (i.e. what does the id pointer point to? an NSString? an NSNumber? NSDecimalNumber? NSData? another NSObject subclass?)
  • Oops, the subject should have read "KVC", not "KVG"! :) my bad.

    On Jul 18, 2012, at 8:08 AM, William Squires wrote:

    > Okay, after reading some of the documentation on KVC coding, I understand (I think) that the point is to allow me to specify a property of an object with an NSString, then set/get that property value using KVC (i.e. valueForKey: or setValue:forKey:). But it seems like the fact that there's no way to specify (or grab) the value of the property in a generic-enough manner would be a problem - anyone using it would still have to know the data type of the property they want to access, and thus they might as well just use the accessors. I can see this would work if ObjC had a generic data type (like the 'variant' data type in VB/REALbasic), but AFAIK, ObjC doesn't have such, and valueForKey: returns an id, right?
    > How can I determine what I get back? (i.e. what does the id pointer point to? an NSString? an NSNumber? NSDecimalNumber? NSData? another NSObject subclass?)
    >
  • Le 18 juil. 2012 à 15:08, William Squires <wsquires...> a écrit :

    > Okay, after reading some of the documentation on KVC coding, I understand (I think) that the point is to allow me to specify a property of an object with an NSString, then set/get that property value using KVC (i.e. valueForKey: or setValue:forKey:). But it seems like the fact that there's no way to specify (or grab) the value of the property in a generic-enough manner would be a problem - anyone using it would still have to know the data type of the property they want to access, and thus they might as well just use the accessors. I can see this would work if ObjC had a generic data type (like the 'variant' data type in VB/REALbasic), but AFAIK, ObjC doesn't have such, and valueForKey: returns an id, right?
    > How can I determine what I get back? (i.e. what does the id pointer point to? an NSString? an NSNumber? NSDecimalNumber? NSData? another NSObject subclass?)
    >
    >

    - [NSObject isKindOfClass:] ?

    -- Jean-Daniel
  • On 2012-07-18, at 9:08 AM, William Squires <wsquires...> wrote:

    > Okay, after reading some of the documentation on KVC coding, I understand (I think) that the point is to allow me to specify a property of an object with an NSString, then set/get that property value using KVC (i.e. valueForKey: or setValue:forKey:). But it seems like the fact that there's no way to specify (or grab) the value of the property in a generic-enough manner would be a problem - anyone using it would still have to know the data type of the property they want to access, and thus they might as well just use the accessors. I can see this would work if ObjC had a generic data type (like the 'variant' data type in VB/REALbasic), but AFAIK, ObjC doesn't have such, and valueForKey: returns an id, right?
    > How can I determine what I get back? (i.e. what does the id pointer point to? an NSString? an NSNumber? NSDecimalNumber? NSData? another NSObject subclass?)

    id *is* ObjC's “variant” type. You can determine the type of the underlying data by first determining its class (e.g.: [obj isKindOfClass:[NSString class]]), and then going from there. Scalar or composite values are wrapped in the appropriate classes—e.g.: NSNumber for numeric values and NSValue for generic data. The whole system is really quite flexible, particularly when you consider that ObjC, unlike so many other languages, doesn't choke on Nil values.

    —Mt.
  • On 18/07/2012, at 11:08 PM, William Squires wrote:

    > How can I determine what I get back? (i.e. what does the id pointer point to? an NSString? an NSNumber? NSDecimalNumber? NSData? another NSObject subclass?)

    Others have told you about -isKindOfClass, but I think you are incorrect in thinking you even need it.

    If you ask for a property by name, you already know its type. It's no different than using the accessor. If you don't know its name, you can't ask for its value, so the type is irrelevant.

    Where the anonymity is a benefit is when you need to drive a UI from a data model. Most UI controls take an "object" value, which is typed id. By using -valueForKey: and then -setObjectValue:, your code doesn't need to know the type - it just passes the value as a black box of some kind. This is how bindings works, which eliminates even that bit of glue.

    Properties are not conceptually any different from a dictionary. If you ask a dictionary to return an object for a given key, you know its type by contract, not by inspection.

    --Graham
  • On Jul 18, 2012, at 10:22 AM, Graham Cox wrote:

    >
    > On 18/07/2012, at 11:08 PM, William Squires wrote:
    >
    >> How can I determine what I get back? (i.e. what does the id pointer point to? an NSString? an NSNumber? NSDecimalNumber? NSData? another NSObject subclass?)
    >
    >
    > Others have told you about -isKindOfClass, but I think you are incorrect in thinking you even need it.
    >
    > If you ask for a property by name, you already know its type. It's no different than using the accessor. If you don't know its name, you can't ask for its value, so the type is irrelevant.
    >
    > Where the anonymity is a benefit is when you need to drive a UI from a data model. Most UI controls take an "object" value, which is typed id. By using -valueForKey: and then -setObjectValue:, your code doesn't need to know the type - it just passes the value as a black box of some kind. This is how bindings works, which eliminates even that bit of glue.
    >
    > Properties are not conceptually any different from a dictionary. If you ask a dictionary to return an object for a given key, you know its type by contract, not by inspection.

    How do you automatically know its type if you know its name?  I'm interested to understand the "why" of that statement.
  • On 19/07/2012, at 12:57 AM, Alex Zavatone wrote:

    > How do you automatically know its type if you know its name?  I'm interested to understand the "why" of that statement.

    Because it tells you the type right there in the @property declaration!

    If you're expecting a string and the object returns an array, who's at fault? Not you.

    --Graham
  • On Jul 18, 2012, at 11:06 AM, Graham Cox wrote:

    >
    > On 19/07/2012, at 12:57 AM, Alex Zavatone wrote:
    >
    >> How do you automatically know its type if you know its name?  I'm interested to understand the "why" of that statement.
    >
    >
    > Because it tells you the type right there in the @property declaration!
    >

    Ah, yes, when coding, of course.  For some reason, I was expecting that during runtime/debugging, if you know the name of an object or you are accessing a object your coworker wrote there was some concept that instantly told you its class and makeup.

    > If you're expecting a string and the object returns an array, who's at fault? Not you.
  • On Jul 18, 2012, at 11:06 AM, Graham Cox wrote:

    >
    > On 19/07/2012, at 12:57 AM, Alex Zavatone wrote:
    >
    >> How do you automatically know its type if you know its name?  I'm interested to understand the "why" of that statement.
    >
    >
    > Because it tells you the type right there in the @property declaration!
    >

    Ah, yes, when coding, of course.  For some reason, I was expecting that during runtime/debugging, if you know the name of an object or you are accessing a object your coworker wrote there was some concept that instantly told you its class and makeup.

    > If you're expecting a string and the object returns an array, who's at fault? Not you.
  • On Jul 18, 2012, at 6:08 AM, William Squires <wsquires...> wrote:

    > But it seems like the fact that there's no way to specify (or grab) the value of the property in a generic-enough manner would be a problem - anyone using it would still have to know the data type of the property they want to access, and thus they might as well just use the accessors.

    No, not really, since values can be used (as type 'id') without having to worry about their class.

    For example, key-value-observing (KVO) and bindings use KVC in a generic way, without having to care what the exact types of properties are. The runtime support for nibs can also use KVC to assign values to outlets.

    Is there something generic that you'd like to do with KVC that you can't figure out how to do?

    —Jens
  • On Jul 18, 2012, at 8:16 AM, Marco Tabini wrote:

    >
    > On 2012-07-18, at 9:08 AM, William Squires <wsquires...> wrote:
    >
    >> Okay, after reading some of the documentation on KVC coding, I understand (I think) that the point is to allow me to specify a property of an object with an NSString, then set/get that property value using KVC (i.e. valueForKey: or setValue:forKey:). But it seems like the fact that there's no way to specify (or grab) the value of the property in a generic-enough manner would be a problem - anyone using it would still have to know the data type of the property they want to access, and thus they might as well just use the accessors. I can see this would work if ObjC had a generic data type (like the 'variant' data type in VB/REALbasic), but AFAIK, ObjC doesn't have such, and valueForKey: returns an id, right?
    >> How can I determine what I get back? (i.e. what does the id pointer point to? an NSString? an NSNumber? NSDecimalNumber? NSData? another NSObject subclass?)
    >
    > id *is* ObjC's “variant” type. You can determine the type of the underlying data by first determining its class (e.g.: [obj isKindOfClass:[NSString class]]), and then going from there. Scalar or composite values are wrapped in the appropriate classes—e.g.: NSNumber for numeric values and NSValue for generic data. The whole system is really quite flexible, particularly when you consider that ObjC, unlike so many other languages, doesn't choke on Nil values.
    >
    >
    Okay, but the problem with wrapped scalars is determining what went in (int, float, char, BOOL) once you get it out (presumably as an NSNumber)? I suppose one way would be to define a 'type' dictionary in the object whose properties I'm interested in, but this would create extra complexity when creating the low-level model objects.

    What I'm trying to do is make a game engine that is generic enough that client code can call their properties whatever they want as long as they wrap the "effects" to apply on those objects with the appropriate key path (for example gameEngine.player.health) if the coder created an @property on the player class called "health". They could also call it "life" or "hitPoints" or "hp" or whatever, they just need to code the write key path into the 'codon' stored in the effect object. For example, an effect could be called "fireball" and do 1d6 HP to a player caught in the blast radius. My 'effect' class has an @property of type NSMutableArray (which holds 'codons' in NSStrings) of the form:

    <keypath><assignment operator><value>

    and the parser will check the <assignment> for '=', '+=', '-=', '*=' and '/=', evaluate the <value> portion, and pass those on to the player object via valueForKey: (to get the property's initial value before the assignment, and setValue:forKey: to set the new value.

    What I want is a way - in my parser - to check if the property at <keypath> is of a compatible data type to the <value> before executing the 'codon' and, if it's not, to raise an NSException with a descriptive message (including the name of the effect, and the mis-written 'codon').

    > —Mt.
  • On Jul 18, 2012, at 4:46 PM, William Squires <wsquires...> wrote:

    > Okay, but the problem with wrapped scalars is determining what went in (int, float, char, BOOL) once you get it out (presumably as an NSNumber)? I suppose one way would be to define a 'type' dictionary in the object whose properties I'm interested in, but this would create extra complexity when creating the low-level model objects.

    Just ask for what you want, and NSNumber will return it as that type. If you want a float, call -floatValue. If you want an int, call -intValue.
    If you want to know what the actual type stored was, check the .objCType property, which is a type-encoding string of the sort created by @encode. These are fairly easy to parse. If you need to access that, though, it's fairly likely that you're doing something wrong...

    —Jens
  • On 19/07/2012, at 1:33 AM, Alex Zavatone wrote:

    > Ah, yes, when coding, of course.  For some reason, I was expecting that during runtime/debugging, if you know the name of an object or you are accessing a object your coworker wrote there was some concept that instantly told you its class and makeup.

    Well, there is - you can use the runtime functions to delve into the class hierarchy of an object.

    But that's not how you write code.

    If you are using code a co-worker wrote then you use the documentation that the co-worker provided to make use of it. And by documentation I mean the headers - they are the "contract" which his code promises to abide by. If a property is declared to return a string and it doesn't, that's a bug, pure and simple. The same headers told you the names of the properties - how else would you know them? Property declarations link a name to a type. If every property were typed 'id' however and you were expected to examine the type at runtime, then your co-worker should probably be pensioned off rather quickly. Where a type is returned as 'id', it means that any object can be returned, and that's a hint that your code should not need to care what it is, and so, if you find yourself needing to know, that could be a code smell that something's amiss in your approach.

    Note that -isKindOfClass only tells you whether an object is a given class or a subclass of it, it returns a BOOL. So it's only there to confirm what you already expect, or to reject silly mistakes, like passing in the wrong kind of object to something that must have another kind.

    If you do something like this:

    NSString* theType = NSStringFromClass([someObject class]);

    if([theType isEqualToString:@"classTypeA"])
    {
    [self doStuffForClassAType:someObject];
    }
    else if( [theType isEqualToString:@"classTypeB"])
    {
    [self doStuffForClassBType:someObject];

    }
    else if .....

    then that's really violating the spirit and intention of object-oriented programming.

    --Graham
  • On 19/07/2012, at 9:46 AM, William Squires wrote:

    > Okay, but the problem with wrapped scalars is determining what went in (int, float, char, BOOL) once you get it out (presumably as an NSNumber)? I suppose one way would be to define a 'type' dictionary in the object whose properties I'm interested in, but this would create extra complexity when creating the low-level model objects.

    In fact you can't find this out for certain, the documentation states: "Note that number objects do not necessarily preserve the type they are created with".

    The internal storage is enough to preserve the precision of the value it was created with, but no more. When you ask for its value, the precision of the result is at least as good as what it was created with, and that's all it guarantees.

    Note that NSNumber has a -stringValue method, and NSString has -intValue and -floatValue methods too. So interconverting between numeric values and strings further exempts you from knowing the object type. However, I'm not sure whether, if you pass a string to -setValue:forKey: and the property is numeric, whether a conversion to an NSNumber is done there, or whether the object would just store the string. That might cause problems.

    Typically for UI controls, any conversion of this kind is done by the control (or strictly, by its formatter), so the situation doesn't tend to arise.

    > What I want is a way - in my parser - to check if the property at <keypath> is of a compatible data type to the <value> before executing the 'codon' and, if it's not, to raise an NSException with a descriptive message (including the name of the effect, and the mis-written 'codon').

    Why not just declare the property to always be a numeric type, and always convert it to one if necessary? Because it's trivial to get it in string form, you don't need to deal with strings here.

    Or, if there are other properties that have to be strings (e.g. player name, which has no numeric representation), you could wrap the whole thing in an object of your own class that preserves type information, so you know what the property ultimately needs to be converted to. Then your properties deal in this object type rather than NSNumbers or NSStrings, and you have a generic solution.

    Your parser could look at the value and determine that in fact it has no numeric representation, and so it knows it must preserve its "stringyness", and not return it as a NSNumber.

    --Graham
  • On Jul 18, 2012, at 20:31 , Graham Cox wrote:

    > However, I'm not sure whether, if you pass a string to -setValue:forKey: and the property is numeric, whether a conversion to an NSNumber is done there, or whether the object would just store the string. That might cause problems.

    If the property is a scalar numeric, KVC examines the property type and boxes or unboxes the scalar value to NSNumber or from the object it's given. Passing a string can indeed cause problems, though. The following code, for example, will throw an exception:

    @property unsigned int myValue;

    [someObject setValue: @"1" forKey: @"myValue"];

    (Hint: look up the documentation for -[NSString unsignedIntValue].)
  • On 19/07/2012, at 1:53 PM, Quincey Morris wrote:

    > (Hint: look up the documentation for -[NSString unsignedIntValue].)

    Yes, I see. :)

    In fact that's the second piece of not-quite-joined-up thinking I've found in Cocoa just today. Makes you wonder how often the members of the Cocoa dev teams compare notes.

    --Graham
  • Okay, I think there's a misunderstanding here. In this case - as the writer of the game engine - I don't know (nor should I care) what the interface to the model objects is - as long as the developer of said model objects codes the keys into my game engine, it should be able to manipulate the model object(s) according to the 'effects' the coder feeds into the game engine - KVC should do the rest. The parser in the game engine though, should check (somehow) to see if the value assigned in the 'effect' can be put into the property specified in the 'key' so that I can issue a scripting error (or raise an NSException) if they types are incompatible (in my case, I want the parser to be even more strict than C itself and warn if they assign an integer to a float, or vice versa.
      I'm wondering if there's some way to use introspection to figure out the (primitive) type of a property, such as 'int', 'float', 'char', or 'BOOL' without having to load the model classes with unnecessary complexity (i.e. keep the complexity in one place - the game engine.)

    On Jul 18, 2012, at 10:09 PM, Graham Cox wrote:

    >
    > On 19/07/2012, at 1:33 AM, Alex Zavatone wrote:
    >
    >> Ah, yes, when coding, of course.  For some reason, I was expecting that during runtime/debugging, if you know the name of an object or you are accessing a object your coworker wrote there was some concept that instantly told you its class and makeup.
    >
    >
    > Well, there is - you can use the runtime functions to delve into the class hierarchy of an object.
    >
    > But that's not how you write code.
    >
    > If you are using code a co-worker wrote then you use the documentation that the co-worker provided to make use of it. And by documentation I mean the headers - they are the "contract" which his code promises to abide by. If a property is declared to return a string and it doesn't, that's a bug, pure and simple. The same headers told you the names of the properties - how else would you know them? Property declarations link a name to a type. If every property were typed 'id' however and you were expected to examine the type at runtime, then your co-worker should probably be pensioned off rather quickly. Where a type is returned as 'id', it means that any object can be returned, and that's a hint that your code should not need to care what it is, and so, if you find yourself needing to know, that could be a code smell that something's amiss in your approach.
    >
    > Note that -isKindOfClass only tells you whether an object is a given class or a subclass of it, it returns a BOOL. So it's only there to confirm what you already expect, or to reject silly mistakes, like passing in the wrong kind of object to something that must have another kind.
    >
    > If you do something like this:
    >
    > NSString* theType = NSStringFromClass([someObject class]);
    >
    > if([theType isEqualToString:@"classTypeA"])
    > {
    > [self doStuffForClassAType:someObject];
    > }
    > else if( [theType isEqualToString:@"classTypeB"])
    > {
    > [self doStuffForClassBType:someObject];
    >
    > }
    > else if .....
    >
    >
    > then that's really violating the spirit and intention of object-oriented programming.
    >
    >
    > --Graham
  • On 19/07/2012, at 11:13 PM, William Squires wrote:

    > Okay, I think there's a misunderstanding here.

    Sure.

    > In this case - as the writer of the game engine - I don't know (nor should I care) what the interface to the model objects is - as long as the developer of said model objects codes the keys into my game engine, it should be able to manipulate the model object(s) according to the 'effects' the coder feeds into the game engine - KVC should do the rest. The parser in the game engine though, should check (somehow) to see if the value assigned in the 'effect' can be put into the property specified in the 'key' so that I can issue a scripting error (or raise an NSException) if they types are incompatible (in my case, I want the parser to be even more strict than C itself and warn if they assign an integer to a float, or vice versa.
    > I'm wondering if there's some way to use introspection to figure out the (primitive) type of a property, such as 'int', 'float', 'char', or 'BOOL' without having to load the model classes with unnecessary complexity (i.e. keep the complexity in one place - the game engine.)

    I think part of the problem here is that you have a reasonable understanding of your problem space, and are using terminology that makes sense to you but no-one else. From this and your previous description of what you're trying to do, to be honest I'm none the wiser. Perhaps a concrete example would help, because when you talk about 'effect' I think you mean something specific your game engine implements that is not the general meaning of 'effect'.

    I've been trying to ignore that and give you advice based on concepts in the abstract.

    One of these is the general rule that in object-oriented programming, taking different code-paths based on the type of an object is avoided, and instead some common method is called on the object and that takes the necessary code path. You seem determined to break this rule, which I suggest is a bad idea.

    If your game engine needs to violate basic concepts like this to do its thing, it's probably not a very good design. KVC exists so that any object can be treated (in terms of its properties) in much the same way as a dictionary, and that's about it. If that's not enough, it's probably not the right technology to be basing this on.

    You mention that assigning an int to a float should throw an exception. Basing it on KVC and NSNumber that's not possible. You can't know from the internal type of an NSNumber what value it was created with, for example, if I assign the float value 1.0 to an NSNumber it is perfectly within its rights to store it as an integer, because that's the maximum precision it requires to represent that particular float. If you attempt to examine the obj-c type of the NSNumber, you might see integer, not float, even though the original property that was boxed into this value was a float! Asking for its -floatValue however, you get 1.0, so there's no problem with this until you expect it to to track types end-to-end for you.

    All I can suggest is that you don't use the automatic boxing/unboxing of scalar properties and instead force clients of your engine to return properties in some form you can always deal with, such as an object you define that embodies the data value and type information. e.g. declare a class 'WSGameEngineProperty' that can hold numbers, strings or any other value you want to work with and ask your clients to send and receive these. (Make it easy on them by defining a nice set of convenience class methods for making these using any scalar or string type). You can still use KVC to set and retrieve these, because they are object types and won't be subject to automatic boxing. In effect what you're doing is giving up on automatically detecting the data type (because you can't for numbers, and shouldn't in general) and putting that responsibility onto the programmer who is the client of your engine, who is a lot more intelligent and can always do the right thing!

    If that's too big a pill to swallow, another alternative could be to replace KVC with something similar of your own which converts to and from your private property type automatically (you can use @encode to detect the original type of a scalar property). That way your implementation can track the type. The only restriction would be that your clients would have to implement their objects with methods that you require, such as setGameProperty:forKey:, and -gamePropertyForKey: in place of the KVC equivalents.

    I still fail to see how your game engine would even be able to ask for a property without knowing what it is going to be dealing with, or is it just up to the client to push values into your engine? If the latter, the client could tell you the type through the API you define, no?

    --Graham
  • On Jul 19, 2012, at 6:13 AM, William Squires <wsquires...> wrote:

    > I'm wondering if there's some way to use introspection to figure out the (primitive) type of a property, such as 'int', 'float', 'char', or 'BOOL'

    Sure — call the Obj-C runtime function property_getAttributes(). You'll first want to call class_getProperty() to get the objc_property_t pointer representing the property. If the property was declared in a superclass, you'll need to manually walk up the class hierarchy until you find the base class that has it.

    For an example of how to use this, you can take a look at
    https://github.com/snej/MYUtilities/blob/master/MYDynamicObject.m
    especially the functions getPropertyInfo() and getPropertyType().

    —Jens
  • On Jul 18, 2012, at 4:46 PM, William Squires <wsquires...> wrote:

    > For example, an effect could be called "fireball" and do 1d6 HP to a player caught in the blast radius. My 'effect' class has an @property of type NSMutableArray (which holds 'codons' in NSStrings) of the form:
    >
    > <keypath><assignment operator><value>
    >
    > and the parser will check the <assignment> for '=', '+=', '-=', '*=' and '/=', evaluate the <value> portion, and pass those on to the player object via valueForKey: (to get the property's initial value before the assignment, and setValue:forKey: to set the new value.

    This sounds kind of like a rule system, like WebObjects used to include in DirectToWeb (D2W), as a feature atop KVC and EOQualifier.

    Rather than using strings you need to parse as your model for something like this, things will be easier if you use a distinct class.

    For example, you could create a class that has a key path and an NSExpression used to generate a value based on parameters passed in (such as the original value at the key path). Then not only do you not have to do the parsing, you also get more features "free" because you can use everything NSExpression provides.

      -- Chris
previous month july 2012 next month
MTWTFSS
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
Go to today