Python

7. iOS에서 Python 사용하기

저자:

Russell Keith-Magee (2024-03)

iOS에서 실행되는 파이썬은 데스크톱 플랫폼에서의 파이썬과 다릅니다. 데스크톱 플랫폼에서는 파이썬이 일반적으로 컴퓨터의 모든 사용자가 사용할 수 있는 시스템 리소스로 설치됩니다. 그러면 사용자는 python 실행 파일을 실행하고 대화형 프롬프트에서 명령을 입력하거나, 파이썬 스크립트를 실행하여 파이썬과 상호작용합니다.

iOS에서는 시스템 리소스로 설치한다는 개념이 없습니다. 소프트웨어 배포의 유일한 단위는 “앱”입니다. 또한 python 실행 파일을 실행하거나 파이썬 REPL과 상호작용할 수 있는 콘솔도 없습니다.

결과적으로 iOS에서 파이썬을 사용할 수 있는 유일한 방법은 임베디드 모드로 사용하는 것입니다. 즉, 네이티브 iOS 애플리케이션을 작성하고 libPython 을 사용하여 파이썬 인터프리터를 내장하며, Python embedding API 를 사용하여 파이썬 코드를 호출하는 방식입니다. 전체 파이썬 인터프리터, 표준 라이브러리, 그리고 모든 파이썬 코드는 iOS App Store를 통해 배포될 수 있는 독립형 번들로 패키징됩니다.

파이썬으로 iOS 앱을 작성하는 것을 처음 시도해 보려 한다면, BeeWare 및 ` Kivy <https://kivy.org>`__와 같은 프로젝트가 훨씬 더 접근하기 쉬운 사용자 경험을 제공할 것입니다. 이러한 프로젝트는 iOS 프로젝트를 실행하는 것과 관련된 복잡성을 관리해 주므로 파이썬 코드 자체에만 집중하면 됩니다.

7.1. iOS에서의 파이썬 런타임

7.1.1. iOS 버전 호환성

최소 지원 iOS 버전은 configure--host 옵션을 사용하여 컴파일 타임에 지정됩니다. 기본적으로 iOS용으로 컴파일될 때 파이썬은 최소 지원 iOS 버전 13.0으로 컴파일됩니다. 다른 최소 iOS 버전을 사용하려면 --host 인자로 버전 번호를 포함하십시오. 예를 들어, --host=arm64-apple-ios15.4-simulator 는 배포 대상이 15.4인 ARM64 시뮬레이터 빌드를 컴파일합니다.

7.1.2. 플랫폼 식별

iOS에서 실행할 때 sys.platformios 로 보고됩니다. 이 값은 앱이 시뮬레이터에서 실행되는지 실제 기기에서 실행되는지 여부와 관계없이 iPhone 또는 iPad에서 반환됩니다.

iOS 버전, 기기 모델 및 시뮬레이터 여부를 포함한 특정 런타임 환경에 대한 정보는 platform.ios_ver() 를 사용하여 확인할 수 있습니다. platform.system() 은 기기에 따라 iOS 또는 iPadOS 를 반환합니다.

os.uname() 은 커널 수준의 상세 정보를 보고하며, Darwin 이라는 이름을 반환합니다.

7.1.3. 표준 라이브러리 가용성

Python 표준 라이브러리는 iOS에서 몇 가지 눈에 띄는 누락 및 제한 사항이 있습니다. 자세한 내용은 iOS용 API 가용성 가이드 를 참조하십시오.

7.1.4. 바이너리 확장 모듈

플랫폼으로서 iOS의 주요 차이점 중 하나는 App Store 배포 시 애플리케이션 패키징에 대한 엄격한 요구 사항이 있다는 것입니다. 이러한 요구 사항 중 하나는 바이너리 확장 모듈의 배포 방식을 규정합니다.

iOS App Store는 iOS 앱 내의 모든 바이너리 모듈이 적절한 메타데이터와 함께 프레임워크에 포함되어야 하며, 패키징된 앱의 Frameworks 폴더에 저장된 동적 라이브러리여야 한다고 요구합니다. 프레임워크당 하나의 바이너리만 존재할 수 있으며, Frameworks 폴더 외부에는 실행 가능한 바이너리 자료가 없어야 합니다.

