Forum Archive

objc_util wishlist

scj643

I have found myself using objc_util a whole lot. Heck my pride and joy of a library is made with it

Some things I wish objc_util had are

Better ways of responding to blocks.

A block should have it's own accessible class that functions within the block can access. Also a block should be self contained and not having to push to a global variable

class Block(object):
    def __init__(self):
        self.someValue = 1
    def callOnFinish(self, passed_value):
        self.someValue = passed_value

Better type error handling

Currently objc_utils throws very cryptic errors when a type issue happens with an objc function. The type expected should be stated in the error

Error objects

If something has an option that is an NSError a built in part of objc_util should provide that. (I currently have an implementation but it's strange)

Though in all I'm still really happy with objc_util

lukaskollmer

@scj643 said:

Currently objc_utils throws very cryptic errors when a type issue happens with an objc function

What exactly do you mean? I never found the objc_util errors to be cryptic at all.

scj643

It doesn't tell you what type it expected

lukaskollmer

@scj643 because it doesn't know. The objc runtime can only get the expected type encoding of an argument or a method's return value. The type encoding specifies which primitive type an argument should be (float/int/char/etc) or whether it is an object. But since ObjC classes are basically just glorified structs, the runtime can't differentiate between different classes. (That's why objc has the id type to represent "any objc-object")

NSHipster on Type Encodings

scj643

It doesn't actually say what type it wants
example

v = UIView.new()
v.setAlpha_('t')
lukaskollmer

@scj643 it says that the type error is caused by the 3rd argument (which makes sense since 1 is self and 2 is cmd).
And it can't say which type it wants because it doesn't know what to expect, it just knows that what it got was wrong

JonB

Actually, since objc_util is setting up the ctypes call (based on the method encoding), it does know the types that are needed. For instance, you can copy objc_util and make the following changes (two places, in ObjCClassMethod and ObjCinstanceMethod) where the objc_msgSend calls are:

            try:
                res = objc_msgSend(obj.ptr, sel(self.sel_name), *args)
            except ctypes.ArgumentError as e:
                argnum=int(re.match('argument (\d*):',e.args[0]).groups()[0]) #1based
                e.args=('{} UserArgument #{}: Expected:{}, Got {}'.format(self.sel_name,argnum-2,argtypes[argnum-3], type(args[argnum-3])), )
                raise

which produces a traceback like

>>> v = UIView.new()
>>> v.setAlpha_('t')
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/private/var/mobile/Containers/Shared/AppGroup/C534C622-2FDA-41F7-AE91-E3AAFE5FFC6B/Pythonista3/Documents/objc_util3.py", line 901, in __call__
    res = objc_msgSend(obj.ptr, sel(self.sel_name), *args)
ctypes.ArgumentError: setAlpha: UserArgument #1: Expected:<class 'ctypes.c_void_p'>, Got <class 'str'>

Frankly, I have never understand why the python ctypes.ArgumentError doesnt already contain that info.

JonB

@scj643 re: blocks, you can always add instance variables (functions are objects in python).

def myblk(blk, arg):
    if myblk.x:
        return myblk.x
myblk.x=2
...
blk=ObjCBlock(myblk)

It would be nice to have a ObjCBlock as decorator:

@ObjCBlock(argtypes=[c_void_p], restype=None)
def myfun(blk, someargs):
    ...

Also, you can have blocks that live as instance methods:

class MyDelegate(object):
...    def myblock(self,_blk):
...       print(self.x)
...    def __init__(self):
...       self.x=10
...    
>>> m=MyDelegate()
>>> m.blk=ObjCBlock(m.myblock,argtypes=[c_void_p]

This can let you associate other objects with a particular block, to avoid the need for globals.