iOS 项目中使用 Python下

上一节最后我们通过将 Python 所依赖的 Resources 通过 Bundle 成功引入项目。并通过 fopen 方式结合 PyRun_SimpleFileEx 执行成功,对于执行单一方法的 Py 文件来说还是很方便的,但是我们的业务往往比较复杂,需要被拆分成多个文件类,除系统自带的库之外我们也需要引入一些其他的第三方库,同时我们也需要实例化某个具体类,并调用这些类的中的具体方法并且传入参数,那么我们该如何实现呢?

Py代码/第三方库的引入

直接引入

第一种方式比较简单粗暴,我们将自己开发的文件统一放在一个文件目录下,并命名为 ocpy 。并将该文件目录直接放入 Resources 目录中的 python 环境目录下,如下图所示

7.png

同样的,我们把运行所依赖的其他第三方资源直接也放在该目录下,如 requests 库

8.png

这种方式比较简单,但是同时将 Python环境自有的库、第三方库、和自己的库混合在一起不方便今后的管理。有没有更好的方式来实现当然有,我们可以通过设置 PYTHONPATH 来实现。

设置 PYTHONPATH

我们新建一个 PythonCon.bundle 资源文件,把我们自己开发的文件放入里面如下图所示

9.png

在设置完 PythonHome 路径后,即可设置 PYTHONPATH 的路径为PythonCon.bundle

NSString *python_path = [NSString stringWithFormat:@"PYTHONPATH=%@/PythonCon.bundle/", resourcePath, nil];
putenv((char *)[python_path UTF8String]);

同样的我们可以用该方法设置我们所依赖的第三方库(此处未把第三方库从PythonEnv.bundle中独立出来)。

Python 类中方法的调用

模块的导入&类的创建

以上准备工作完成后,我们便具备对文件类中的方法进行调用了,首先我们需要引入我们需要调用的模块,然后获取该模块中所有的类和方法,获取并初始化我们需要的类

// 模块的引入
PyObject *pModule = PyImport_ImportModule([@"ocpy.main" UTF8String]);
// 获取模块中的类/方法
PyObject *pDict = PyModule_GetDict(pModule);
// 获取某个具体的e类/方法
PyObject *pClass = PyDict_GetItemString(pDict, "JDGFileObject");
// 创建&初始化该类
PyObject *pyClass = PyInstanceMethod_New(pClass);
_pyInstance = PyObject_CallObject(pyClass, NULL);

方法调用

我们可以使用 PyObject_CallMethod 方法来调用我们所创建类中的方法,PyObject_CallMethod 需要至少传入三个参数,
第一个即是我们刚刚初始化的类 _pyInstance,第二个参数为我们需要调用的该类中的方法,第三个及以后是调用该方法中需要传入的参数类型(如果没有可直接为 NULL)

例如我们需要调用 main 文件中的 test 方法,该方法需要两个字符串类型 arg1和arg2参数,并最终返回一个 "success" 结果,最终我们通过使用PyArg_Parse 从 result 对象中成功解析出我们所需要的内容 "success"。

PyObject *result = PyObject_CallMethod(_pyInstance, [func UTF8String], "(s,s)", [arg1 UTF8String], [arg2 UTF8String]);
char *resultCString = NULL;
PyArg_Parse(result, "s", &resultCString);
NSString *content = [NSString stringWithFormat:@"%s", resultCString];
NSLog(@"========%@", content);

以上便是整个py类设置调用的过程,完整的代码见文末

Pod 发布过程中的问题

当所有工作完成后,我们可以将该工具发布出去供他人使用,而公司内部也有成熟的组件管理工具,非常方便,我们只需要把相关模块发布到系统中,其他人就可以直接在他的模块中使用。我准备把这个检测工具包含 Python 和依赖的 OpenSSL、BZip2、XZ等通过该工具发布到系统中。

项目编译 Must define SIZEOF_WECHSR_T 问题

以前发布的组件都是比较常规类型的,没发布过这种依赖二进制文件类型的组件,因此也就没研究过这个工具的发布过程,没想到这一次发布就出现问题了

