我所在的公司对项目编译后的大小和资源文件有严格的要求,每次集成发版对于包体积的增量都是有严格的控制,因此,如何减少包体积是每一个研发都需要考虑的。
对于包体积大小我们可以从资源文件和编码来控制,如何减少项目编译文件的大小,只能从代码层面去进行一些优化,如规范代码,合理的使用组合、继承等设计模式。对于资源文件,如图片我们可以进行压缩后在集成。下面就是如何利用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 "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!"