Какая разница между NSFont и CTFont и почему CTFont зависает NSTextFieldCell? - PullRequest
0 голосов
/ 11 июня 2019

Я хочу показать шрифты в NSTableView. Если я использую шрифты, введенные NSFont(name: fontName, size: size), то все в порядке. Но в этом случае я могу использовать только те шрифты, которые установлены в системе. Поэтому я сделал расширение NSFont:

public extension NSFont {
    static func read(from path: String, size: CGFloat) throws -> NSFont {
        guard let dataProvider = CGDataProvider(filename: path) else {
            throw NSError(domain: "file not found", code: 77, userInfo: ["fileName" : path])
        }
        guard let fontRef = CGFont ( dataProvider ) else {
            throw NSError(domain: "Not a font file", code: 77, userInfo: ["fileName" : path])
        }
        return CTFontCreateWithGraphicsFont(fontRef, size, nil, nil) as NSFont
    }
}

Вроде все работает, созданные таким образом шрифты находят свое место в [NSFont] массивах. Но если попытаться привязать их к NSTextFieldCell шрифту в NSTableView, программа взорвется:

(this goes forever and throws Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffeef3ffff8))
......
......
#261509 0x00007fff6c023e63 in -[NSCTFont isEqual:] ()
#261510 0x00007fff4539908c in _CFNonObjCEqual ()
#261511 0x00007fff6c023e63 in -[NSCTFont isEqual:] ()
#261512 0x00007fff4539908c in _CFNonObjCEqual ()
#261513 0x00007fff6c023e63 in -[NSCTFont isEqual:] ()
#261514 0x00007fff6bfccf63 in -[NSAttributeDictionary isEqualToDictionary:] ()
#261515 0x00007fff6bfccc11 in attributeDictionaryIsEqual ()
#261516 0x00007fff475c6712 in hashProbe ()
#261517 0x00007fff475c6518 in -[NSConcreteHashTable getItem:] ()
#261518 0x00007fff6bfc5be8 in +[NSAttributeDictionary newWithDictionary:] ()
#261519 0x00007fff6bff7cbe in -[_NSCachedAttributedString initWithString:attributes:] ()
#261520 0x00007fff6bfdac1f in __NSStringDrawingEngine ()
#261521 0x00007fff6bff7380 in _NSStringDrawingCore ()
#261522 0x00007fff42b16477 in _NSDrawTextCell2 ()
#261523 0x00007fff42b15328 in __45-[NSTextFieldCell _drawForegroundOfTextLayer]_block_invoke ()
#261524 0x00007fff42a8c529 in -[NSFocusStack performWithFocusView:inWindow:usingBlock:] ()
#261525 0x00007fff42b14bff in -[NSTextFieldCell _drawForegroundOfTextLayer] ()
#261526 0x00007fff42b1445a in -[NSTextFieldCell updateLayerWithFrame:inView:] ()
#261527 0x00007fff42b14322 in -[NSControl updateLayer] ()
#261528 0x00007fff42afe301 in _NSViewUpdateLayer ()
......
......

Я думаю, что в CTFont чего-то не хватает, что есть в NSFont. Но что?

Ответы [ 2 ]

0 голосов
/ 11 июля 2019

Вы не делаете ничего плохого. Это ошибка в macOS.

Вы можете разыграть CTFont до NSFont, потому что эти типы являются «бесплатными мостовыми соединениями». Это означает, что CTFont размещается в памяти таким образом, чтобы соответствовать требованиям экземпляров Objective-C. Одно из этих требований заключается в том, что первое слово объекта содержит указатель (называемый указателем «isa») на класс объекта. В случае CTFont этот класс называется NSCTFont и является подклассом NSFont.

NSCTFont (определенный в частной платформе UIFoundation) переопределяет метод isEqual:. Если вы посмотрите на разборку этой функции (и если вы понимаете сборку x86), вы увидите, что она определена примерно так:

- (BOOL)isEqual:(NSObject *)other {
    if (other == 0) { return NO; }
    if (other == self) { return YES; }
    return _CFNonObjCEqual(self, other);
}

Итак, если объекты не являются явно разными (потому что other равен нулю) и не являются явно одинаковыми (потому что они имеют одинаковый указатель), то этот метод isEqual: вызывает _CFNonObjCEqual, который является частным Основная функция основания. Случилось так, что _CFNonObjCEqual является частью открытой версии Core Foundation, поэтому мы можем взглянуть на его реализацию :

Boolean _CFNonObjCEqual(CFTypeRef cf1, CFTypeRef cf2) {
    //cf1 is guaranteed to be non-NULL and non-ObjC, cf2 is unknown
    if (cf1 == cf2) return true;
    if (NULL == cf2) { CRSetCrashLogMessage("*** CFEqual() called with NULL second argument ***"); HALT; }
    CFTYPE_OBJC_FUNCDISPATCH1(Boolean, cf2, isEqual:, cf1);
    CFTYPE_SWIFT_FUNCDISPATCH1(Boolean, cf2, NSObject.isEqual, (CFSwiftRef)cf1);
    __CFGenericAssertIsCF(cf1);
    __CFGenericAssertIsCF(cf2);
    if (__CFGenericTypeID_inline(cf1) != __CFGenericTypeID_inline(cf2)) return false;
    if (NULL != __CFRuntimeClassTable[__CFGenericTypeID_inline(cf1)]->equal) {
        return __CFRuntimeClassTable[__CFGenericTypeID_inline(cf1)]->equal(cf1, cf2);
    }
    return false;
}

Комментарий говорит нам, что ожидается: аргумент cf1 должен быть известен как базовый тип Foundation, который не является собственным экземпляром Objective-C, но аргумент cf2 может быть собственным экземпляром Objective-C.

Вот строка, которая имеет значение:

    CFTYPE_OBJC_FUNCDISPATCH1(Boolean, cf2, isEqual:, cf1);

Это макрос C, и мы не можем увидеть его реальное определение, потому что реальное определение было удалено из релиза с открытым исходным кодом. Но мы можем догадаться, что это, вероятно, расширяется до чего-то вроде этого:

if ([cf2 respondsToSelector:@selector(isEqual:)]) {
    return [cf2 isEqual:cf1];
}

И это будет проблемой, если cf2 равно также и NSCTFont, потому что тогда он рекурсивно вызывает -[NSCTFont isEqual:] (с замененными аргументами), который будет вызывать _CFNonObjCEqual, до тошноты (пока вы не получите вид переполнения стека, для которого назван этот веб-сайт).

Вы можете отправить отчет об ошибке на https://feedbackassistant.apple.com/, или, если хотите, с помощью приложения Feedback Assistant.

0 голосов
/ 10 июля 2019

Я не нашел ответа.Но поскольку бесконечный цикл начался, когда я попытался использовать NSPredicate, при сравнении NSCTFont isEqual: я создал контроллеры для шрифтов и использовал их для сравнения по NSPredicate.Больше нет бесконечного цикла.

...