이는 바이너리 확장 모듈이 sys.path``의 어느 위치에서나 로드되는 것을 허용하는 일반적인 Python 방식과 충돌합니다. App Store 정책을 준수하려면 iOS 프로젝트는 모든 Python 패키지를 후처리하여 ``.so 바이너리 모듈을 적절한 메타데이터와 서명이 포함된 개별 독립 프레임워크로 변환해야 합니다. 이러한 후처리 방법에 대한 자세한 내용은 프로젝트에 Python 추가하기 가이드를 참조하십시오.

Python이 새 위치에서 바이너리를 찾을 수 있도록, sys.path``에 있는 원래의 ``.so 파일은 .fwork 파일로 대체됩니다. 이 파일은 앱 번들과 비교하여 프레임워크 바이너리의 상대적 위치를 포함하는 텍스트 파일입니다. 프레임워크가 원본 위치를 다시 참조할 수 있도록, 프레임워크에는 앱 번들과 비교하여 .fwork 파일의 위치를 포함하는 .origin 파일이 포함되어야 합니다.

예를 들어, _whiz 가 바이너리 모듈 sources/foo/bar/_whiz.abi3.so 로 구현되고 sources 가 애플리케이션 번들을 기준으로 sys.path 에 등록된 위치인 경우의 from foo.bar import _whiz 임포트를 고려해 보겠습니다. 이 모듈은 반드시 Frameworks/foo.bar._whiz.framework/foo.bar._whiz (모듈의 전체 임포트 경로에서 프레임워크 이름을 생성)로 배포되어야 하며, .framework 디렉터리에 있는 Info.plist 파일이 해당 바이너리를 프레임워크로 식별해야 합니다. foo.bar._whiz 모듈은 원래 위치에 sources/foo/bar/_whiz.abi3.fwork 마커 파일로 표시되며, 이 파일에는 Frameworks/foo.bar._whiz/foo.bar._whiz 경로가 포함됩니다. 또한 프레임워크는 .fwork 파일의 경로를 포함하는 Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin 을 포함해야 합니다.

iOS에서 실행될 때, Python 인터프리터는 .fwork 파일을 읽고 임포트할 수 있는 AppleFrameworkLoader 를 설치합니다. 한 번 임포트되면 바이너리 모듈의 __file__ 속성은 .fwork 파일의 위치를 보고합니다. 하지만 로드된 모듈에 대한 ModuleSpecorigin 을 프레임워크 폴더 내 바이너리의 위치로 보고합니다.

7.1.5. 컴파일러 스텁 바이너리

Xcode는 iOS용 명시적 컴파일러를 노출하지 않으며, 대신 전체 컴파일러 경로로 해석되는 xcrun 스크립트를 사용합니다 (예: iPhone 기기용 clang``을 가져오기 위한 ``xcrun --sdk iphoneos clang). 그러나 이 스크립트를 사용하는 데는 두 가지 문제가 있습니다.

  • xcrun 의 출력에 기기 종속적인 경로가 포함되어 사용자 간에 공유할 수 없는 sysconfig 모듈이 생성될 수 있으며,

  • 공백을 포함하는 CC/CPP/LD/AR 정의가 생성됩니다. 많은 C 에코시스템 도구들은 명령 줄의 첫 번째 공백에서 분리하여 컴파일러 실행 파일의 경로를 가져올 수 있다고 가정하지만, xcrun 을 사용할 때는 그렇지 않습니다.

이러한 문제를 방지하기 위해 Python은 이 도구들에 대한 스텁을 제공합니다. 이 스텁은 기본 xcrun 도구를 래핑하는 셸 스크립트로, 컴파일된 iOS 프레임워크와 함께 배포되는 bin 폴더에 포함됩니다. 이 스크립트들은 재배치 가능하며 항상 적절한 로컬 시스템 경로로 해석됩니다. 프레임워크와 함께 제공되는 bin 폴더에 이러한 스크립트를 포함함으로써, sysconfig 모듈의 내용이 최종 사용자가 자신의 모듈을 컴파일하는 데 유용하게 활용됩니다. iOS용 서드파티 Python 모듈을 컴파일할 때 이 스텁 바이너리들이 PATH에 포함되어 있는지 확인해야 합니다.

