ios脚本开发

iOS免越狱脚本怎么写?从环境判断到第一个脚本跑通的完整步骤

面向写脚本的人的执行视角教程:先讲清免越狱到底指什么、三条链路对应哪个事件模块,再给可直接抄的脚本骨架(含官方示例的 autoServiceStart 环境检查写法),接着按环境/交互/节点/输入/图色五类列出常用函数与参数,然后是坐标怎么定、等待怎么写、失败怎么查、多台怎么下发,每步都标注官方函数签名与返回值约定。

约 21 分钟更新于

一把椅子,三个小时

认识一个人,做电商客服外包,想把「每天早上把二十几个后台的订单状态过一遍」这件事交出去。

他先看了官方文档,发现函数一大堆,不知道从哪下手;又听说「免越狱、USB HID、代理模式、蓝牙」这些词,更糊涂了——四个词都听过,但不知道跟写脚本有什么关系。

那三天他的状态是这样的:第一天装环境,第二天试着看懂一个例子,第三天在纠结「到底该用哪个事件模块」。真正开始写代码,已经是第四天。

这篇就是把那四天压缩成一篇。顺序按「一个脚本从零到跑起来」的真实路径走,每个环节给出官方文档里的真实函数名和参数,不绕概念。写 iOS免越狱脚本 最耗时间的从来不是逻辑,是搞清楚每个函数到底返回什么。

苹果群控执行脚本时的界面,可以按设备查看每一步的执行状态

一、先想清楚:脚本里的「免越狱」到底指什么

很多人一上来就纠结「免越狱能不能做 XX」,其实先要把这个词的含义定下来。

免越狱不等于「不装东西」 ,它指的是不改动系统。iOS 的沙箱、签名机制、系统完整性都不动,你的手机还是一台能正常升级、保修有效的手机。自动化之所以能做到这一点,是因为它只需要两件事:

  1. 看到屏幕(截图或投屏)
  2. 送出输入(点击、滑动、打字)

这两件事都有系统本身就开放的路子,不需要打开系统。所以「免越狱脚本」的完整含义是:在不改动系统的前提下,程序化地完成「看屏幕 + 送输入」这件事。

理解这一点之后,很多问题就自然消解了——比如「免越狱脚本能不能改系统设置」,答案是不能,因为它压根没碰系统;而绝大多数业务场景要的也不是改系统,是「按时把一批固定操作做完」。写 iOS自动化脚本 的门槛之所以比想象中低,也是这个道理:你要的能力就两样,别的都不用碰。


二、三条链路,脚本写法差在哪

这是新手最容易卡住的地方。同一个业务逻辑,在不同链路上要调用不同的事件模块:

链路 事件模块 系统要求 额外要准备
代理模式 agentEvent iOS 13+ 签名 IPA
USB HID usbHidEvent iOS 17+ 一根数据线
蓝牙 BLE bleEvent iOS 17+ ESP32 开发板
OTG HID otgEvent iOS 17+ 开发板 + 转接头

iOS 17 是个硬门槛。官方文档的原话是:USB HID 和蓝牙 BLE 都要求 iOS 17 及以上,低于 iOS 17 只能用代理模式。手上是老机器的话,先确认版本再动手,能省掉大量无效尝试。

顺带说清免签名这件事:代理模式要签名 IPA,而 USB HID、蓝牙、OTG 这三条硬件链路都不需要签名——不用买证书,也不用管证书什么时候过期。这是很多人选硬件链路的第一个理由。

好消息是四个模块的方法名是一致的:

四个模块都有的方法:
  clickPoint(x, y)          点击坐标
  press(x, y, duration)     长按
  doubleClickPoint(x, y)    双击
  swipeToPoint(x1,y1,x2,y2,duration)   滑动
  multiTouch(fingers)       多点触控
  touchDown / touchMove / touchUp      按下/移动/抬起(自己拼手势)
  systemKey(...)            系统键(Home、音量等)
  keyPress / keyPressChar   按键与字符输入

也就是说:业务逻辑写一遍,换链路只改模块名。设计脚本时这一点值得利用——把点击操作包一层函数,链路切换只改那一处:

// 把事件模块包一层,换链路只改这里
const EV = usbHidEvent;   // 代理模式换成 agentEvent,蓝牙换 bleEvent,OTG 换 otgEvent

function tap(x, y) {
    return EV.clickPoint(x, y);
}

各自还有一点专属接口,用到时再查文档即可(比如蓝牙的 bleEvent.openSerial、setWifiInfo,OTG 的 otgEvent.scanOtgDevice、isOtgConnect)。


