SiriBlog

siriyang的个人博客


  • 首页

  • 排行榜

  • 标签115

  • 分类37

  • 归档320

  • 关于

  • 搜索

Pythonista中文文档:objc_util

发表于 2020-04-02 更新于 2021-10-29 分类于 计算机 , 技术 , Python 阅读次数: Valine:
本文字数: 9.7k 阅读时长 ≈ 9 分钟

Pythonista中文文档

objc_util — 桥接Objective-C API的工具集


  objc_util模块提供了使用Python中的Objective-C API的“桥梁”。

  基于ctypes和Objective-C运行时库,objc_util允许你将现有的Objective-C类自动“包装”成为Python方法进行调用相应的Objective-C信息。作为一个简单的示例,此Objective-C代码:

1
2
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]
[pasteboard setString:@"Hello Objective-C"];

可以翻译成以下Python代码:

1
2
3
4
5
from objc_util import *
UIPasteboard = ObjCClass('UIPasteboard')

pasteboard = UIPasteboard.generalPasteboard()
pasteboard.setString_('Hello Objective-C')

  你可以看到,从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
2
3
4
5
6
# 从: UIColor *color = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0]
# 到:
UIColor = ObjCClass('UIColor')
color = UIColor.colorWithRed(1.0, green=0.0, blue=0.0, alpha=1.0)
# 甚至:
color = UIColor.color(red=1.0, green=0.0, blue=0.0, alpha=1.0)

  这种(更自然的)语法在大多数情况下应该可以使用,但是在某些情况下,方法名称和关键字参数的给定组合无法明确地转换为ObjC方法调用。在这些情况下,只需使用上述的调用变体(用下划线替换冒号,并且不要使用关键字参数)。

注意:与一样ctypes,有很多方法可以使用此模块使Python崩溃。调用Objective-C方法时,必须非常小心以提供正确的参数类型。

示例1 –设置屏幕亮度

  这个简单的示例设置设备的屏幕亮度:

1
2
3
4
5
6
7
8
9
10
11
12
from objc_util import *

# “导入”一个 Objective-C 类 (为该类生成一个代理):
UIScreen = ObjCClass('UIScreen')

# 调用类方法,这等效于Objective-C中的 `[UIScreen mainScreen]` :
screen = UIScreen.mainScreen()

# `screen`现在是用 ObjCInstance 包装而成的Objective-C对象,并且期待一个消息给他。

# 下面的调用等效于`[screen setBrightness:0.6]`:
screen.setBrightness(0.6)

示例2 – 在Music / iPod app中访问当前歌曲

  此代码段在控制台中打印当前正在播放的歌曲(请注意,这仅适用于内置音乐应用程序,不适用于第三方音频播放器):

1
2
3
4
5
6
7
8
9
10
11
from objc_util import *

MPMusicPlayerController = ObjCClass('MPMusicPlayerController')
player = MPMusicPlayerController.systemMusicPlayer()
now_playing = player.nowPlayingItem()
if now_playing:
artist = now_playing.valueForProperty_('artist')
title = now_playing.valueForProperty_('title')
print('Now playing: %s -- %s' % (artist, title))
else:
print('No music playing')

创建新的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
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
# - (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
def mailComposeController_didFinishWithResult_error_(_self, _cmd, controller, result, error):
print('Mail composer finished')
# 在`ObjCInstance`中包装控制器参数,这样我们可以传递信息:
mail_vc = ObjCInstance(controller)
# 将代理设为None,释放其内存:
mail_vc.setMailComposeDelegate_(None)
ObjCInstance(_self).release()
# 取消工作表:
mail_vc.dismissViewControllerAnimated_completion_(True, None)

methods = [mailComposeController_didFinishWithResult_error_]
protocols = ['MFMailComposeViewControllerDelegate']
MyMailComposeDelegate = create_objc_class('MyMailComposeDelegate', NSObject, methods=methods, protocols=protocols)

