iOS开发iOS开发

在iOS项目中集成python

2023-06-11  本文已影响0人  万年老参

目前此方法构建的iOS程序,无法上传苹果服务器!!!,原因是.io文件不被允许打进程序包内
目的:在iOS开发项目中集成python,python3的标准库有很多好用的方法,集成后可以在iOS代码中使用。并可以调用自定义python方法
以python3.10为例:

1.准备工作:mac安装python环境及IDE:

官方下载地址:https://www.python.org/downloads/macos/
选择自己需要的版本下载安装即可

点击Downloadxxxxx后,下载安装.png
安装后在终端中输入: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使用演示视频)。)
首先介绍两个库:


1. Python-Apple-support:

下载对应版本项目到本地下载的版本号一定要和 本地的Python是同一个版本,很重要,否则会出现一些奇奇怪怪的问题(未验证)
在终端cd进入下载的Python-Apple-support文件夹目录使用以下命令:
make

make iOS /macOS/watchOS
可以生成对应的库(make会同时生成iOS,macOS,watchOS的库。make iOS则会生成iOS的库,(但是同时也会生成macOS的))

make指令.png
生成过程比较花时间,耐心等待。。。。。
很花时间。。。
已经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环境下路径引用等出了问题。

上一篇 下一篇

猜你喜欢

热点阅读