pyobjc indexed accessor method with range
Asked Answered
U

1

3

I'm trying to implement an indexed accessor method for my model class in Python, as per the KVC guide. I want to use the optional ranged method, to load multiple objects at once for performance reasons. The method takes a pointer to a C-array buffer which my method needs to copy the objects into. I've tried something like the following, which doesn't work. How do I accomplish this?

@objc.accessor    # i've also tried @objc.signature('v@:o^@')
def getFoos_range_(self, range):
    return self._some_array[range.location:range.location + range.length]

Edit: I finally found the type encodings reference after Apple moved all the docs. After reading that, I tried this:

@objc.signature('v@:N^@@')
def getFoos_range_(self, buf, range):

but this didn't appear to work either. The first argument is supposed to be a pointer to a C-array, but the length is unknown until runtime, so I didn't know exactly how to construct the correct type encoding. I tried 'v@:N^[1000@]@' just to see, and that didn't work either.

My model object is bound to the contentArray of an NSArrayController driving a table view. It doesn't appear to be calling this method at all, perhaps because it expects a different signature than the one the bridge is providing. Any suggestions?

Unconcerned answered 3/9, 2009 at 23:19 Comment(0)
F
2

You were close. The correct decorator for this method is:

@objc.signature('v@:o^@{_NSRange=QQ}')

NSRange is not an object, but a struct, and can't be specified simply as @; you need to include the members1.

Unfortunately, this is not the end of it. After a whole lot of experimentation and poring over the PyObjC source, I finally figured out that in order to get this method to work, you also need to specify metadata for the method that is redundant to this signature. (However, I still haven't puzzled out why.)

This is done using the function objc.registerMetaDataForSelector:

objc.registerMetaDataForSelector(b"SUPERCLASSNAME", 
                                 b"getKey:range:",
        dict(retval=dict(type=objc._C_VOID),
             arguments={ 
                  2+0:  dict(type_modifier=objc._C_OUT,
                             c_array_length_in_arg=2+1),
                  2+1:  dict(type=b'{_NSRange=II}',
                             type64=b'{_NSRange=QQ}')
             }
        )
)

Examples and some details of the use of this function can be found in the file test_metadata_py.py (and nearby test_metadata*.py files) in the PyObjC source.

N.B. that the metadata has to be specified on the superclass of whatever class you are interested in implementing get<Key>:range: for, and also that this function needs to be called sometime before the end of your class definition (but either before or inside the class statement itself both seem to work). I haven't yet puzzled these bits out either.

I based this metadata on the metadata for NSArray getObjects:range: in the Foundation PyObjC.bridgesupport file2, and was aided by referring to Apple's BridgeSupport manpage.

With this worked out, it's also worth noting that the easiest way to define the method is (at least, IMO):

@objc.signature('v@:o^@{_NSRange=QQ}')
def get<#Key#>_range_(self, buf, inRange):
    #NSLog(u"get<#Key#>")
    return self.<#Key#>.getObjects_range_(buf, inRange)

I.e., using your array's built-in getObjects:range:.


1: On 32-bit Python, the QQ, meaning two unsigned long longs, should become II, meaning two unsigned ints
2: Located (on Snow Leopard) at: /System/Library/Frameworks/Python.framework/Versions/2.6/Extras/lib/python/PyObjC/Foundation/PyObjC.bridgesupport

Facesaving answered 17/3, 2011 at 8:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.