ここに私が試していないいくつかのアイデアがあります。
Core Textを直接使用します。他の API はその上に構築されます。
並列化します。最新のすべての Mac (および最新のすべての iOS デバイスでさえも) には、複数のコアがあります。string 配列を複数のサブ配列に分割します。サブアレイごとに、ブロックをグローバル GCD キューに送信します。ブロックで、必要なコア テキストまたはNSLayoutManager
オブジェクトを作成し、サブ配列内の文字列を測定します。このように、両方の API を安全に使用できます。(コアテキスト) ( NSLayoutManager
)
「高いメモリ フットプリント」について:ローカル自動解放プール ブロックを使用して、ピーク メモリ フットプリントを削減します。
「かかる時間はすべて上記の文字列の作成中にあり、それ自体がディールブレーカーです」: すべての時間がこれらの行に費やされていると言っています:
double random = (double)arc4random_uniform(1000) / 1000;
NSString *randomNumber = [NSString stringWithFormat:@"%f", random];
浮動小数点数のフォーマットにはコストがかかります。これはあなたの本当のユースケースですか?0 ≤ n < 1000 に対して n/1000 の形式のランダムな有理数をフォーマットしたいだけなら、もっと速い方法があります。また、多くのフォントでは、すべての桁が同じ幅になっているため、数字の列を簡単にタイプセットできます。このようなフォントを選択すると、そもそも文字列の測定を回避できます。
アップデート
Core Text を使用して思いついた最速のコードを次に示します。ディスパッチ バージョンは、私の Core i7 MacBook Pro のシングル スレッド バージョンのほぼ 2 倍の速さです。あなたのプロジェクトの私のフォークはここにあります。
static CGFloat maxWidthOfStringsUsingCTFramesetter(
NSArray *strings, NSRange range) {
NSString *bigString =
[[strings subarrayWithRange:range] componentsJoinedByString:@"\n"];
NSAttributedString *richText =
[[NSAttributedString alloc]
initWithString:bigString
attributes:@{ NSFontAttributeName: (__bridge NSFont *)font }];
CGPathRef path =
CGPathCreateWithRect(CGRectMake(0, 0, CGFLOAT_MAX, CGFLOAT_MAX), NULL);
CGFloat width = 0.0;
CTFramesetterRef setter =
CTFramesetterCreateWithAttributedString(
(__bridge CFAttributedStringRef)richText);
CTFrameRef frame =
CTFramesetterCreateFrame(
setter, CFRangeMake(0, bigString.length), path, NULL);
NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frame);
for (id item in lines) {
CTLineRef line = (__bridge CTLineRef)item;
width = MAX(width, CTLineGetTypographicBounds(line, NULL, NULL, NULL));
}
CFRelease(frame);
CFRelease(setter);
CFRelease(path);
return (CGFloat)width;
}
static void test_CTFramesetter() {
runTest(__func__, ^{
return maxWidthOfStringsUsingCTFramesetter(
testStrings, NSMakeRange(0, testStrings.count));
});
}
static void test_CTFramesetter_dispatched() {
runTest(__func__, ^{
dispatch_queue_t gatherQueue = dispatch_queue_create(
"test_CTFramesetter_dispatched result-gathering queue", nil);
dispatch_queue_t runQueue =
dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
dispatch_group_t group = dispatch_group_create();
__block CGFloat gatheredWidth = 0.0;
const size_t Parallelism = 16;
const size_t totalCount = testStrings.count;
// Force unsigned long to get 64-bit math to avoid overflow for
// large totalCounts.
for (unsigned long i = 0; i < Parallelism; ++i) {
NSUInteger start = (totalCount * i) / Parallelism;
NSUInteger end = (totalCount * (i + 1)) / Parallelism;
NSRange range = NSMakeRange(start, end - start);
dispatch_group_async(group, runQueue, ^{
double width =
maxWidthOfStringsUsingCTFramesetter(testStrings, range);
dispatch_sync(gatherQueue, ^{
gatheredWidth = MAX(gatheredWidth, width);
});
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
return gatheredWidth;
});
}