Ответ 1
Вот некоторые идеи, которые я не пробовал.
-
Используйте Основной текст напрямую. Другие API-интерфейсы построены поверх него.
-
Распараллеливание. Все современные Mac (и даже все современные устройства iOS) имеют несколько ядер. Разделите массив строк на несколько подмассивов. Для каждого subarray отправьте блок в глобальную очередь GCD . В блоке создайте необходимые объекты Core Text или
NSLayoutManager
и измерьте строки в подмассиве. Оба API могут использоваться безопасно таким образом. (основной текст) (NSLayoutManager
) -
Относительно "Высокий объем памяти": Использовать локальные блоки пула автообновления для уменьшения пикового объема памяти.
-
Относительно "Все время, затраченное на создание вышеуказанных строк, которое само по себе является самозахватом": вы говорите, что все время тратится на эти строки:
double random = (double)arc4random_uniform(1000) / 1000; NSString *randomNumber = [NSString stringWithFormat:@"%f", random];
Форматирование числа с плавающей запятой является дорогостоящим. Это ваш реальный прецедент? Если вы просто хотите отформатировать случайное рациональное выражение формы n/1000 для 0 ≤ n < 1000, есть более быстрые способы. Кроме того, во многих шрифтах все цифры имеют одинаковую ширину, поэтому легко набирать столбцы чисел. Если вы выберете такой шрифт, вы можете избежать измерения строк в первую очередь.
UPDATE
Здесь самый быстрый код, который я использовал с помощью Core Text. Отправленная версия почти в два раза быстрее, чем однопоточная версия на моем Core i7 MacBook Pro. Моя версия вашего проекта здесь.
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;
});
}