7.2. iOS에 Python 설치하기

7.2.1. iOS 앱 빌드 도구

iOS를 위한 빌드에는 Apple의 Xcode 도구가 필요합니다. 최신 안정 버전의 Xcode를 사용하는 것을 강력히 권장합니다. Apple은 구형 macOS 버전을 위한 Xcode를 유지 관리하지 않으므로, 이를 위해 가장 최근(또는 두 번째로 최근) 출시된 macOS 버전이 필요합니다. Xcode Command Line Tools만으로는 iOS 개발이 불가능하며, 전체 Xcode 설치가 필요합니다.

iOS 시뮬레이터에서 코드를 실행하려면 iOS 시뮬레이터 플랫폼을 설치해야 합니다. Xcode를 처음 실행할 때 iOS 시뮬레이터 플랫폼을 선택하라는 안내가 표시됩니다. 또는 Xcode 설정 패널의 Platforms 탭에서 선택하여 추가할 수도 있습니다.

7.2.2. iOS 프로젝트에 Python 추가하기

Python은 Swift 또는 Objective-C를 사용하여 모든 iOS 프로젝트에 추가할 수 있습니다. 다음 예제는 Objective-C를 사용합니다. Swift를 사용하는 경우, PythonKit 와 같은 라이브러리가 도움이 될 수 있습니다.

iOS Xcode 프로젝트에 Python을 추가하려면:

  1. Python XCFramework``를 빌드하거나 확보하십시오. Python ``XCFramework``를 빌드하는 방법에 대한 자세한 내용은 :source:`Apple/iOS/README.md` (CPython 소스 배포본 내)의 지침을 참조하십시오. 최소한 ``arm64-apple-ios``와 ``arm64-apple-ios-simulator 또는 x86_64-apple-ios-simulator 중 하나를 지원하는 빌드가 필요합니다.

  2. XCframework 를 iOS 프로젝트로 드래그하십시오. 다음 지침에서는 XCframework 가 프로젝트 루트에 위치한다고 가정하지만, 필요한 경우 경로를 조정하여 원하는 다른 위치에 사용할 수 있습니다.

  3. 애플리케이션 코드를 Xcode 프로젝트 내의 폴더로 추가하십시오. 다음 지침에서는 사용자 코드가 프로젝트 루트의 app 이라는 이름의 폴더에 있다고 가정하며, 필요에 따라 경로를 조정하여 다른 위치를 사용할 수도 있습니다. 이 폴더가 앱 타겟과 연결되어 있는지 확인하십시오.

  4. Xcode 프로젝트의 루트 노드를 선택한 다음 나타나는 사이드바에서 타겟 이름을 선택하여 앱 타겟을 선택하십시오.

  5. “General” 설정의 “Frameworks, Libraries and Embedded Content” 아래에 Python.xcframework 를 추가하고 “Embed & Sign”을 선택하십시오.

  6. “Build Settings” 탭에서 다음 내용을 수정하십시오.

    • 빌드 옵션

      • User Script Sandboxing: No

      • Enable Testability: Yes

    • 검색 경로

      • Framework Search Paths: $(PROJECT_DIR)

      • Header Search Paths: "$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"

    • Apple Clang - Warnings - All languages

      • Quoted Include In Framework Header: No

  7. Python 표준 라이브러리와 자체 Python 바이너리 의존성을 처리하는 빌드 단계를 추가하십시오. “Build Phases” 탭에서 “Embed Frameworks” 단계 에, “Copy Bundle Resources” 단계 에 새로운 “Run Script” 빌드 단계를 추가합니다. 해당 단계의 이름을 “Process Python libraries”로 지정하고, “Based on dependency analysis” 체크박스를 해제한 후 스크립트 내용을 다음과 같이 설정하십시오.

    set -e
    source $PROJECT_DIR/Python.xcframework/build/build_utils.sh
    install_python Python.xcframework app
    

    XCframework를 프로젝트 루트 이외의 다른 곳에 배치한 경우 첫 번째 인수의 경로를 수정하십시오.

  8. 임베디드 모드에서 Python 인터프리터를 초기화하고 사용하기 위한 Objective-C 코드를 추가하십시오. 이때 다음 사항을 확인해야 합니다.

    • UTF-8 모드 (PyPreConfig.utf8_mode)가 활성화됨;

    • Buffered stdio (PyConfig.buffered_stdio)가 비활성화됨;

    • 바이트코드 쓰기 (PyConfig.write_bytecode)가 비활성화됨;

    • 시그널 핸들러 (PyConfig.install_signal_handlers)가 활성화됨;

    • 시스템 로깅 (PyConfig.use_system_logger)이 활성화됨 (선택 사항이지만 강력히 권장되며, 기본적으로 활성화되어 있습니다);

    • 인터프리터용 PYTHONHOME`이 번들의 ``python` 하위 폴더를 가리키도록 설정되어야 하며;

    • 인터프리터용 PYTHONPATH 는 다음을 포함해야 합니다.

      • 앱 번들의 python/lib/python3.X 하위 폴더,

      • 앱 번들의 python/lib/python3.X/lib-dynload 하위 폴더, 그리고

      • 앱 번들의 app 하위 폴더를 포함해야 합니다.

    애플리케이션의 번들 위치는 [[NSBundle mainBundle] resourcePath] 를 사용하여 확인 가능합니다.