三、环境准备:动手前要确认的四件事

  1. 手机系统版本 —— 决定你能走哪条链路(见上表)
  2. 自动化服务状态 —— 中控里能看到服务是否就绪,这是脚本能不能跑的前提
  3. 授权类型 —— 执行脚本要 USB 设备授权,投屏要 USB 投屏授权,两者不是同一个。买错授权是这个环节最常见的坑
  4. 屏幕尺寸 —— 后面写坐标全靠它

前三项在装完环境后自然就绪,第四项要专门说一下:坐标和投屏画面、截图是同一套像素坐标,所以要么在投屏里量,要么用节点自带的坐标。USB HID 链路要显式设置屏幕尺寸(usbHidEvent.setScreenSize(w, h)),不设的话坐标换算没有依据,点哪偏哪。


四、第一个脚本的骨架

不管哪条链路,开头的结构是固定的。下面这份可以直接抄:

function main() {
    logd("检查自动化环境...");
    if (!autoServiceStart(3)) {
        logw("自动化服务启动失败,无法执行脚本");
        exit();
        return;
    }

    // 节点抓取参数,脚本开头设一次就够
    setFetchNodeParam({
        "labelFilter": "2",       // 只取有 label 的节点
        "visibleFilter": "2",     // 只取 visible = true 的
        "maxDepth": "20",         // 层级越少越快,建议 1-500
        "excludedAttributes": "visible,selected,enable,accessible"
    });

    // ... 业务代码

    logd("脚本执行结束");
}

// 环境检查:官方示例里给出的写法,直接用
function autoServiceStart(time) {
    for (let i = 0; i < time; i++) {
        if (isServiceOk()) {
            return true;
        }
        let started = startEnv();
        logd("第" + (i + 1) + "次启动服务结果: " + started);
        if (isServiceOk()) {
            return true;
        }
    }
    return isServiceOk();
}

main();

关于 autoServiceStart :它不是系统函数,是官方示例里给出的辅助写法。思路很朴素——循环调 startEnv(),再用 isServiceOk() 检查,最多试 time 次。这比自己写一个 sleep(5000) 然后赌服务起来了要可靠得多,建议直接沿用。

关于 setFetchNodeParam :这个参数表对性能影响比你想象的大。文档里说 excludedAttributes「可以增加抓取速度」——设备一多,少抓几个用不上的属性,累积下来差异很明显。maxDepth 建议 1–500,越小越快。

如果走 USB HID ,开头换成 HID 自己的会话启动:

let r = usbHidEvent.sessionStart(true);   // 参数是「是否尝试增强兼容模式」,默认 true
if (!(r == null || r === "")) {
    logw("开启 HID 会话失败: " + r);
    return;
}
r = usbHidEvent.setScreenSize(1170, 2532);  // 坐标换算依赖这一步

返回值约定要记住:null 或空字符串代表成功,其它字符串是错误信息。这是全文所有代码判断的基础,也是新手最容易搞反的地方。


五、常用函数清单:五类够用

文档里函数很多,但真正每天用的就那么几十个。按用途分五类:

1. 环境与服务

函数 作用
isServiceOk() 自动化服务是否就绪
startEnv() / closeEnv() 启动 / 停止运行环境
isDeviceOnline() 设备是否在线
isDeviceAuthOk() / getDeviceAuth() 授权状态
sleep(毫秒) 暂停执行
logd / logi / logw / loge 四档日志输出

2. 交互(点击与手势)

四个事件模块的方法名一致,上面已列出。代理模式下还有 agentEvent.touchDown / touchMove / touchUp 用来拼自定义手势。

3. 节点(找元素,最常用的一类)

节点查询是链式写法,不是传配置对象:

// 按文字找(正则匹配),最多等 5 秒
let nd = labelMatch("发布").getOneNodeInfo(5000);
// 按 id 找
let nd2 = id("com.example.app:id/btn").getOneNodeInfo(3000);
// 拿全部匹配
let list = labelMatch(".*订单.*").getNodeInfo(5000);

可用的筛选器:id / idMatch、label / labelMatch、name / nameMatch、type / typeMatch、value / valueMatch、xpath,以及 visible、enable 这类属性。

节点对象上能拿到:id、xpath、label、name、type、value、bounds、index、depth、visible、enable,方法有 clickCenter、clickRandom、parent、child、allChildren、siblings、previousSiblings、nextSiblings。

每次查询前先 releaseNode() 再 lockNode() ——官方示例固定这么写。先释放上一轮数据,再锁定当前界面,否则可能读到上一屏的节点。

4. 输入文字

代理模式走输入法模块:

if (imeApi.isOk()) {
    let r = imeApi.input("要输入的内容");
    logd("输入结果: " + r);   // 空字符串代表输入失败,非空是实际输入的内容
}
imeApi.setClipboard("#话题标签");   // 长文本建议走剪贴板
imeApi.paste();
imeApi.dismiss();                  // 收起键盘,别漏

