← All courses ← Course home

Lesson 05 of 05

Pass the iOS release checklist

Before you send a LiteRT-LM iOS app to review, check the privacy manifest, duplicate Objective-C classes, large memory maps, and long-context GPU output.

Agent brief (llms.md)

Environment setup

Same setup as lesson 01: LiteRT-LM 0.18.0 (com.google.ai.edge.litertlm:litertlm-android:0.18.0 on Android, the LiteRTLM Swift package on iOS), Xcode 27 or Android Studio, and a real device. Mac scripts run with uv run in the litert-ship project (Python 3.11+).

You also need the Xcode command line tools for nm and otool: xcode-select --install.

1. Privacy manifest for required-reason APIs

Apple asks every app and third-party SDK to declare the "required reason" APIs it uses in a privacy manifest (PrivacyInfo.xcprivacy). Issue #3863 found that the LiteRT-LM iOS binaries (the CLiteRTLM.xcframework for v0.17.1 and the Metal prebuilt dylibs) used these APIs and shipped no manifest:

BinaryCategory found in #3863Reason codes the reporter suggested
CLiteRTLM.frameworkFile Timestamp, System Boot TimeC617.1 (and 3B52.1 if users pick files), 35F9.1
libLiteRtMetalAccelerator.dylibFile Timestamp, User DefaultsC617.1, CA92.1
libLiteRtTopKMetalSampler.dylibUser DefaultsCA92.1

The issue was closed after PR #3910 merged on 10 Oct 2026, which is after v0.18.0 (6 Oct). We could not confirm from the release notes that v0.18.0 includes a manifest. So check the build you ship:

APP=build/MyApp.xcarchive/Products/Applications/MyApp.app
find "$APP" -name 'PrivacyInfo.xcprivacy' -print
for bin in "$APP"/Frameworks/*.framework/* "$APP"/Frameworks/*.dylib; do
  [ -f "$bin" ] || continue
  echo "== $bin"
  nm -u "$bin" 2>/dev/null | grep -E '_(stat|fstat|lstat|mach_absolute_time)$|NSUserDefaults' | sort -u
done

If a binary uses one of these APIs and has no manifest of its own, add the categories to your app's PrivacyInfo.xcprivacy. Pick the reason codes that are true for your app. The table above lists the ones the reporter suggested, not a legal answer.

<key>NSPrivacyAccessedAPITypes</key>
<array>
  <dict>
    <key>NSPrivacyAccessedAPIType</key><string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
    <key>NSPrivacyAccessedAPITypeReasons</key><array><string>C617.1</string></array>
  </dict>
  <dict>
    <key>NSPrivacyAccessedAPIType</key><string>NSPrivacyAccessedAPICategorySystemBootTime</string>
    <key>NSPrivacyAccessedAPITypeReasons</key><array><string>35F9.1</string></array>
  </dict>
  <dict>
    <key>NSPrivacyAccessedAPIType</key><string>NSPrivacyAccessedAPICategoryUserDefaults</string>
    <key>NSPrivacyAccessedAPITypeReasons</key><array><string>CA92.1</string></array>
  </dict>
</array>

2. Duplicate Objective-C classes

Issue #3864 (still open) reports that libLiteRtMetalAccelerator.dylib and libLiteRtTopKMetalSampler.dylib each contain the same 35 Objective-C classes (GTMLogger, GTMLog*, GIPLog*, GIPSafe*, GSCError*, SRL*). The GPU path loads both, so the runtime prints 35 "Class X is implemented in both" warnings and uses one copy at random. If your app also links Google Toolbox for Mac, you get a third copy of the GTM* classes.

3. Large memory maps in a big app

Issue #2545 showed a Gemma 4 E2B model where one 1.28 GB section could not get a contiguous memory map in a large host app, even with about 3 GB free. The engine still said ready. Then inference returned empty output or crashed. The same model worked in a small sample app.

4. Long context on the iOS GPU

From lesson 04: #3913 is on iOS, on an iPhone 17 Pro, with v0.18.0. Run the parity set on your oldest and newest supported iPhone. For prompts above 2048 tokens, use activationDataType: .float32 or CPU unless your parity report says float16 is fine.

import LiteRTLM

func engineConfig(modelPath: String, promptTokens: Int, gpuAllowed: Bool, maxTokens: Int) throws -> EngineConfig {
    guard gpuAllowed else {
        return try EngineConfig(modelPath: modelPath, backend: .cpu(), maxNumTokens: maxTokens)
    }
    let activation: ActivationDataType = promptTokens > 2048 ? .float32 : .float16
    return try EngineConfig(modelPath: modelPath, backend: .gpu,
                            maxNumTokens: maxTokens, activationDataType: activation)
}

Release checklist

  1. Privacy manifest covers every category the LiteRT-LM binaries use.
  2. Duplicate class warnings counted and written down.
  3. Model tested inside the full app after heavy use. Empty output handled.
  4. Parity report passed on oldest and newest supported devices.
  5. Stop-then-leave test from lesson 01 passed 100 times.
  6. maxNumTokens set from the bundle, near-limit test passed (lesson 03).

Sources

Key concepts

  • #3863: LiteRT-LM iOS binaries used required-reason APIs with no privacy manifest. A fix merged after v0.18.0.
  • #3864: two Metal dylibs define the same 35 Objective-C classes.
  • #2545: a large model section can fail to map in a big app, and the engine can still say ready.
  • #3913: long prompts on the iOS GPU need float32 or CPU unless your tests say otherwise.

Takeaways

  • Scan your archive for manifests and required-reason symbols before every release.
  • Test the model inside the full app, not only in a sample.
  • Keep one written checklist and run it for every LiteRT-LM update.