1.png

该工具在发布时,有一个检验的过程,和工具维护人员沟通后了解到公司内部所发布的组件需要同时支持 armv7、arm64 、i386、 x86_64 这四种类型,而作者提供的默认 .a 只包含 arm64 和 x86_64 这两种类型,而我在做测试时一直使用的是 iphone6 及其以上的模拟器,因此一直没有出现这个错误,当我换成 iphone5模拟器去执行时,出现了下面这个错误提示

4.png

和上面的错误日志是一样的,还好作者提供了编译脚本项目,通过查看作者提供的脚本 中makefile 文件,打包的时候只支持 arm64 和 x86_64 这两种类型,因此我们只需要对 makefile 进行简单的修改这样就可以解决这个问题。因为我只使用了iOS模块因此我只修改了该模块

2.png

修改完成后在该文件的同目录下执行 make iOS 命令完成重新编译。support 目录下的文件就是我们需要的最新文件如下图所示

3.png

发布 OpenSSL 问题

以上问题解决后,再次执行发布功能,这次出现了另外一个错误

6.png

错误提示比较少,看不出具体问题,在不引入其他模块进行项目编译、发布等都是是正常的,和工具维护人员也进行了相关沟通,他也进行了尝试,也出现了相同的错误。

既然常规方式不行,只能通过各种其他方式进行问题排查,拆分各个模块,每次只发布其中一个模块,最后发现只有在发布 openssl 这个模块时出现了这个错误,其他模块单独和一起发布都是正常的,问题缩小就容易解决了,经过了解,openssl 这个模块已经有相关成功组件,那就是自己的原因了,询问了对方是如何进行发布操作的,但是他也不知道,最后把对方发布成功的包给要过来了,经过对比发现,我的文件比对方的要多几个 __DECC_INCLUDE_ 开头的文件,如下图

5.png

看了下这些文件,里面无相关代码,只有一些注释,其中有一句 This file is only used by HP C on VMS, and is included automatically,既然在iOS里面用不到,那就直接删了(注意删除其他目录下类似文件),删除后再次发布,这次终于发布成功了。

完整的代码

NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
NSLog(@"resourcePath === %@",resourcePath);
NSString *python_home;
putenv("PYTHONOPTIMIZE=1");
putenv("PYTHONDONTWRITEBYTECODE=1");
putenv("PYTHONUNBUFFERED=1");
// Set the home for the Python interpreter
python_home = [NSString stringWithFormat:@"%@/PythonEnv.bundle/Resources/", resourcePath, nil];
NSLog(@"PythonHome is: %@", python_home);
Py_SetPythonHome(Py_DecodeLocale([python_home UTF8String], NULL));
    
NSString *python_path = [NSString stringWithFormat:@"PYTHONPATH=%@/PythonCon.bundle/", resourcePath, nil];
putenv((char *)[python_path UTF8String]);
NSLog(@"PYTHONPATH is: %@", python_path);
NSLog(@"Initializing Python runtime...");
Py_Initialize();
PyEval_InitThreads();
    
// 模块的引入
PyObject *pModule = PyImport_ImportModule([@"ocpy.main" UTF8String]);
// 获取模块中的类/方法
PyObject *pDict = PyModule_GetDict(pModule);
// 获取某个具体的e类/方法
PyObject *pClass = PyDict_GetItemString(pDict, "JDGFileObject");
// 创建&初始化
PyObject *pyClass = PyInstanceMethod_New(pClass);
PyObject *pyInstance = PyObject_CallObject(pyClass, NULL);

// 方法的调用
PyObject *result = PyObject_CallMethod(pyInstance, [func UTF8String], "(s,s)", [arg1 UTF8String], [arg2 UTF8String]);
char *resultCString = NULL;
PyArg_Parse(result, "s", &resultCString);
NSString *content = [NSString stringWithFormat:@"%s", resultCString];
NSLog(@"========%@", content);

以上内容参考以下文章

c-api

C调用python类的正确方法

在iOS中运行Python

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342