app.plist - Plist 操作 API
读写 macOS Property List 文件。
keyPath 表达
新版(引擎 v1.3.0+)keyPath 支持两种形式:
- 字符串 —— 单一 key 字面值,不做任何切分(含
.也直接当 key 的一部分) - 数组 —— 嵌套路径,元素是 string key 或 number index(Lua 1-based)
为什么不用 "a.b.c" 这种点分隔?因为 plist 的 key 字面值经常含 .(反域名命名约定如 com.apple.WatchKit),dot-split 跟 key 字面值直接冲突。新设计把”一段 key”和”多段嵌套”两件事解耦,更安全。
-- 单一 key(含 dot 也直接 work)
app.plist.getValue(path, "CFBundleIdentifier")
app.plist.getValue(path, "com.apple.developer.networking.wifi-info")
-- 嵌套(数组)
app.plist.getValue(path, {"CFBundleURLTypes", 1, "CFBundleURLSchemes", 1})
-- ^ 第 1 个 dict ^ 第 1 个 scheme(Lua 1-based)
方法
app.plist.read(path)
读取 plist 文件。
参数:
path(string) - plist 文件路径
返回值: table|nil, error
local info = app.plist.read("/Applications/Safari.app/Contents/Info.plist")
if info then
app.log.info("版本: " .. info.CFBundleShortVersionString)
app.log.info("Bundle ID: " .. info.CFBundleIdentifier)
end
app.plist.write(path, data)
写入 plist 文件(XML 格式)。data 内 dict/array 中的 nil/NSNull 会被自动 strip,避免 serialization 失败。
参数:
path(string) - 文件路径data(table) - 要写入的数据
返回值: boolean, error
app.plist.write("/path/to/config.plist", {
AppName = "MyApp",
Version = "1.0.0",
Settings = {
Theme = "dark",
AutoSave = true
}
})
app.plist.getValue(path, keyPath)
读取指定路径的值。
参数:
path(string) - plist 文件路径keyPath(string | table) - 见上方 keyPath 表达
返回值: any|nil, error —— 找不到返回 nil(不是 error)
-- 顶层
local version = app.plist.getValue(infoPath, "CFBundleShortVersionString")
-- 含 dot 的 key(无需转义)
local wifi = app.plist.getValue(entitlements, "com.apple.developer.networking.wifi-info")
-- 嵌套(数组 keyPath,Lua 1-based)
local scheme = app.plist.getValue(infoPath, {"CFBundleURLTypes", 1, "CFBundleURLSchemes", 1})
app.plist.setValue(path, keyPath, value)
设置指定路径的值。value 为 nil 时删除该 key(推荐用 removeValue 更显式)。
参数:
path(string) - plist 文件路径keyPath(string | table) - 见上方 keyPath 表达value(any | nil) - 值
返回值: boolean, error
-- 顶层赋值
app.plist.setValue(configPath, "LastOpened", os.time())
-- 嵌套赋值(中间路径不存在会自动创建)
app.plist.setValue(configPath, {"Settings", "Theme"}, "light")
-- 删除 key(v1.3.0 起真正删除;旧版本会写入 NSNull)
app.plist.setValue(configPath, "OldKey", nil)
app.plist.removeValue(path, keyPath) v1.3.0+
显式删除 key(推荐写法,比 setValue(path, k, nil) 更可读)。
参数:
path(string) - plist 文件路径keyPath(string | table) - 见上方 keyPath 表达
返回值: boolean, error
-- 移除 ipa 的支持设备限制
app.plist.removeValue(infoPath, "UISupportedDevices")
app.plist.removeValue(infoPath, "UIRequiredDeviceCapabilities")
-- 嵌套删除
app.plist.removeValue(configPath, {"Settings", "OldKey"})
app.plist.hasKey(path, keyPath) v1.3.0+
判断 key 是否存在。消除”key 不存在”与”key 值为 nil”的歧义——getValue 都返回 nil,但语义不同。
参数:
path(string) - plist 文件路径keyPath(string | table) - 见上方 keyPath 表达
返回值: boolean, error
if app.plist.hasKey(infoPath, "UISupportedDevices") then
app.plist.removeValue(infoPath, "UISupportedDevices")
end
写入语义(setValue / removeValue / 数组)
中间层
- 缺失的中间层会自动创建(类型由下一段 keyPath 决定:string key → dict,number index → array)。
- 中间层已存在但类型与 keyPath 不符(如用字符串 key 去穿一个数组)→ 报错,不会静默覆盖、不丢数据。
数组
- 只能改写已有下标,或在末尾追加(下标 = 当前长度 + 1,Lua 1-based)。
- 下标越界 → 报错。plist 数组不允许空洞,不会用占位值填充。
Data / Date
setValue/removeValue改某个 key 时,不会损坏文件里其他的 Data / Date 字段(内部按原始类型读改写;只有read/getValue返回给 Lua 时才转成 Base64 / ISO 8601 字符串)。
-- 自动创建中间层
app.plist.setValue(p, {"Settings", "Theme"}, "dark") -- Settings 不存在则自动建
-- 数组末尾追加(已有 2 项 → 下标 3 追加)
app.plist.setValue(p, {"URLSchemes", 3}, "myapp")
-- 越界报错
local ok, err = app.plist.setValue(p, {"URLSchemes", 9}, "x") -- ok = false
-- 类型冲突报错(CFBundleName 是字符串,却当 dict 穿过)
local ok2, err2 = app.plist.setValue(p, {"CFBundleName", "x"}, "v") -- ok2 = false
类型映射
| Plist 类型 | Lua 类型 |
|---|---|
| dict | table(哈希表) |
| array | table(数组,Lua 1-based) |
| string | string |
| integer/real | number |
| true/false | boolean |
| data | string(Base64 编码) |
| date | string(ISO 8601 格式) |
示例
读取应用信息
function MyPlugin:getAppInfo(appPath)
local infoPath = app.path.join(appPath, "Contents/Info.plist")
if not app.path.exists(infoPath) then return nil end
local info = app.plist.read(infoPath)
if not info then return nil end
return {
name = info.CFBundleName or info.CFBundleDisplayName,
version = info.CFBundleShortVersionString,
build = info.CFBundleVersion,
bundleId = info.CFBundleIdentifier,
minOS = info.LSMinimumSystemVersion
}
end
移除 ipa 支持设备限制
function MyPlugin:removeDeviceRestrictions(infoPath)
for _, key in ipairs({"UISupportedDevices", "UIRequiredDeviceCapabilities"}) do
if app.plist.hasKey(infoPath, key) then
app.plist.removeValue(infoPath, key)
app.log.info("Removed " .. key)
end
end
end
修改嵌套配置
-- 切换 Settings.Enabled
local enabled = app.plist.getValue(configPath, {"Settings", "Enabled"})
app.plist.setValue(configPath, {"Settings", "Enabled"}, not enabled)
升级须知(旧插件迁移)
如果你的插件之前用 "a.b.c" 字符串表达嵌套路径(dot-split),需要改成数组:
-- 旧(v1.2.0 及之前,已废弃)
app.plist.getValue(p, "Settings.Theme")
-- 新(v1.3.0+)
app.plist.getValue(p, {"Settings", "Theme"})
如果之前用 setValue(p, k, nil) 期待删除——旧版会写入 NSNull(实际不删),v1.3.0+ 真正删除。推荐改用 removeValue 让语义更显式。