Open freakboy3742 opened 6 years ago
Hm, it seems like calls to keyboardType
are handled dynamically. The keyboardType
property does exist on UITextField
, but there is no keyboardType
method. Instead, UITextField
overrides forwardingTargetForSelector:
. When called with SEL("keyboardType")
as an argument, it returns a UITextInputTraits
object, which does have a statically provided keyboardType
method. (The UITextInputTraits
here is a class, apparently undocumented, and not the protocol of the same name.)
When calling a method with Rubicon using Python method call syntax, the method is looked up on the class (in the attribute access) and then called later (when the ObjCBoundMethod
object is called). This doesn't work for dynamic methods, since they don't exist on the class. On the other hand, send_message
uses objc_msgSend
and friends to send a method call to the object directly, which triggers the whole dynamic method call process.
Here are some results of playing around with this in Pythonista (iPhone SE, iOS 10.3.3). I used this library that I wrote a while ago since it has better reflection/introspection support than Rubicon does at the moment. (Eventually I want to port that over to Rubicon, or implement something similar, when I have the time...)
>>> import objc
>>> objc.classes.UITextField
Out[0] = objc.api.Class('UITextField')
>>> objc.classes.UITextField.instance_properties_declared["keyboardType"]
Out[2] = <objc.api.Property 'keyboardType', attributes b'Tq,D,N'>
>>> objc.classes.UITextField.instance_methods_declared["keyboardType"]
Traceback (most recent call last): <snip>
builtins.KeyError: objc.api.Selector('keyboardType')
>>> objc.classes.UITextField.instance_methods["keyboardType"]
Traceback (most recent call last): <snip>
builtins.KeyError: 'keyboardType'
>>> tf = objc.classes.UITextField.new()
>>> tf.methods["keyboardType"]
Traceback (most recent call last): <snip>
builtins.KeyError: 'keyboardType'
>>> tf.keyboardType()
Traceback (most recent call last): <snip>
builtins.AttributeError: keyboardType
>>> tf.msg_send("keyboardType")
Traceback (most recent call last): <snip>
builtins.ValueError: No method found for selector b'keyboardType', cannot infer restype and argtypes
>>> tf.msg_send("keyboardType", restype="long long", argtypes=[])
Out[5] = 0
>>> tf.methods["keyboardType"]
Traceback (most recent call last): <snip>
builtins.KeyError: 'keyboardType'
>>> tf.forwardingTargetForSelector_(objc.Selector("keyboardType"))
Out[498] = <objc.api.ID wrapping UITextInputTraits at <cdata 'id_s *' 0x1741d1df0>: <UITextInputTraits : 0x1741d1df0>
public
autocapitalization: 2
autocorrection: 0
spellchecking: 0
keyboard type: 0
kb appearance: 0
<snip>
>
>>> _.keyboardType()
Out[499] = 0
On iOS,
UITextField
defines akeyboardType
property. However, this property doesn't appear to get picked up by Rubicon.Other properties (like
borderStyle
) work fine.If you manually invoke
send_message(obj, 'setKeyboardType:', ...)
, it works, too.So - there's evidently something about the property discovery process.
A little digging revealed that the
keyboardType()
andsetKeyboardType()
accessor and mutator aren't found bycache_property_methods()
. This may be due to the fact that they're defined on theUITextInputTraits
protocol, rather than on a direct superclass ofUITextField
.