自己的一个独立 macOS 项目,是关于PDF文件操作的相关功用,上线了两年多了,一向运用的 Swift 和 APPKit 原生进行的功用开发。

macOS Swift 原生项目集成 Python3 运行环境

链接如下:Easy PDF

有用户提出需求,期望能够开发 PDF 文件转 office 文件的功用。其时看到这个需求时,查找了一下材料,感觉难度有点大,耗时耗力就没理会。后来无意中发现了一个 Python 库,pdf2docx,能够完美实现 PDF 文件转 Word 文件的功用,但无法的是这么凶猛的功用是 Python 实现的。后来就冒出想法,可不能够在 Swift 项目中去集成 Python 开发的功用库呢,然后开始全网查找各种材料…

通过这次功用开发实现,发现 Python 调配 Swift 能够做许多之前没想过的一些功用,还需求待研究。

先说终究成果

成功在 Swift macOS 项目中集成了 Python3 环境,并运用 pip3 装置依靠库,通过 Swift 去调用 Python 第三方库的函数,而且通过了苹果的 App Store 的审阅.

一、需求预备的材料

  • Python 环境装置,Mac 系统没有 Python3 环境,建议去 Python 官网下载装置包来装置,便利简略,主要是后边可能需求重复卸载和装置,Python 官网下载地址,我是下载的 Python 3.11.0

macOS Swift 原生项目集成 Python3 运行环境

  • 支撑 Swift 和 macOS 的 Python 库, Python-Apple-support,下载后解压后有两个文件夹,python-stdlibPython.xcframework下载的版别号必定要和上面下载的 Python3 装置包是同一个版别,很重要,否则会呈现一些奇古怪怪的问题。 这个 Python 库是通过这个项目编译生成的,有兴趣的能够深入研究一下,一个运用 Python 来开发 macOS 应用的开源项目 briefcase

  • PythonKit:Swift 调用 Python 函数的库,没有太多的挑选

二、项目中集成 Python3 解释器

  1. 建一个新项目,项目中先把 PythonKit 搞进去,用 Swift Package Manager 或 cocoapods 都能够,推荐运用 Swift Package Manager,项目看起来更加清爽。

macOS Swift 原生项目集成 Python3 运行环境

  1. 将 python-stdlib和Python.xcframework 文件拖入文件夹,选中 Copy items if neededCreate folder references,很重要。

macOS Swift 原生项目集成 Python3 运行环境

macOS Swift 原生项目集成 Python3 运行环境

  1. 查看 Python.xcframework 设置为 Do Not Embed

macOS Swift 原生项目集成 Python3 运行环境

  1. 增加SystemConfiguration.Framework

macOS Swift 原生项目集成 Python3 运行环境

macOS Swift 原生项目集成 Python3 运行环境

  1. 查看 python-stdlib

macOS Swift 原生项目集成 Python3 运行环境

  1. 创建 Python 头文件 新建一个文件 module.modulemap,内容如下,将这个文件保存到项目中的Python.xcframework/macos_arm64_x86_64/Headers 中
module Python {
  umbrella header "Python.h"
  export *
  link "Python"
}
  1. 增加一个 Run Script,内容如下,取消 Based on dependency analysis
set -e
echo "Signing as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)"
find "$CODESIGNING_FOLDER_PATH/Contents/Resources/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 {} \;

macOS Swift 原生项目集成 Python3 运行环境

macOS Swift 原生项目集成 Python3 运行环境

  1. 查看 Python3 的运转环境是否现已预备好,能够先把电脑本地装置的 Python3 环境删掉测验一下,看项目中的 Python3 是否是咱们自己下载的版别。
import Cocoa
import PythonKit
import Python
class ViewController: NSViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
        // 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()
    let sys = Python.import("sys")
        print("Python \(sys.version_info.major).\(sys.version_info.minor)")
    print("Python Version: \(sys.version)")
    print("Python Encoding: \(sys.getdefaultencoding().upper())")
  }
}

打印出 Python 版别,阐明运转环境现已预备好了

