照片分享界面不显示拍摄地点,导出的文件却仍可能带着坐标。做 iOS 图片导出功能时,验收对象应当是接收方最终拿到的文件,而不是应用内的预览图。在云端 Mac 上准备一张含 GPS 信息的测试照片,就能把这项隐私要求变成重复执行的文件检查。
先确定导出边界
一条常见路径是:选择照片、编辑或缩放、生成临时文件,再把文件交给分享流程。每一步都可能改变元数据的处理方式。只检查选择器返回的图片,无法证明最终文件已清理;只检查分享前的缩略图也不行。
测试夹具要由团队自行制作,并确认原文件确实包含 GPS 字典。不要拿真实用户照片做持续集成样本,也不要把带坐标的夹具放进公开仓库。可以把夹具存放在受控的测试资产目录,记录预期格式与像素尺寸;测试结束后清理生成的文件。
本文的通过条件是「最终文件没有 GPS 字典」,不是「导出文件与原图逐字节相同」。重新编码后,文件哈希通常会变化。
用 ImageIO 生成并复查文件
下面的 Swift 脚本读取输入图像,解码后按原文件类型重新编码。它只显式传递方向,不把输入文件的元数据字典整体交给输出端。脚本随后重新打开输出文件,检查 GPS 字典、尺寸、方向和格式。先在隔离目录保存为 redact.swift:
import Foundation
import ImageIO
import CoreGraphics
guard CommandLine.arguments.count == 3 else {
fatalError("Usage: redact input output")
}
let input = URL(fileURLWithPath: CommandLine.arguments[1])
let output = URL(fileURLWithPath: CommandLine.arguments[2])
guard input.standardizedFileURL != output.standardizedFileURL else {
fatalError("Input and output must differ")
}
guard let source = CGImageSourceCreateWithURL(input as CFURL, nil),
let type = CGImageSourceGetType(source),
let image = CGImageSourceCreateImageAtIndex(source, 0, nil),
let original = CGImageSourceCopyPropertiesAtIndex(source, 0, nil)
as? [CFString: Any],
let destination = CGImageDestinationCreateWithURL(
output as CFURL, type, 1, nil
) else {
fatalError("Cannot read input or create output")
}
let orientation = original[kCGImagePropertyOrientation] ?? 1
let options: [CFString: Any] = [
kCGImagePropertyOrientation: orientation
]
CGImageDestinationAddImage(destination, image, options as CFDictionary)
guard CGImageDestinationFinalize(destination),
let result = CGImageSourceCreateWithURL(output as CFURL, nil),
let finalImage = CGImageSourceCreateImageAtIndex(result, 0, nil),
let metadata = CGImageSourceCopyPropertiesAtIndex(result, 0, nil)
as? [CFString: Any] else {
fatalError("Cannot verify output")
}
guard metadata[kCGImagePropertyGPSDictionary] == nil,
CGImageSourceGetType(result) == type,
finalImage.width == image.width,
finalImage.height == image.height,
String(describing: metadata[kCGImagePropertyOrientation] ?? 1)
== String(describing: orientation) else {
fatalError("Export verification failed")
}
print("GPS removed; format, dimensions and orientation checked")
在云端 Mac 的终端运行:
swiftc -framework Foundation -framework ImageIO \
-framework CoreGraphics redact.swift -o redact
./redact fixtures/with-gps.jpg output/without-gps.jpg
先创建 output 目录,并用已核实含 GPS 字典的 JPEG 替换示例输入。运行成功只说明这一次输出通过检查。正式应用若有裁剪、压缩或分享扩展路径,应把检查接在各路径产生的最终文件之后,而不是以脚本代替应用的实际导出实现。
给结果加上反向用例
只有「清理后通过」不足以证明断言有效。用同一套读取逻辑检查输入夹具:它必须检出 GPS 字典;再检查输出:字典必须不存在。如果输入本来就没有坐标,两边都通过的测试没有鉴别力。为避免日志泄露,不要打印坐标值或完整元数据。
还要保留一份不经清理的复制文件作为反向用例,确认检查器会拒绝它。复制时直接复制文件字节,不要经过会改写元数据的图片工具。这样既验证清理步骤,也验证验收器没有把「读取失败」误当成「没有 GPS」。
重新编码后还要看什么
移除定位信息不等于图片功能全部正常。解码并重新编码可能改变有损压缩质量、色彩表现或其他非定位元数据。尺寸相同,也不代表像素逐点相同。把不同来源的照片放进回归集,至少覆盖竖拍、横拍及不同方向标记;人工查看最终文件是否旋转、偏色或明显失真。
| 检查项 | 通过条件 |
|---|---|
| 定位信息 | 最终文件不存在 GPS 字典 |
| 图像尺寸 | 与该导出路径预期的宽高一致 |
| 显示方向 | 按输出方向标记显示正确 |
| 文件格式 | 接收方拿到的是预期格式 |
| 读取失败 | 测试失败,不按「无 GPS」处理 |
示例脚本要求尺寸与输入一致,适合不缩放的导出路径。若产品会裁剪或压缩,应把尺寸断言改成该路径的明确预期。若需要保留色彩配置或特定版权字段,也应为这些字段分别写测试;不要为了保留它们而把原始元数据字典原样复制过去。
接入云端 Mac 的验收流程
将夹具、应用实际生成的文件和检查脚本分开放置。流水线先执行导出,再读取最终交付路径上的文件;文件不存在、无法解码或元数据读取失败,都应直接失败。并行任务使用各自的输出目录,避免上一次运行留下的文件制造假通过。
最后做一次人工核对:从应用完成真实分享或保存操作,用接收方拿到的文件复检,而不只看中间缓存。这能发现「编辑预览已清理、分享时却重新引用原文件」一类路径错误。通过后删除临时导出物;测试报告只记录是否存在 GPS 字典、尺寸、方向及失败步骤,不记录坐标。这样验收结论既能复现,也不会在排查日志里再次暴露要清理的信息。
常见问题
界面不显示拍摄地点,是否就说明导出的照片没有 GPS 信息?
不能。界面预览与导出文件是两条路径,必须读取最终导出文件的元数据,确认其中不存在 GPS 字典。
移除 GPS 信息后,能否只比较导出文件的哈希值?
不能。重新编码通常会改变文件字节;应分别检查 GPS 字典、图像尺寸、方向和实际显示效果。
用独享物理 Mac mini 验证下一步
VMCommit 提供 M4 独享物理 Mac mini,支持按天、周、月或季租用。选择机型与节点后,可在控制台查看实时可用性。