目前此方法构建的iOS程序,无法上传苹果服务器!!!,原因是.io文件不被允许打进程序包内
目的:在iOS开发项目中集成python,python3的标准库有很多好用的方法,集成后可以在iOS代码中使用。并可以调用自定义python方法
以python3.10为例:
1.准备工作:mac安装python环境及IDE:
官方下载地址:https://www.python.org/downloads/macos/
选择自己需要的版本下载安装即可
安装后在终端中输入:python3 可以查看是否安装成功
IDE我用的是PyCharm免费版,官方下载地址:https://www.jetbrains.com/pycharm/download/#section=mac
下载后安装。创建个测试程序:
截屏2023-06-07 16.09.21.png
测试代码:
# find the difference between two texts
# tested with Python24 vegaseat 6/2/2005
text1 = """The
World's
is Shortest
on
Books:
Human
"""
text2 = """The
World's
Shortest
Books:
Human
"""
import difflib
# create a list of lines in text1
text1Lines = text1.splitlines(1)
print ("Lines of text1:")
for line in text1Lines:
print (line)
print
# dito for text2
text2Lines = text2.splitlines(1)
print ("Lines of text2:")
for line in text2Lines:
print (line)
print
diffInstance = difflib.Differ()
diffList = list(diffInstance.compare(text1Lines, text2Lines))
print( '-'*50)
print ("Lines different in text1 from text2:")
for line in diffList:
if line[0] == '-':
print (line)
2.iOS开发集成Python步骤
(参考资料来自(youtube)How to embed a Python interpreter in an iOS app - presented by Łukasz Langa(Python-Apple-support使用演示视频)。)
首先介绍两个库:
- Python-Apple-support:个人理解这个库是对Python标准库做了包装,使其可以集成在macOS,watchOS,iOS的app中,通过C的方式来实现在app开发中使用python。
-
PythonKit:Swift 与 Python 交互的库
swift项目集成在集成Python-Apple-support后,使得swift可以调用python,原本这个库使用在MacOS环境下的,如果我们希望用在iOS上,需要做一些处理,下文有讲到。有兴趣的可以在这里查看相关资料(youtube)Setting up PythonKit: Python Interoperability in Swift Part 1
1. Python-Apple-support:
下载对应版本项目到本地下载的版本号一定要和 本地的Python是同一个版本,很重要,否则会出现一些奇奇怪怪的问题(未验证)。
在终端cd进入下载的Python-Apple-support文件夹目录使用以下命令:
make
或
make iOS /macOS/watchOS
可以生成对应的库(make会同时生成iOS,macOS,watchOS的库。make iOS则会生成iOS的库,(但是同时也会生成macOS的))
生成过程比较花时间,耐心等待。。。。。
很花时间。。。
已经20分钟了,还没好。。。
终于好了
make后的文件路径:
文件路径.png
在support文件夹下能找到我们需要的东西:
Python.xcframework,python-stdlib(python标准组件?),platform-site(配置及寻址?)
1,将Python.xcframework集成到swift工程中(集成后注意target-General中确认Embed为Do Not Embed)
2,将platform-site和python-stdlib文件夹添加到我们的swift工程中。
3,添加一个module.modulemap文件,(告诉xcode需要链接python库内的所有文件),可以在xcode中file-newFile,创建一个Empty文件,内容如下:
module Python {
umbrella header "Python.h"
export *
link "Python"
}
在finder中找到此文件,修改后缀名为modulemap,(修改完后打开看看把多余的内容删除掉),然后将此文件放到我们工程里的Python.xcframework中(右键点击Python.xcframework,点击show in finder),放在如下位置(如果希望支持模拟器,需要复制一份在模拟器的文件夹下也放置一份):
未命名.png
4,添加依赖库:在swift工程-targets-Build Phases-Link Binary With Libraries中添加Libz,libsqlite(这里理论上如果我们确定使用的python组件没有使用到这两个库,不添加应该也可以)
到这一步,理论上我们就可以在我们的iOS项目中用C的方式来使用Python库了。swift工程需要通过OC桥接方式来调用,然而我们有更好的选择:PythonKit。
2.PythonKit:
这个库可以让我们在swift中直接调用Python组件,原本是用在MacOS环境下的,设置路径后可以用于iOS。
1,将PythonKit集成到工程中(支持cocoapods,Swift_Package方式)我使用的是Package集成.
2,指定python库的路径及初始化,(一般在app启动时,或者在调用python时保证初始化过了就行):
import Python
//省略无关部分//
//初始化python
guard let stdLibPath = Bundle.main.path(forResource: "python-stdlib", ofType: nil) else { return }
guard let libDynloadPath = Bundle.main.path(forResource: "python-stdlib/lib-dynload", ofType: nil) else { return }
setenv("PYTHONHOME", stdLibPath, 1)
setenv("PYTHONPATH", "\(stdLibPath):\(libDynloadPath)", 1)
Py_Initialize()
// we now have a Python interpreter ready to be used
这里python-stdlib就是我们前面集成到项目中的Python-Apple-support的文件夹路径,我们自定义的.py文件也可以放到这里。看资料说不设置路径的话,是可以用于macOS_APP开发的,但是我们是iOS开发所以需要 指定路径,指向我们包内的py单元。
3.调用:
以下是我自定义的一个.py文件,其中使用到了python的标准库,集成后可以通过swift来调用。
如果想直接使用python的标准库,只需要知道.py文件的名称和方法名,按照示例去调用即可。
首先我自己定义了一个文件:diffTool.py,将此文件放在python-stdlib文件夹内,内容如下:
import difflib
print ("++++++++++++")
def diffTwoString(str1,str2):
diffInstance = difflib.Differ()
diffList = list(diffInstance.compare(str1, str2))
for line in diffList:
# if line[0] == '-':
print(line)
return("pythonWork")
在swift代码中调用:
//python工具类:
import Foundation
import Python
import PythonKit
class PythonMake{
//app启动时调用设置和初始化。
class func buildPython(){
guard let stdLibPath = Bundle.main.path(forResource: "python-stdlib", ofType: nil) else { return }
setenv("PYTHONHOME", stdLibPath, 1)
setenv("PYTHONPATH", stdLibPath, 1)
Py_Initialize()
}
//比较两个字符串,
class func testPythonDiff(text1:String,text2:String){
//调用python
let difflib = Python.import("diffTool") //导入对应的py文件
let result = difflib.diffTwoString(text1,text2) //调用python方法并传参
}
}
python返回类型在Swift中看是PythonObject类型,大部分情况下用的时候需要强转为swift类型使用。注意swift为强类型语言,转的时候需要明确指定类型(尤其是字典,数组),不然会出现指向python的错误。
//举例:result是python返回的值
//当result是字符串时:
let str = String(result)
//当result是字符串数组时:
let ary : [String] = Array(result)
math库好像调用有问题,参考:https://stackoverflow.com/questions/74390910/modulenotfounderror-no-module-named-encodings-while-running-python-in-ios 中的回答部分。
添加Script将so文件签名:
添加script.png名字:Sign Python Binary Modules
内容:
set -e
echo "Signing as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)"
find "$CODESIGNING_FOLDER_PATH/python-stdlib/lib-dynload" -name "*.so" -exec /usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der {} \;
完成以上步骤后,在debug环境下应该可以在模拟器或真机上运行并成功调用python代码了。
4.存在的问题:
4.1release下无法使用:
此问题通过设置:Setting ->Enable Testability -> release -Yes得以解决,但是原理不清楚,纯属凑巧解决掉的
目前debug下可以正常调用。
但是testfliight或release模式下,
设置 pythonPath 和Py_Initialize()时没有崩溃:
guard let stdLibPath = Bundle.main.path(forResource: "python-stdlib", ofType: nil) else { return }
guard let stdDynloadPath = Bundle.main.path(forResource: "python-stdlib/lib-dynload", ofType: nil) else { return }
setenv("PYTHONHOME", stdLibPath, 1)
setenv("PYTHONPATH", "(stdLibPath):(stdDynloadPath)", 1)
Py_Initialize()
//以上内容会被执行不会导致崩溃,并且debug下后续可以正常调用,路径应该是有效的
然后调用python会崩溃:
//调用自定义的python文件
let py = Python //这里会闪退
let a = py.import("diffTool")
let test = a.diff_modelTest("123123123","123123123")
经检查,闪退在PythonKit的这里:
PythonKit-> PythonLibrary+Symbols:Py_IncRef(pointer)
看起来似乎是个指针错误,在Xcode里看到了很多0x0000000000000000
比如:
sharedMethodDefinition UnsafeMutablePointer<PythonKit.PyMethodDef> 0x0000000000000000
如果有解决办法请告知。
4.2打包问题:
但我在发布到appstore时,又遇到了问题, 报错不允许将so文件打包进来
Asset validation failed
Invalid bundle structure. The “xxxxxx.app/python-stdlib/lib-dynload/math.cpython-39-iphoneos.so” binary file is not permitted. Your app cannot contain standalone executables or libraries, other than a valid CFBundleExecutable of supported bundles. For details, visit: https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle (ID: 74345377-b2bb-466b-bc4d-e3edb8a8f429)
python-stdlib/lib-dynload路径下所有的io文件和, python-stdlib/config-3.10-iphoneos, python-stdlib/config-3.10-iphonesimulator.arm64 , python-stdlib/config-3.10-iphonesimulator.x86_64下的文件,都报错了。
这个好像解决不了,:
https://github.com/beeware/Python-Apple-support/issues/176
4.3math库连接问题:
我的swift版本是5.8,Python版本是3.10.11,python-apple-suppot库选择的3.10版本。然而当我集成后,在代码中调用python的随机数函数时,会报错random库链接不到math库,并且我在python-stdlib内也的确没找到math.py文件。然后我通过pyCharm中找到了math文件,然后show in finder,发现路径是在一个缓存路径下:
math路径
并且在pyCharm中调用random是没问题的。估计是在iOS环境下路径引用等出了问题。
网友评论