macOS Swift 原生项目集成 Python3 运行环境

  1. 运用 pip3 装置第三方依靠,毕竟运用第三方依靠库才是咱们的终究目的。 前提条件: Mac 电脑的本地 Python3 环境需求装置好,pip3也得装置,装置很简略,点击下载好的 python-3.11.0-macos11.pkg 直接装置就能够了,pip3 的装置办法就不说了,也没啥难点。

装置 Python 依靠库命令,以装置 pdf2docx 为例

pip3 install pdf2docx -t /Users/Desktop/EmbeddedPython/EmbeddedPython/python-stdlib

/Users/Desktop/EmbeddedPython/EmbeddedPython/python-stdlib 是项目中 python-stdlib 的途径,装置完成后 python-stdlib 文件夹中就会有第三方依靠库了。

  1. Python 第三方依靠库 pdf2docx 的办法调用。将转换的文件保存到下载文件夹,项目中需求设置沙盒权限。

macOS Swift 原生项目集成 Python3 运行环境

代码示例:

import Cocoa
import Python
import PythonKit
class ViewController: NSViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        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()
        // 测验文件,可修改的 PDF 文件
        let pdfFilePath = Bundle.main.path(forResource: "test", ofType: "pdf")
        // 将转换的 docx 文件保存到下载文件夹
        var url = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0] as URL
        url = url.appendingPathComponent("test.docx")
        let docFilePath = url.path
        // 导入 pdf2docx 模块
        let pdf2docx = Python.import("pdf2docx")
        // 创建 Converter
        let converter = pdf2docx.Converter(pdfFilePath)
        // 调用 convert 办法
        converter.convert(docFilePath)
    }
}

pdf2docx 在 Python 中的运用办法,能够和上面 Swift 的调用办法进行对比

from pdf2docx import Converter
pdf_file = '/path/to/test.pdf'
docx_file = 'path/to/test.docx'
# pdf 转 docx
cv = Converter(pdf_file)
cv.convert(docx_file)      # 一切页面
  1. PDF 文件转 Word 的效果,仍是很满意的。

macOS Swift 原生项目集成 Python3 运行环境

三、关于 App Store 的审阅

第一次兴致勃勃的打包提交审阅,app的装置包由 5MB 变为了 120MB,也是没办法的工作,毕竟项目中集成了 Python3 运转环境和一些有必要的 Python 第三方依靠库。

  1. 第一次被拒,回复说项目中有许多弃用的API,这个是主动触发了苹果机审扫描到的一些标识,开始我也没发现,联系了 Python-Apple-support 的开源作者,才发现了问题,直接给苹果回复,项目中没有运用弃用办法和私有API,他会重新审阅。

  2. 第2次被拒,弃用API的问题解决了,第2次苹果回复了一大堆 Python 里边的办法名,都是带有下划线前缀的办法,被判定为是私有办法,这个不能忍啊,明显也是机器扫描的成果,也是直接回复苹果,描述一下这些办法仅仅 Python 函数的命名规矩,然后把 Python-Apple-support 的开源链接扔给苹果,让他自己去查看这些办法是否是私有API。

  3. 通过两次回复,苹果终究给审阅通过了

四、关于打包的bug

我的项目呈现了一个不知道的bug,当项目本地运转的时分,debug 和 release 都是没有问题的,可是 Archive 打包就会呈现古怪的问题,打包挑选 debug,导出的 app 是正常运转的,如果挑选 release 打包,运转则会溃散,我猜测可能和 Python 的动态库签名有关,在 release 环境下打包,有些装备被 Xcode 打包优化修改了,现在还没找到出问题的当地,所以我就在 debug 形式下 Archive 提包了,也不影响运用。

macOS Swift 原生项目集成 Python3 运行环境

五、跋文

这个当地需求留意一个问题,当电脑上装置了 Python3 运转环境,然后项目中没有集成 Python3环境,这个时分项目代码中不设置 Python 途径,也是能够正常运转的,Xcode会去查找电脑本地的 Python3 解释器。可是对于需求上线的项目来说,你不能要求用户在本地装置 Python3 的运转环境,所以无法,只能把 Python3 的解释器集成到项目里边去。