From f9c43051b7c70b4cb78335765e27f990720becd0 Mon Sep 17 00:00:00 2001 From: Gregory Moskaliuk Date: Sat, 17 Oct 2026 01:09:22 +0200 Subject: [PATCH] Cache custom font family resolution on iOS Fabric Resolving a custom `fontFamily` in `RCTFontWithFontProperties` enumerates the family's faces and instantiates every one of them to pick the closest weight and style, and it ran once per text fragment per attributed string build. Cache the resolved font per family, size, weight and style, cleared when fonts are registered at runtime, as the legacy `RCTFont.mm` already does for its own lookups. Variants and variation settings are applied after the cache as before. Co-Authored-By: Claude Fable 5.1 --- .../textlayoutmanager/RCTFontUtils.mm | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm index 81a0b318e5fe..2678ef3bea87 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm @@ -385,6 +385,33 @@ static UIFontDescriptorSystemDesign RCTGetFontDescriptorSystemDesign(NSString *f return systemDesigns[family]; } +// Picking a face instantiates every face in the family, so resolved fonts are cached. +static NSCache *RCTCustomFontCache(void) +{ + static NSCache *cache; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + cache = [NSCache new]; + [NSNotificationCenter.defaultCenter + addObserverForName:(NSNotificationName)kCTFontManagerRegisteredFontsChangedNotification + object:nil + queue:nil + usingBlock:^(NSNotification *) { + [cache removeAllObjects]; + }]; + }); + return cache; +} + +static NSString *RCTCustomFontCacheKey(const RCTFontProperties &fontProperties, CGFloat effectiveFontSize) +{ + return [NSString stringWithFormat:@"%@/%.1f/%.2f/%ld", + fontProperties.family, + effectiveFontSize, + fontProperties.weight, + (long)fontProperties.style]; +} + UIFont *RCTFontWithFontProperties(RCTFontProperties fontProperties) { RCTFontProperties defaultFontProperties = RCTDefaultFontProperties(); @@ -406,6 +433,9 @@ static UIFontDescriptorSystemDesign RCTGetFontDescriptorSystemDesign(NSString *f // Handle system font as special case. This ensures that we preserve // the specific metrics of the standard system font as closely as possible. font = RCTDefaultFontWithFontProperties(fontProperties); + } else if ( + (font = [RCTCustomFontCache() objectForKey:RCTCustomFontCacheKey(fontProperties, effectiveFontSize)]) != nil) { + // Resolved before; variants and variation settings are applied below. } else { NSArray *fontNames = [UIFont fontNamesForFamilyName:fontProperties.family]; UIFontWeight fontWeight = fontProperties.weight; @@ -447,6 +477,10 @@ static UIFontDescriptorSystemDesign RCTGetFontDescriptorSystemDesign(NSString *f font = [UIFont fontWithName:fontNames[0] size:effectiveFontSize]; } } + + if (font != nil) { + [RCTCustomFontCache() setObject:font forKey:RCTCustomFontCacheKey(fontProperties, effectiveFontSize)]; + } } // Apply font variants to font object.