이 지침의 7단계와 8단계는 순수 Python 애플리케이션 코드가 포함된 app 이라는 단일 폴더가 있다고 가정합니다. 앱에 서드파티 바이너리 모듈이 있는 경우 다음의 추가 단계가 필요할 수 있습니다.

  • 서드파티 바이너리가 포함된 모든 폴더가 앱 타겟과 연관되어 있거나 7단계의 일부로 명시적으로 복사되도록 해야 합니다. 또한 7단계에서는 특정 빌드가 목표로 하는 플랫폼에 적합하지 않은 바이너리를 제거해야 합니다(예: 시뮬레이터용 앱을 빌드하는 경우 기기용 바이너리 삭제).

  • 서드파티 패키지를 위해 별도의 폴더를 사용하는 경우, 해당 폴더가 7단계의 install_python 호출 끝에 추가되고 8단계의 PYTHONPATH 구성에도 포함되도록 하십시오.

  • 서드파티 패키지가 포함된 폴더 중 .pth 파일을 포함하는 곳이 있다면, 이를 PYTHONPATHsys.path 에 직접 추가하는 대신 사이트 디렉터리 로 추가해야 합니다(site.addsitedir() 사용).

7.2.3. Python 패키지 테스트하기

CPython 소스 트리에는 iOS 시뮬레이터에서 CPython 테스트 스위트를 실행하는 데 사용되는 테스트베드 프로젝트 가 포함되어 있습니다. 이 테스트베드는 여러분의 Python 라이브러리 테스트 스위트를 iOS에서 실행하기 위한 테스트베드 프로젝트로도 사용할 수 있습니다.

iOS XCFramework를 빌드하거나 확보한 후(자세한 내용은 Apple/iOS/README.md 참조), Python iOS 테스트베드 프로젝트를 복제하십시오. XCframework를 구축하기 위해 Apple 빌드 스크립트를 사용했다면 다음을 실행할 수 있습니다.

$ python cross-build/iOS/testbed clone --app <path/to/module1> --app <path/to/module2> app-testbed

또는 본인만의 XCframework를 가져온 경우, 다음을 실행하여:

$ python Apple/testbed clone --platform iOS --framework <path/to/Python.xcframework> --app <path/to/module1> --app <path/to/module2> app-testbed

--app 플래그로 지정된 모든 폴더는 복제된 테스트베드 프로젝트로 복사됩니다. 결과물인 테스트베드는 app-testbed 폴더에 생성됩니다. 이 예시에서 module1``과 ``module2``는 실행 임포트 가능한 모듈이 됩니다. 프로젝트에 추가 의존성이 있는 경우, ``app-testbed/Testbed/app_packages 폴더에 설치할 수 있습니다(pip install --target app-testbed/Testbed/app_packages 또는 유사한 명령 사용).

