objc_util — 桥接Objective-C API的工具集
objc_util
模块提供了使用Python中的Objective-C API的“桥梁”。
基于ctypes和Objective-C运行时库,objc_util
允许你将现有的Objective-C类自动“包装”成为Python方法进行调用相应的Objective-C信息。作为一个简单的示例,此Objective-C代码:
1 | UIPasteboard *pasteboard = [UIPasteboard generalPasteboard] |
可以翻译成以下Python代码:
1 | from objc_util import * |
你可以看到,从Python调用Objective-C API不需要比直接编写Objective-C使用更多的代码。
在后台,objc_util
生成对objc_msgSend()
等的适当调用,以将Python调用转换为Objective-C消息。当调用一个方法时,常见的Python类型(例如上面的例子中的字符串类型,以及还并未列出的字典、列表)被自动转换为等效的基础类型(NSString
,NSMutableArray
,NSMutableDictionary
等)。
从Objective-C选择器到Python方法名称的转换非常简单。你基本上只需要用下划线(’_
‘)替换冒号(’:
‘)。例如,选择器doFoo:withBar:
成为方法doFoo_withBar_
(注意尾下划线!)。
在大多数情况下,你还可以使用稍微更“pythonic”的语法,该语法使用关键字参数作为ObjC选择器名称的一部分,例如:
1 | # 从: UIColor *color = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0] |
这种(更自然的)语法在大多数情况下应该可以使用,但是在某些情况下,方法名称和关键字参数的给定组合无法明确地转换为ObjC方法调用。在这些情况下,只需使用上述的调用变体(用下划线替换冒号,并且不要使用关键字参数)。
注意:与一样ctypes,有很多方法可以使用此模块使Python崩溃。调用Objective-C方法时,必须非常小心以提供正确的参数类型。
示例1 –设置屏幕亮度
这个简单的示例设置设备的屏幕亮度:
1 | from objc_util import * |
示例2 – 在Music / iPod app中访问当前歌曲
此代码段在控制台中打印当前正在播放的歌曲(请注意,这仅适用于内置音乐应用程序,不适用于第三方音频播放器):
1 | from objc_util import * |
创建新的Objective-C类
为了更高级地使用Objective-C API,有时需要在运行时创建自己的Objective-C类。你要执行此操作的主要两种情况是:
- 实现常用的代理模式,即为内置的Objective-C类提供回调接口。
- 继承Objective-C类,以便进行自定义。一个例子是继承
UIView
以重载-drawRect:
。
为此,objc_util
模块提供了create_objc_class()
函数,该函数使用Objective-C运行时来分配和注册新类,然后将该类包装在一个ObjCClass
对象中,你可以像上面示例中的内置类一样使用该对象。
要使用create_objc_class()
创建一个Objective-C类,你需要做以下事情:
命名 - 创建类的名称,是一个字符串。它只能由字母,数字和下划线字符组成。它不能以数字开头。请注意,实际创建的类的名称可能与此不同,因为具有该名称的类可能已经存在。在这种情况下,如果
debug
参数为True
(默认值),则会自动选择一个新名称。如果debug
为False
,则将返回现有的类,并且忽略所有其他参数。父类 – 一个
ObjCClass
对象,确定新类从其继承的Objective-C类。方法 – 用于创建新类的实例方法的函数列表。要从Python函数创建Objective-C方法,Objective-C运行时需要其他元数据:选择器名称,返回值的类型以及任何参数的类型。
create_objc_class()
尽可能尝试自动导出此元数据,请参见下面对于create_objc_class()
的描述以获取详细信息。每个Objective-C方法都需要至少两个从Objective-C调用时隐藏的参数:_self
是指向Objective-C对象本身的指针(请注意,该方法未包装在ObjCInstance
对象中,因此如果需要,则必须手动执行此操作),以及_cmd
,是指向选择器的指针(通常不需要)。这两个“隐藏”参数的名称无关紧要。请注意,参数是作为“原始”指针而不是ObjCInstance
对象传递给Objective-C方法的,但是你可以通过手动包装对象参数来轻松地转换对象参数,例如obj = ObjCInstance(_self)
。类方法(可选)– 与方法相同,但用于类方法(很少需要)。
协议(可选)– 字符串列表,用于提示方法的类型编码。如果实现代理(或其他)协议,则应包括协议名称(例如’
UITableViewDataSource
‘),以确保可以正确推断任何方法的返回值和参数类型。
这是创建一个简单类的示例,该类充当MFMailComposeViewController
的代理(用于显示标准iOS 邮件工作表)。代理必须使用此类,因为否则将无法摆脱邮件工作表(工作表完成后会通知该代理,并负责将其关闭)。:
1 | # - (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error |
API参考
类
class objc_util.ObjCClass(name)
具有给定名称的Objective-C类的包装;充当调用Objective-C类方法的代理。
将方法调用即时转换为Objective-C消息 - 这是通过将方法名称中的下划线替换为选择器名称中的冒号,并将选择器和参数用于对Objective-C运行时中低级函数objc_msgSend()
的调用来完成的。
例如,调用NSDictionary.dictionaryWithObject_forKey_(obj, key)
(Python)被有效地转换为[NSDictionary dictionaryWithObject:obj forKey:key]
(Objective-C)。如果方法调用返回了Objective-C对象,则将其包装在ObjCInstance
中,因此可以将调用链接起来(ObjCInstance
使用等效的代理机制)。
一些常用的类是模块的成员(请参见底部的列表),对于其他一些类,您只需使用类名“导入”即可,例如:
1 | UIPasteboard = ObjCClass('UIPasteboard') |
class objc_util.ObjCInstance(ptr)
包装器,用于指向Objective-C对象的指针;充当向对象发送消息的代理。
将方法调用即时转换为Objective-C消息 - 这是通过用选择器名称中的冒号替换方法名称中的下划线,并在Objective-C运行时中使用选择器和参数来调用objc_msgSend()
函数来完成的。例如,调用obj.setFoo_withBar_(foo, bar)
(Python)被有效地转换为[obj setFoo:foo withBar:bar]
(Objective-C)。如果方法调用返回了Objective-C对象,则它也被包装在ObjCInstance
中,因此可以将调用链接起来。
ObjCInstance
通过调用NSObject
的description
方法实现__str__
和__repr__
。
如果实例包装了一个共同的Objective-C集合类型(NSArray
,NSDictionary
,NSSet
),其行为类似于原来的Python集合中的许多方面。它可以迭代(for .. in
),您可以通过键/索引,使用方括号表示法(some_dict['key'],some_array[3]...
)等访问项目。
class objc_util.ObjCBlock(func, restype=None, argtypes=None)
警告: 区块支持是实验性的。如果您可以选择使用不需要块的API,则强烈建议您这样做。
ObjCBlock
可用于将块(“闭包”)传递给Objective-C方法。如上所述,这是实验性的,如果可能的话,您通常应首选使用不需要块的API。但是某些API绝对需要使用块。对于没有返回值且没有参数的块,您可以传递Python函数,并且其将自动转换为ObjCBlock
。在其他情况下,在显式创建块时,需要指定return
和argumet
类型。
带参数的块的示例(用于通过自定义比较函数对NSMutableArray进行排序):
1 | from objc_util import * |
函数
objc_util.autoreleasepool()
为NSAutoreleasePool
充当包装器的上下文管理器(类似于Objective-C中的@autoreleasepool {...}
)。
使用:
1 | with objc_util.autoreleasepool(): |
objc_util.create_objc_class(name, superclass=NSObject, methods=[], classmethods=[], protocols=[], debug=True)
创建并返回一个实现给定方法的新ObjCClass。
- 选择器名称是从函数名称派生的。函数名称可以选择以Objective-C类名称为前缀。例如,这两个函数产生等效的选择器名称:
1 | def MyClass_doSomething_withObject_(_self, _cmd, foo, bar): |
- 确定返回类型和参数类型,
create_objc_class()
检查父类是否具有带有相同选择器的方法。在这种情况下,类型将从父类的方法继承。此策略适用于重载子类中的方法。如果失败,则使用protocols
参数。协议是字符串列表,例如['UIGestureRecognizerDelegate', 'UITableViewDataSource']
– 这些协议使用相同的选择器检查方法。例如,此策略可用于实现委托协议。总之,这些策略应确定最常见情况下的类型信息。如果他们不适合你的情况,你可以在你传递的函数对象上设置restype
,argtypes
和encoding
。
注意: 如果要使用现有的 Objective-C类,只需使用获得对它的引用
ObjCClass(name)
。该函数用于创建新类,例如,将Objective-C继承或实现委托协议。
objc_util.load_framework(name)
用给定的名称(例如’SceneKit’)加载系统框架。
这等效于Objective-C代码。[[NSBundle bundleWithPath:@"/System/Frameworks/<name>.framework"] load]
objc_util.ns(obj)
将常见的Python对象转换为它们的ObjC等效项,即str=> NSString
,int/ float=> NSNumber
,list=> NSMutableArray
,dict=> NSMutableDictionary
,bytearray=> NSData
,set=> NSMutableSet
。支持嵌套结构(list
/ dict
/ set
)。如果一个对象已经是ObjCInstance
的实例,则保持不变。
如果Objective-C方法期望将对象作为参数,则使用此函数自动转换Python对象参数。例如,您可以将Python字符串传递给期望使用NSString
的Objective-C方法。
objc_util.nsurl(url_or_path)
将Python字符串转换为NSURL
对象(包装于ObjCInstance
)。
如果字符串包含冒号(’:
‘),则将其视为完整URL,并转换为NSURLusing +URLWithString:
。否则,将使用+fileURLWithPath:
构建URL 。
objc_util.nsdata_to_bytes(data)
将一个NSData
对象(包装于ObjCInstance
)转换为Python字节字符串。
objc_util.uiimage_to_png(img)
将UIImage
对象(包装于ObjCInstance
)转换为包含PNG数据的Python字节字符串。
objc_util.on_main_thread(func)
装饰函数,用于在UIKit主线程上调用另一个函数。许多Objective-C API(尤其是UIKit中的API)都需要从主线程中调用。这通常用于装饰另一个函数,但也可以用于将函数调用分派到主线程,例如on_main_thread(my_function)(param1, param2)
装饰器示例:
1 | from objc_util import * |
objc_util.sel(name)
sel_registerName的方便的包装器(将Python字符串转换为Objective-C选择器)。
Objective-C类/数据结构
为了方便起见,提供了一些常用的Objective-C类和数据结构作为模块级对象,因此不必显式包装它们。他们是:
class objc_util.CGPoint
class objc_util.CGSize
class objc_util.CGVector
class objc_util.CGRect
class objc_util.CGAffineTransform
class objc_util.UIEdgeInsets
class objc_util.NSRange
class objc_util.NSDictionary
class objc_util.NSMutableDictionary
class objc_util.NSArray
class objc_util.NSMutableArray
class objc_util.NSSet
class objc_util.NSMutableSet
class objc_util.NSString
class objc_util.NSMutableString
class objc_util.NSData
class objc_util.NSMutableData
class objc_util.NSNumber
class objc_util.NSURL
class objc_util.NSEnumerator