imeApi.dismiss() 这一步最容易漏。输入完不收键盘,键盘会一直占着下半屏,把下面的按钮挡住,点击落到键盘上,而失败不报错、只表现为「操作没生效」。

USB HID 链路是另一套:usbHidEvent.inputText()(走剪贴板粘贴)、usbHidEvent.typeText()(键盘打字,遇非英文自动粘贴)。

5. 图色、截图与设备

函数 作用
image.captureFullScreen() 全屏截图
image.findImage(...) / findImageByColor(...) 找图 / 偏色找图
image.cmpColor(...) 比色
device.getDeviceInfo() 设备信息(取实时屏幕宽高用它)
device.getDeviceId() / getDeviceName() / getModel() / getOSVersion() 设备标识与版本
device.getBattery() / isCharging() 电量
device.applist() 已安装应用列表

节点查不到的时候,图色是兜底手段;需要留证或排查的时候,截图是标配。


六、坐标怎么写才不会点歪

坐标问题占了新手失败原因的一半,值得单独讲。

优先用节点自己的坐标,不要手量。 节点对象带 bounds,直接算中心点:

let nd = labelMatch("确认").getOneNodeInfo(5000);
if (nd) {
    clickPoint(nd.bounds.centerX(), nd.bounds.centerY());
}

这样写的好处是换机型、换分辨率都不用改——坐标是从当前设备实时算出来的。

确实要写硬坐标时,注意三件事:

  1. 坐标基准是投屏画面/截图的像素坐标,不是逻辑分辨率
  2. 分辨率变化或横竖屏切换后必须重设屏幕尺寸,USB HID 是 setScreenSize(w, h),蓝牙和 OTG 也有各自同名接口
  3. 竖屏和横屏是两套坐标系,混用时需要用 adjustScreenOrientation 切换,别指望自动识别

能不用坐标就别用。 一条经验:如果某个位置只能用坐标,说明那里大概率是长度不固定的列表或图标,这种地方正是最容易被版本更新搞坏的。


七、等元素,还是等时间

这是脚本稳不稳的分水岭。

固定等待(sleep) 的问题在于它跟现实脱节:网络快的时候白等,网络慢的时候还没加载完就开始点。短期凑合可以,长期一定出问题。

正确做法是把等待交给查询函数 ——getOneNodeInfo(timeout) 里的超时参数就是天然的等待:传 15000 就是「最多等 15 秒」,元素提前出现会立刻返回。

// 不好的写法
sleep(3000);
clickPoint(585, 2280);

// 好的写法
let btn = labelMatch("下一步").getOneNodeInfo(15000);
if (btn) {
    clickPoint(btn.bounds.centerX(), btn.bounds.centerY());
} else {
    logw("15 秒内没出现,可能界面变了");
}

需要等「元素消失」时,用循环查 + sleep 小步轮询(每次 300–500ms,最多几十次),比一次性睡很久好。


八、调试:三层工具配合用

脚本跑不通的时候,按这个顺序查,比盯代码快得多。

第一层:日志。 在关键步骤前打一行 logd("步骤名"),成本一行,收益是「失败时知道停在哪」。这比事后猜要省事得多。

第二层:截图。 失败分支里加一句 image.captureFullScreen()。绝大多数失败是「停在了意料之外的页面」——弹窗、更新提示、登录过期。看到截图基本就定位了。

第三层:中控的实时日志和执行历史。 前者看单台设备的逐步输出,后者看多设备的结果汇总。执行历史里能直接看到哪台失败、失败在哪一步,是排查多设备问题的入口。

实时日志界面,可以查看每台设备每一步的执行输出

一个实用习惯:新脚本先在单台设备上跑,第一步只做最短的动作。比如只做「打开 App 并确认首页出现了」,跑通再往下加。一上来写完整流程,失败时你无法判断是环境问题、定位问题还是逻辑问题。


九、从一台到多台:脚本怎么下发

单台跑通之后,下一步是铺开。中控里的做法很直接:

  1. 把编译好的 iec 文件放进中控左下角的脚本目录,右键 → 刷新
  2. 选中目标设备(或先在分组栏分好组),右键脚本 → 执行脚本

分组这件事在设备超过十台之后就从“锦上添花”变成“必须” :几十台塞在一屏里,你根本看不出哪台失败了;分成几组,问题一眼可见。

前面提过的那条边界再强调一次:执行脚本要 USB 设备授权,投屏要 USB 投屏授权。另外,同一台设备同一时间只跑一个任务,所以批量提速靠的是设备数量,不是单台并发。

