iOS 项目中如何使用 Python
我所在的公司对项目编译后的大小和资源文件有严格的要求,每次集成发版对于包体积的增量都是有严格的控制,因此,如何减少包体积是每一个研发都需要考虑的。
对于包体积大小我们可以从资源文件和编码来控制,如何减少项目编译文件的大小,只能从代码层面去进行一些优化,如规范代码,合理的使用组合、继承等设计模式。对于资源文件,如图片我们可以进行压缩后在集成。下面就是如何利用python对项目中的使用的图片进行检测并实现在线压缩、上传等功能。
正确引入 Python
Python 库本身就支持 C 直接调用,只要我们能正确的引入即可。git上查找了下,果然已经有人给我们造好了轮子点这个链接,作者已经把对应版本 python 压缩成 zip 并上传,可直接下载解压使用,当然我们也可以使用提供的 makefile 脚本自行编译。同时作者还提供了一个快速创建iOS项目的模版点这个链接以下是记录使用过程中的一些问题。
模版项目的安装
下载作者提供的模版项目到本地点这个链接,查看作者提供的README.rst 文件,两个步骤快速生成模版项目(我是使用自己手动创建的项目)
step1 执行 $ pip install cookiecutter 安装 cookiecutter 插件
step2 执行 $ cookiecutter https://github.com/pybee/Python-iOS-template --checkout 3.7 安装指定版本的python
生成的项目的整体目录结构如下所示
3.8 版本 'cpython/initconfig.h' file not found 错误
目前最新的Python版本已经是3.8了,本着使用最新版本的原则,下载了3.8版本,解压后引入工程,
编译项目出现如下错误:
通过错误提示和头部标识,我们发现这个文件不能直接被引用使用,在看下所有的 cpython 目录下文件,除了initconfig.h 文件其他都有 this header file must not be included directly 标识,既然不能直接 included 直接干掉整个 cpython 目录除 initconfig.h 的所有文件。再次编译发现错误如下
在查看python headers 目录下的所有文件,发现还有外层还有一个 pystate.h 文件,里面有一个对 cpython 目录除 pystate.h 引用,但是刚刚已经被我们删了
删除也无法解决问题,说明该方式不对。查看了下issues里面的问题列表,也没有发现该问题和解决方法,既然高版本不行,只能使用3.7了。
3.7 版本
替换项目中的python相关文件,直接编译项目这次更离谱22个错误
查看错误会发现这些报错都是Python目录的 Resource/lib 文件下的文件,直接把该文件目录删除再次运行项目,这次运行成功了。完整的项目截图如下所示
ps记得引入 libz.tdb 和 libsqlite3.tdb 两个模块。
代码尝试 No module named 'encodings' 错误
直接将模版项目中main文件的代码粘贴复制到自己项目中,运行项目这次出现了如下错误
错误是越来越多了,分析下错误提示发现是sqlite3库问题,检查下项目中如下的配置位置,发现是libz.tdb 和 libsqlite3.tdb 两个模块没有被导入,按下图正确导入
再次运行项目,项目运行起来,但是新的错误又出现了
python在初始化的时候失败了没有找到 encodings 模块,想到前面删除的 Resource/lib 文件目录,那么还是我们对模块引入的方式有问题,从网上找了下 iOS 工程中调用Python方法,看到这篇文章有关于Home路径的设置,下载了作者提供的代码,发现可以把该模块制作成 bundle模块引入项目,并修改原代码中关于 python_home 项目终于正确的运行起来了,修改删除无用的代码,并创建测试 main.py 文件,控制台成功打印出日志
完整的 main.m 代码
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "Python.h"
#include <dlfcn.h>
int main(int argc, char *argv[]) {
int ret = 0;
unsigned int I;
NSString *tmp_path;
NSString *python_home;
NSString *python_path;
wchar_t *wpython_home;
const char* nslog_script;
const char* main_script;
wchar_t** python_argv;
@autoreleasepool {
NSString * resourcePath = [[NSBundle mainBundle] resourcePath];
// Special environment to prefer .pyo; also, don't write bytecode
// because the process will not have write permissions on the device.
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);
wpython_home = Py_DecodeLocale([python_home UTF8String], NULL);
Py_SetPythonHome(wpython_home);
// Set the PYTHONPATH
python_path = [NSString stringWithFormat:@"PYTHONPATH=%@/Library/Application Support/com.example.jddd/app:%@/Library/Application Support/com.example.jddd/app_packages", resourcePath, resourcePath, nil];
NSLog(@"PYTHONPATH is: %@", python_path);
putenv((char *)[python_path UTF8String]);
// iOS provides a specific directory for temp files.
tmp_path = [NSString stringWithFormat:@"TMP=%@/tmp", resourcePath, nil];
putenv((char *)[tmp_path UTF8String]);
NSLog(@"Initializing Python runtime...");
Py_Initialize();
main_script = [
[[NSBundle mainBundle] pathForResource:@"main"
ofType:@"py"] cStringUsingEncoding:NSUTF8StringEncoding];
if (main_script == NULL) {
NSLog(@"Unable to locate jddd main module file.");
exit(-1);
}
// If other modules are using threads, we need to initialize them.
PyEval_InitThreads();
@try {
// Start the main.py script
NSLog(@"Running '%s'...", main_script);
FILE* fd = fopen(main_script, "r");
if (fd == NULL) {
ret = 1;
NSLog(@"Unable to open '%s'; abort.", main_script);
} else {
ret = PyRun_SimpleFileEx(fd, main_script, 1);
fclose(fd);
if (ret != 0) {
NSLog(@"Application quit abnormally!");
} else {
// In a normal iOS application, the following line is what
// actually runs the application. It requires that the
// Objective-C runtime environment has a class named
// "PythonAppDelegate". This project doesn't define
// one, because Objective-C bridging isn't something
// Python does out of the box. You'll need to use
// a library like Rubicon-ObjC [1], Pyobjus [2] or
// PyObjC [3] if you want to run an *actual* iOS app.
// [1] http://pybee.org/rubicon
// [2] http://pyobjus.readthedocs.org/
// [3] https://pythonhosted.org/pyobjc/
UIApplicationMain(argc, argv, nil, @"PythonAppDelegate");
}
}
}
@catch (NSException *exception) {
NSLog(@"Python runtime error: %@", [exception reason]);
}
@finally {
Py_Finalize();
}
NSLog(@"Leaving...");
}
exit(ret);
return ret;
}
以上内容参考以下文章