그 후 app-testbed 폴더를 사용하여 앱의 테스트 스위트를 실행할 수 있습니다. 예를 들어, module1.tests 가 테스트 스위트의 진입점이라면 다음과 같이 실행할 수 있습니다.

$ python app-testbed run -- module1.tests

이는 데스크톱용 Python 빌드에서 python -m module1.tests 를 실행하는 것과 동일합니다. -- 이후의 모든 인수는 데스크톱 장치에서 python -m 에 전달되는 인자와 마찬가지로 테스트베드에 전달됩니다.

또한 다음을 실행하여 테스트베드 프로젝트를 Xcode에서 열 수 있습니다.

$ open app-testbed/iOSTestbed.xcodeproj

이를 통해 디버깅을 위해 Xcode의 전체 도구 세트를 사용할 수 있습니다.

테스트 스위트 실행에 사용되는 인수는 테스트 플랜의 일부로 정의됩니다. 테스트 플랜을 수정하려면 프로젝트 트리에서 테스트 플랜 노드(루트 노드의 첫 번째 자식)를 선택하고 “Configurations” 탭을 선택하십시오. “Arguments Passed On Launch” 값을 수정하여 테스트 인자를 변경할 수 있습니다.

테스트 플랜은 병렬 테스트를 비활성화하고, 디버거 설정을 제공하기 위해 Testbed.lldbinit 파일을 사용하는 것을 명시합니다. 기본 디버거 구성은 SIGINT, SIGUSR1, SIGUSR2, 및 SIGXFSZ 시그널에 대한 자동 중단점(breakpoint)을 비활성화합니다.

7.3. App Store 준수 사항

서드파티 iOS 기기에 앱을 배포하는 유일한 방법은 iOS App Store에 제출하는 것입니다. 배포를 위해 제출된 앱은 Apple의 앱 검토 프로세스를 통과해야 합니다. 이 프로세스에는 제출된 애플리케이션 번들의 문제 코드를 검사하는 일련의 자동화된 검증 규칙이 포함됩니다. 앱이 이러한 검증 단계를 통과할 수 있도록 보장하기 위해 수행해야 하는 몇 가지 단계가 있습니다.

7.3.1. 표준 라이브러리의 호환되지 않는 코드

파이썬 표준 라이브러리에는 이러한 자동화된 규칙을 위반하는 것으로 알려진 코드가 일부 포함되어 있습니다. 이러한 위반 사항은 오탐(false positive)으로 보이지만, 애플의 검토 규칙에 이의를 제기할 수 없으므로 앱이 App Store 심사를 통과하려면 파이썬 표준 라이브러리를 수정해야 합니다.

파이썬 소스 트리에는 App Store 검토 과정에서 문제를 일으키는 것으로 알려진 모든 코드를 제거하는 패치 파일 이 포함되어 있습니다. 이 패치는 iOS용 빌드 시 자동으로 적용됩니다.

7.3.2. 개인정보 보호 매니페스트

2025년 4월, 애플은 특정 서드파티 라이브러리가 개인정보 보호 매니페스트(Privacy Manifest)를 제공해야 한다는 요구사항 <https://developer.apple.com/support/third-party-SDK-requirements>`__을 도입했습니다. 결과적으로 영향을 받는 라이브러리 중 하나를 사용하는 바이너리 모듈이 있는 경우, 해당 라이브러리에 대한 `.xcprivacy`` 파일을 제공해야 합니다. OpenSSL은 이 요구사항의 영향을 받는 라이브러리 중 하나이며, 그 외에도 다른 라이브러리들이 포함됩니다.

만약 사용자가 mymodule.so``라는 이름의 바이너리 모듈을 생성하고 7단계에서 설명한 Xcode 빌드 스크립트를 사용한다면, ``mymodule.so 옆에 mymodule.xcprivacy 파일을 배치할 수 있으며, 이 경우 바이너리 모듈이 프레임워크로 변환될 때 개인정보 보호 매니페스트가 필요한 위치에 설치됩니다.

분실물 보관소