@on_main_thread
def show_mail_sheet():
MFMailComposeViewController = ObjCClass('MFMailComposeViewController')
mail_composer = MFMailComposeViewController.alloc().init().autorelease()
# 使用你的新代理类:
delegate = MyMailComposeDelegate.alloc().init()
mail_composer.setMailComposeDelegate_(delegate)
# 显示邮件工作表:
root_vc = UIApplication.sharedApplication().keyWindow().rootViewController()
root_vc.presentViewController_animated_completion_(mail_composer, True, None)

if __name__ == '__main__':
show_mail_sheet()

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from objc_util import *
cheeses = ns(['Camembert', 'Feta', 'Gorgonzola'])
print(cheeses)

def compare(_cmd, obj1_ptr, obj2_ptr):
obj1 = ObjCInstance(obj1_ptr)
obj2 = ObjCInstance(obj2_ptr)
# 通过长度排序字符串:
return cmp(obj1.length(), obj2.length())

# 注意: 第一个参数 (隐藏的) `_cmd` 是块本身, 所以这里使用三个参数代替两个参数
compare_block = ObjCBlock(compare, restype=NSInteger, argtypes=[c_void_p, c_void_p, c_void_p])

sorted_cheeses = cheeses.sortedArrayUsingComparator_(compare_block)
print(sorted_cheeses)

函数

objc_util.autoreleasepool()

  为NSAutoreleasePool充当包装器的上下文管理器(类似于Objective-C中的@autoreleasepool {...})。

使用:

1
2
with objc_util.autoreleasepool():
# 做一些事情...

objc_util.create_objc_class(name, superclass=NSObject, methods=[], classmethods=[], protocols=[], debug=True)

  创建并返回一个实现给定方法的新ObjCClass。

  • 选择器名称是从函数名称派生的。函数名称可以选择以Objective-C类名称为前缀。例如,这两个函数产生等效的选择器名称:
1
2
3
4
5
6
7
def MyClass_doSomething_withObject_(_self, _cmd, foo, bar):
pass

def doSomething_withObject_(_self, _cmd, foo, bar):
pass

# 两个函数的选择器名称都将是'doSomething:withObject:'.
  • 确定返回类型和参数类型,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
2
3
4
5
6
7
8
9
from objc_util import *

@on_main_thread
def post_notification(name):
NSNotificationCenter = ObjCClass('NSNotificationCenter')
center = NSNotificationCenter.defaultCenter()
center.postNotificationName_object_(name, None)

# 每次调用 post_notification(...) 都会自动地在主线程发生

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

-------- 本文结束 感谢阅读 --------
相关文章
  • Pythonista中文文档:scene
  • Pythonista中文文档:cb
  • Pythonista中文文档:contacts
  • Pythonista中文文档:canvas
  • Pythonista中文文档:photos
觉得文章写的不错的话,请我喝瓶怡宝吧!😀
SiriYang 微信支付

微信支付

SiriYang 支付宝

支付宝

  • 本文标题: Pythonista中文文档:objc_util
  • 本文作者: SiriYang
  • 创建时间: 2020年04月02日 - 21时04分
  • 修改时间: 2021年10月29日 - 18时10分
  • 本文链接: https://blog.siriyang.cn/posts/20200402210058id.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
Python Pythonista 文档
Pythonista中文文档:sound
Pythonista中文教程:ui.TableView使用教程
  • 文章目录
  • 站点概览
SiriYang

SiriYang

努力搬砖攒钱买镜头的摄影迷
320 日志
33 分类
88 标签
RSS
GitHub E-Mail
Creative Commons
Links
  • 友情链接
  • 作品商铺

  1. objc_util — 桥接Objective-C API的工具集
    1. 示例1 –设置屏幕亮度
    2. 示例2 – 在Music / iPod app中访问当前歌曲
    3. 创建新的Objective-C类
    4. API参考
      1. 类
      2. 函数
    5. Objective-C类/数据结构
蜀ICP备19008337号 © 2019 – 2025 SiriYang | 1.7m | 25:41
0%