技能 编程开发 苹果备忘录自动化性能优化

苹果备忘录自动化性能优化

v20260423
apple-notes-performance-tuning
本技能指南专门解决自动化处理大型备忘录集合(如万级笔记)时的性能瓶颈。它提供了从架构、缓存策略到脚本编写的最佳实践(如批量处理和增量同步),指导用户优化JXA/AppleScript代码,确保自动化流程在高并发和大规模数据下依然保持高效和响应性。
获取技能
275 次下载
概览

Apple Notes Performance Tuning

Overview

Apple Notes automation performance degrades linearly with note count because JXA loads all note objects into memory when you access a collection. A vault with 10,000+ notes can take 30+ seconds for a simple list operation. The primary bottleneck is the Apple Events bridge between your script and Notes.app — every property access (name, body, date) is a separate IPC call. This guide covers caching strategies, incremental sync, batch optimization, and architectural patterns to keep automation responsive at scale.

Performance Benchmarks

Operation 100 notes 1,000 notes 10,000 notes
List all (names only) ~0.5s ~3s ~30s
Search by name (.whose()) ~0.3s ~2s ~20s
Full-text search (body scan) ~1s ~8s ~80s
Create single note ~0.2s ~0.2s ~0.2s
Export all to JSON ~1s ~10s ~100s
Count notes only (.length) ~0.1s ~0.3s ~1s

Strategy 1: Minimize Property Access

// BAD: Each property access is a separate Apple Event IPC call
const Notes = Application("Notes");
const allNotes = Notes.defaultAccount.notes();
allNotes.forEach(n => {
  console.log(n.name());   // IPC call 1
  console.log(n.body());   // IPC call 2
  console.log(n.modificationDate()); // IPC call 3
});
// With 1000 notes = 3000 IPC calls

// GOOD: Batch extract in a single JXA evaluation
const data = Notes.defaultAccount.notes().map(n => ({
  title: n.name(),
  modified: n.modificationDate().toISOString(),
}));
// Single JXA evaluation, much faster for bulk reads

Strategy 2: Local SQLite Cache

#!/bin/bash
# Export notes to SQLite for fast local queries
DB="$HOME/.notes-cache.db"
sqlite3 "$DB" "CREATE TABLE IF NOT EXISTS notes (
  id TEXT PRIMARY KEY, title TEXT, body TEXT, folder TEXT,
  created TEXT, modified TEXT, indexed_at TEXT
);"

osascript -l JavaScript -e '
  const Notes = Application("Notes");
  Notes.defaultAccount.notes().map(n => JSON.stringify({
    id: n.id(), title: n.name(), body: n.plaintext(),
    folder: n.container().name(),
    created: n.creationDate().toISOString(),
    modified: n.modificationDate().toISOString()
  })).join("\n");
' | while IFS= read -r line; do
  echo "$line" | jq -r '[.id, .title, .body, .folder, .created, .modified, now | todate] | @csv' \
    | sqlite3 "$DB" ".import /dev/stdin notes"
done 2>/dev/null

# Now query locally (instant)
sqlite3 "$DB" "SELECT title FROM notes WHERE body LIKE '%project%' ORDER BY modified DESC LIMIT 10;"

Strategy 3: Incremental Sync

// src/sync/incremental.ts
import { execSync } from "child_process";
import { readFileSync, writeFileSync } from "fs";

const LAST_SYNC_FILE = ".notes-last-sync";

function getLastSync(): Date {
  try { return new Date(readFileSync(LAST_SYNC_FILE, "utf8").trim()); }
  catch { return new Date(0); } // First run: sync everything
}

function incrementalSync(): void {
  const lastSync = getLastSync();
  const allNotes = JSON.parse(execSync(
    `osascript -l JavaScript -e 'JSON.stringify(Application("Notes").defaultAccount.notes().map(n => ({id: n.id(), title: n.name(), modified: n.modificationDate().toISOString()})))'`,
    { encoding: "utf8" }
  ));

  const changed = allNotes.filter((n: any) => new Date(n.modified) > lastSync);
  console.log(`${changed.length} notes modified since ${lastSync.toISOString()}`);

  // Process only changed notes (fetch full body only for these)
  for (const note of changed) {
    console.log(`Syncing: ${note.title}`);
    // ... process individual note
  }

  writeFileSync(LAST_SYNC_FILE, new Date().toISOString());
}

Strategy 4: Use .whose() for Filtered Queries

// .whose() pushes filtering to Notes.app (faster than client-side filter)
const Notes = Application("Notes");

// Faster than loading all notes and filtering in JS
const recentNotes = Notes.defaultAccount.notes.whose({
  _match: [ObjectSpecifier().modificationDate, ">", new Date(Date.now() - 86400000)]
});

// Search by name (case-insensitive)
const matches = Notes.defaultAccount.notes.whose({
  name: { _contains: "project" }
});

Error Handling

Issue Cause Solution
Script hangs for >60s Too many notes with body() access Use .length first to assess scale; use cache for large vaults
Memory spike during export All note bodies loaded into JXA runtime Process in batches; stream to file instead of building array
SQLite cache stale Forgot to re-sync after edits Run incremental sync on schedule via launchd
.whose() returns wrong results Complex predicates not supported in JXA Fall back to full load + JS filter for complex queries
iCloud sync slows writes Each write triggers sync Batch writes with 1s delay; use "On My Mac" for bulk import

Resources

Next Steps

For handling rate limits during bulk operations, see apple-notes-rate-limits. For monitoring performance trends, see apple-notes-observability.

信息
Category 编程开发
Name apple-notes-performance-tuning
版本 v20260423
大小 5.63KB
更新时间 2026-04-28
语言