美文网首页iOS开发iOS开发
在iOS项目中集成python

在iOS项目中集成python

作者: 万年老参 | 来源:发表于2023-06-11 17:22 被阅读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使用演示视频)。)
    首先介绍两个库:

    • 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的))

    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环境下路径引用等出了问题。

    相关文章

      网友评论

        本文标题:在iOS项目中集成python

        本文链接:https://www.haomeiwen.com/subject/bgygedtx.html