这也是 iOS群控 和“多写几个脚本”的分界线:脚本解决“一件事怎么做”,群控解决“同一件事怎么在几十台上同时做对、并且知道哪台没做对”。

需要和外部系统对接时(比如让电商后台自动触发),可以走开放接口,参数和返回值都是 JSON,用 Python、Node、cURL 都能调。这块展开是一篇单独的篇幅,先记住入口在中控的 8019 端口即可。


十、进阶:脚本写稳之后的三件事

工程化。 脚本一长,纯 JS 单文件就难维护了。文档里给了 npm 和 TypeScript 的支持方式,可以按工程结构组织代码。

保护。 脚本要交给别人用的时候,源码就是资产。文档里有专门的 JS 混淆说明,可以在发布前处理一遍。

参数化。 把账号、关键词、时间点这些经常改的东西抽成配置,而不是写死在流程里。这一条决定了你的脚本能用多久——改版一次就要重读源码的脚本,没人愿意维护。


十一、按场景推荐怎么配(想跳过过程就看这节)

如果你不想读完上面十节,这一节就是结论。下面五种情况基本覆盖了绝大多数团队,对应的都是同一套中控里的不同入口。

你的情况 推荐方案 为什么
iOS 13–16,或流程复杂、需要精确识别界面元素 推荐 EasyClick 代理模式 功能最全,有节点选择器,调试最方便
iOS 17+,想省掉签名成本,界面比较稳定 推荐 EasyClick 加 USB HID 一根数据线,免签名免开发板,风控也更温和
iOS 17+,风控压力大,或者设备分散摆放 推荐 EasyClick 加蓝牙 HID 风控最友好,不受线长限制
完全不想写代码 推荐 EasyClick AI 智能体 中文对话描述任务,或拖拽出工作流
要让自己的系统来触发设备操作 推荐 EasyClick 开放接口 8019 端口,HTTP 加 JSON,任何语言都能接

这五种情况有一个共同点:它们共用同一个中控、同一套授权、同一批设备。所以实际落地时很少是“只选一种”——更常见的组合是先用代理模式把流程逻辑跑通,风控有压力了切到 HID,临时性的任务交给 AI 智能体,固定的批处理用脚本加定时。

一句话总结:选平台比选单点方案重要。单点方案决定某一件具体的事怎么做,平台决定这几条链路和入口能不能共用一套设备与授权管理。上面这张表里的五种情况,在 EasyClick 里对应的是同一个平台的不同入口,不是五套要分别学的工具。

十二、常见问题

「免越狱」是不是不能做需要登录的操作?

能做。登录属于应用层操作,跟越狱无关。要注意的是登录态会过期,脚本里得处理「发现要求登录 → 走一遍登录流程」这条分支。

要不要一开始就上最复杂的方案?

不要。三条链路各自能做到哪一步不一样,但业务逻辑是同一套。先用最容易跑通的那条把逻辑验证一遍,再按风控和规模要求换链路——换的时候只改事件模块。

脚本里的等待该设多长?

给足上限,不要卡得太紧。getOneNodeInfo(15000) 不会真的等 15 秒,元素出现就返回;但如果上限设 3000,网络稍慢就失败。用户不差这十几秒,但会介意脚本莫名失败。

为什么同样的脚本在另一台设备上就不行?

三个常见原因:分辨率不同但坐标写死了、系统版本不同导致界面差异、那台设备的自动化服务或授权状态不对。按这个顺序查。


写脚本这件事,门槛其实不在语言。那位做客服外包的朋友最后是这么开始的:先只写「打开后台 → 等订单列表出现 → 截图」,跑通之后再逐步加动作。四个小时后他有了第一个能用的脚本;那四天里花掉的三天,全都耗在「不知道该从哪开始」上。

先把环境和服务判断写对,再挑最短的一个动作跑通,剩下的就是加步骤——顺序对了,就不会再卡四天。iPhone自动化 这件事的入门成本,比大多数人预想的低,难的是把它做稳。

想先看具体某条链路怎么落地,可以读iOS免越狱自动化三条链路怎么选;USB HID 的原理和 API 细节,看苹果群控技术拆解:USB HID 的原理与 API 完整教程;一篇完整的实战代码参考,可以读苹果群控发短视频脚本怎么写。

iEasyClick:手机自动化脚本与苹果群控方案站,覆盖安卓免 root、iOS 免越狱、鸿蒙 Next,提供脚本开发教程、中控投屏与批量运维方案。官网 ieasyclick.net

想要真实跑起来?

本文介绍的方案均可基于 EasyClick 能力在 iEasyClick 落地。官网提供完整文档、开发工具与自动化产品,免费体验。

访问 iEasyClick 官网 →