Skip to content

Reduce per-keystroke latency and fix marked-text range handling - #16

Merged
soomtong merged 5 commits into
mainfrom
claude/macos-korean-ime-performance-3qdn2u
Jul 26, 2026
Merged

Reduce per-keystroke latency and fix marked-text range handling#16
soomtong merged 5 commits into
mainfrom
claude/macos-korean-ime-performance-3qdn2u

Conversation

@soomtong

Copy link
Copy Markdown
Owner

Swift 6 기준 성능 검토에서 발견된 항목을 우선순위 순서대로 커밋 5개에 나눠 반영했습니다. 메모리 누수는 발견되지 않았고, 레이턴시·정확성 관련 항목만 수정했습니다.

변경 내용 (커밋 순서대로)

  1. 입력 전략 세션 캐시getInputStrategy가 매 키 이벤트마다 전략을 재판정했고, knownApps에 없는 앱은 키 입력마다 validAttributesForMarkedText() 동기 IPC 왕복이 발생했습니다. bundleId별 캐시를 추가해 앱당 1회로 제한합니다. 키당 레이턴시 개선 폭이 가장 큰 변경입니다.
  2. NSRange를 UTF-16 길이 기준으로 수정 — 커서 위치와 백스페이스 치환 범위를 String.count(자소 수)로 계산하고 있었습니다. 완성형 한글은 1:1이지만 NFD 폴백(옛한글·아래아)은 자소 1개가 UTF-16 여러 유닛이라 커서/범위가 어긋납니다. utf16.count로 교체하고, 항상 무시되던 min(NSNotFound, ...)도 제거했습니다.
  3. composedKey 캐시 didSet 동기화composingKeys가 public var라 직접 대입 경로(HangulProcessorcomposing/preedit setter, convenience init)에서 캐시가 어긋나 겹모음/겹받침 판정이 조용히 깨질 수 있었습니다. didSet으로 재계산하고 convenience init은 designated init에 위임합니다.
  4. SymbolExtensionConfig static 캐시 — 계산 프로퍼티가 접근마다 Set 2개 + 구조체를 새로 할당했고, 기호확장 활성 시 조합 중 매 키마다 접근됩니다. 레이아웃별 static 상수로 1회만 생성합니다.
  5. 키코드 변환 이벤트당 1회로 통합verifyProcessableprocessKeyCodeInput이 같은 키코드를 두 번 변환하던 것을 inputText에서 한 번 계산해 공유하고, 사용처가 없어진 processKeyCodeInput을 제거했습니다.

검토했으나 수정하지 않은 항목

  • 빠른마침표 타이머의 client 강한 캡처: 누수는 아니며 수명이 최대 250ms 연장되는 수준이라 보류
  • updateLayout 시 조합 중 글자 유실: client 핸들이 없는 시점이라 구조 변경 필요, 보류
  • NotificationCenter 옵저버·retain cycle·keyHistory 성장 등 메모리 누수 후보는 모두 문제 없음 확인

테스트

이 작업 환경(Linux 컨테이너)에서는 xcodebuild를 실행할 수 없어 정적 검토만 거쳤습니다. 머지 전에 로컬에서 make test 실행을 권장합니다. 기존 테스트 API(verifyProcessable 기본 인자 호출)는 그대로 호환됩니다.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DXPSfN3YMi7PeunChUCuLW


Generated by Claude Code

claude added 5 commits July 26, 2026 11:01
getInputStrategy was re-deriving the strategy on every key event, and for
apps not in the knownApps table this meant a synchronous
validAttributesForMarkedText() round-trip to the client process per
keystroke. Store the resolved strategy in a per-session bundleId cache so
each app is probed at most once.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DXPSfN3YMi7PeunChUCuLW
NSRange and setMarkedText operate on UTF-16 code units, but the cursor
position and replacement range were computed with String.count, which
counts grapheme clusters. For precomposed syllables the two match, but the
NFD fallback path (old Hangul, araea) emits one grapheme spanning multiple
UTF-16 units, misplacing the cursor and the backspace replacement range.
Also drop the min(NSNotFound, ...) clamp, which never took effect.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DXPSfN3YMi7PeunChUCuLW
The composedKey cache was updated manually in a few mutating helpers, but
composingKeys is a public var: direct assignment (HangulProcessor's
composing/preedit setters) and the convenience inits bypassed the manual
updates, leaving the cache stale or empty and silently breaking compound
vowel/final-consonant matching. Recompute the cache in didSet and delegate
the convenience inits to the designated init so every mutation path stays
consistent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DXPSfN3YMi7PeunChUCuLW
symbolExtensionConfig was a computed property that allocated a fresh
config (two Sets plus the struct) on every access, and it is consulted on
every keystroke while composing when the trait is enabled. Build the
config once as a static constant and return it conditionally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DXPSfN3YMi7PeunChUCuLW
inputText resolved the same key code through KeyCodeMapper twice: once
inside verifyProcessable and again in processKeyCodeInput. Compute the
mapping once in inputText, pass it into verifyProcessable, and drop the
now-unused processKeyCodeInput helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DXPSfN3YMi7PeunChUCuLW
@soomtong
soomtong merged commit 8540cb8 into main Jul 26, 2026
1 check passed
@soomtong
soomtong deleted the claude/macos-korean-ime-performance-3qdn2u branch July 26, 2026 12:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants