ARTICLE · 1046048
TypePHP 开发苹果 iPhone/iOS Native App
用 PHP 开发 iOS App,听起来有点离谱。但 TypePHP 确实可以做到:界面布局、状态、点击逻辑全部用 PHP 写,编译成一个 arm64 的原生可执行文件,装到 iPhone 上直接运行。
先看效果,下面是运行在真机上的演示视频:
标题、说明文字、状态文字,原生 UILabel;「点击计数」「重置」「English」三个按钮,原生 UIButton;点「点击计数」状态文字会累加,点「English」整个界面切换中英文。
这些控件全部由 UIKit 渲染,手感、字体、深色模式都是系统原生的。而业务代码一行 Objective-C / Swift 都没有,全在 PHP 里。
一、分工:PHP 写逻辑,Objective-C++ 只做一层薄桥
TypePHP 的开发方式和普通 PHP 项目一样,只是多了一个约定:你需要什么原生能力,就在桥接文件里声明一个函数。
示例 App 只用到 6 个原生函数:
ui_app_run() | |
ui_create_window() | |
ui_add_label() | |
ui_add_button() | |
ui_set_control_text() | |
ui_show_window() |
这层桥本身是 Objective-C++(.mm),属于你的 App,代码量很小。剩下的事情——界面怎么排、按钮点了做什么、状态怎么变——全部是 PHP。
另外,TypePHP 编译后是机器指令,App 里不会包含任何 PHP 源码。
二、代码长什么样
官方示例在 examples/apple-native/:
examples/apple-native/├── php-src/│ ├── application.php # 界面与交互逻辑(PHP)│ └── ios-main.php # iOS 入口(PHP)├── ios-src/│ ├── uikit_bridge.stub.php # 声明给 PHP 用的原生函数│ └── uikit_bridge.mm # UIKit 实现(Objective-C++)├── ios.yml # 编译配置└── package-ios-app.sh # 打包签名脚本ios-main.php 是 iOS 的入口,一共十几行:
<?php/** iOS 启动完成后,由桥回调 */functiontypephp_application_did_launch(): void{ HelloApplication::build('UIKit'); ui_show_window();}/** 每个控件被点击时,由桥回调,参数是控件 ID */functiontypephp_application_control_activated(int $controlId): void{ HelloApplication::handleEvent($controlId);}functionmain(): void{ ui_app_run('TypePHP iOS Hello');}界面部分就是普通的 PHP,buildPhoneLayout() 里描述布局:
privatestaticfunctionbuildPhoneLayout(): void{ ui_create_window('TypePHP Native Hello', 390, 844);self::$titleLabel = ui_add_label('', 24, 672, 342, 76, 30, true);self::$descriptionLabel = ui_add_label('', 30, 544, 330, 104, 16, false);self::$statusLabel = ui_add_label('', 30, 454, 330, 58, 18, true);self::$countButton = ui_add_button('', 30, 354, 330, 58, 1);self::$resetButton = ui_add_button('', 30, 280, 158, 52, 2);self::$languageButton = ui_add_button('', 202, 280, 158, 52, 3);}点击逻辑也是普通 PHP:
publicstaticfunctionhandleEvent(int $controlId): void{if ($controlId === self::$languageButton) {self::$isChinese = !self::$isChinese;self::renderText();return; }if ($controlId === self::$resetButton) {self::$clickCount = 0;self::renderStatus();return; }if ($controlId === self::$countButton) {self::$clickCount++;self::renderStatus(); }}ui_add_label() / ui_add_button() 会返回一个自增的控件 ID,桥在按钮被点击时把这个 ID 回调给 typephp_application_control_activated()。所以在 PHP 里,事件处理就是一次普通的方法调用。
三、编译
在 TypePHP 仓库根目录执行,用 ios.yml 作为配置:
export PHPX_HOME=/path/to/phpxphp bin/tpc.php examples/apple-native/ios.yml --no-progress产物是 examples/apple-native/typephp_ios_hello,可以确认一下:
$ file typephp_ios_hellotypephp_ios_hello: Mach-O 64-bit executable arm64$ otool -l typephp_ios_hello | grep -A3 LC_BUILD_VERSION cmd LC_BUILD_VERSION platform 2 # iOS minos 15.0ios.yml 里跟 iOS 相关的其实就几行:
target-platform:arm64-apple-ios15.0# 交叉编译目标cpp-compiler:xcrun--sdkiphoneosclang++# 用 Xcode 的 iPhoneOS 工具链cxx-flags:--fobjc-arc# 桥接文件开启 ARC--miphoneos-version-min=15.0ld-flags:--frameworkUIKit--frameworkFoundation--frameworkCoreGraphics四、打包与签名
iOS 必须签名才能安装,先准备好描述文件(.mobileprovision)和开发者证书,然后:
export TYPEPHP_IOS_PROVISIONING_PROFILE=/path/to/profile.mobileprovisionexport TYPEPHP_IOS_CODE_SIGN_IDENTITY='Apple Development: Your Name (TEAMID)'# 描述文件的 Bundle ID 和示例不一致时,用这个覆盖export TYPEPHP_IOS_BUNDLE_IDENTIFIER='your.provisioned.bundle.identifier'sh examples/apple-native/package-ios-app.sh脚本会组装 .app、拷贝图标、做签名并校验,最后输出:
Created and signed .../dist/TypePHP iOS Hello.app五、装到 iPhone 上运行
先列出设备,拿到 <device-id>:
xcrun devicectl list devices然后安装:
xcrun devicectl device install app \ --device <device-id> \'examples/apple-native/dist/TypePHP iOS Hello.app'装完之后直接启动(也可以直接在手机上点图标):
xcrun devicectl device process launch --device <device-id> swoole.typephp手机上就会出现文章开头那个原生界面。第一次安装后需要在「设置 → 通用 → VPN 与设备管理」里信任你的开发者证书,之后就不用再操作了。
六、改成你自己的界面
理解了上面三个函数,就可以把示例改成任何你想要的样子:
坐标是左上角为原点的逻辑画布,示例用 390 x 844(iPhone 竖屏)。桥负责换算成 UIKit 坐标,并随屏幕尺寸等比缩放,所以不用自己处理像素和刘海。ui_add_label($text, $x, $y, $w, $h, $fontSize, $bold):文字控件。ui_add_button($title, $x, $y, $w, $h, $style):按钮,style为1(实心蓝)/2(浅蓝)/3(灰色)。ui_set_control_text($id, $text):更新文字,用来做实时状态展示。按钮点击统一通过 typephp_application_control_activated($id)回调,用 ID 区分即可。
比如想加一个「打招呼」按钮,只要在布局里加一行,再在事件里加一个分支:
// 布局self::$helloButton = ui_add_button('打个招呼', 30, 200, 330, 52, 2);// 事件if ($controlId === self::$helloButton) { ui_set_control_text(self::$statusLabel, '你好,TypePHP!');}需要 UIKit 之外的能力(定位、相机、网络状态……)时,在 uikit_bridge.mm 里加一个 php_ 前缀的函数,并在 .stub.php 里声明,PHP 侧就能直接调用。
七、几点说明
只支持真机。 目前 iOS 目标只提供 iphoneosarm64,没有模拟器目标,必须签名。桥是手写的,不是自动绑定。 与 Android/JNI 那种反射桥不同,Apple 平台每个 App 自己维护 .mm文件——好处是完全可控、体积小,代价是用到什么系统能力就写哪几个函数。描述文件会过期。 个人开发者账号签发的开发描述文件有效期较短(通常 7 天),过期后用 Xcode 重新签发一次即可。 构建环境:macOS + 完整版 Xcode + 对应版本的全静态 libphp.a/libphpx.a(PHPX_HOME/ios/iphoneos-arm64)。
同一套 application.php,换个入口和桥接文件,就能编译成 macOS 的 AppKit 应用;Android 侧也有对应的 arm64 原生方案。一份 PHP 业务代码,落到桌面和移动端原生 UI 上——这是 TypePHP 在服务端之外的一个新方向。
技术社区
TypePHP 由识沃科技(Swoole 团队)主导研发。欢迎添加识沃客服微信,加入技术交流群,与开发者直接交流、获取最新版本与构建指南。
