如何在用户滚动时更新 NSTextView(不会崩溃)

How to update an NSTextView while user is scrolling (without crashing)

我正在使用 NSTextView 来显示长时间搜索的结果,其中添加了后台线程使用

找到的行
[self performSelectorOnMainThread: @selector(addMatch:) 
      withObject:options waitUntilDone:TRUE];

作为我的更新例程

-(void)addMatch:(NSDictionary*)options{
 ...
 NSTextStorage* store = [textView textStorage];
 [store beginEditing];
 [store appendAttributedString:text];
  ...
 [store endEditing];
}

这工作正常,直到用户滚动浏览正在更新的匹配项,此时出现异常

-[NSLayoutManager _fillLayoutHoleForCharacterRange:desiredNumberOfLines:isSoft:] *** attempted layout while textStorage is editing. It is not valid to cause the layoutManager to do layout while the textStorage is editing (ie the textStorage has been sent a beginEditing message without a matching endEditing.)

在布局调用中:

    0   CoreFoundation                      0x00007fff92ea364c __exceptionPreprocess + 172
    1   libobjc.A.dylib                     0x00007fff8acd16de objc_exception_throw + 43
    2   CoreFoundation                      0x00007fff92ea34fd +[NSException raise:format:] + 205
    3   UIFoundation                        0x00007fff8fe4fbc1 -[NSLayoutManager(NSPrivate) _fillLayoutHoleForCharacterRange:desiredNumberOfLines:isSoft:] + 641
    4   UIFoundation                        0x00007fff8fe5970c _NSFastFillAllLayoutHolesForGlyphRange + 1493
    5   UIFoundation                        0x00007fff8fda8821 -[NSLayoutManager lineFragmentRectForGlyphAtIndex:effectiveRange:] + 39
    6   AppKit                              0x00007fff8ef3cb02 -[NSTextView _extendedGlyphRangeForRange:maxGlyphIndex:drawingToScreen:] + 478
    7   AppKit                              0x00007fff8ef3ba97 -[NSTextView drawRect:] + 1832
    8   AppKit                              0x00007fff8eed9a09 -[NSView(NSInternal) _recursive:displayRectIgnoringOpacity:inGraphicsContext:CGContext:topView:shouldChangeFontReferenceColor:] + 1186
    9   AppKit                              0x00007fff8eed9458 __46-[NSView(NSLayerKitGlue) drawLayer:inContext:]_block_invoke + 218
    10  AppKit                              0x00007fff8eed91f1 -[NSView(NSLayerKitGlue) _drawViewBackingLayer:inContext:drawingHandler:] + 2407
    11  AppKit                              0x00007fff8eed8873 -[NSView(NSLayerKitGlue) drawLayer:inContext:] + 108
    12  AppKit                              0x00007fff8efaafd2 -[NSTextView drawLayer:inContext:] + 179
    13  AppKit                              0x00007fff8ef22f76 -[_NSBackingLayerContents drawLayer:inContext:] + 145
    14  QuartzCore                          0x00007fff9337c177 -[CALayer drawInContext:] + 119
    15  AppKit                              0x00007fff8ef22aae -[_NSTiledLayer drawTile:inContext:] + 625
    16  AppKit                              0x00007fff8ef227df -[_NSTiledLayerContents drawLayer:inContext:] + 169
    17  QuartzCore                          0x00007fff9337c177 -[CALayer drawInContext:] + 119
    18  AppKit                              0x00007fff8f6efd64 -[NSTileLayer drawInContext:] + 169
    19  QuartzCore                          0x00007fff9337b153 CABackingStoreUpdate_ + 3306
    20  QuartzCore                          0x00007fff9337a463 ___ZN2CA5Layer8display_Ev_block_invoke + 59
    21  QuartzCore                          0x00007fff9337a41f x_blame_allocations + 81
    22  QuartzCore                          0x00007fff93379f1c _ZN2CA5Layer8display_Ev + 1546
    23  AppKit                              0x00007fff8ef226ed -[NSTileLayer display] + 119
    24  AppKit                              0x00007fff8ef1ec34 -[_NSTiledLayerContents update:] + 5688
    25  AppKit                              0x00007fff8ef1d337 -[_NSTiledLayer display] + 375
    26  QuartzCore                          0x00007fff93379641 _ZN2CA5Layer17display_if_neededEPNS_11TransactionE + 603
    27  QuartzCore                          0x00007fff93378d7d _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 35
    28  QuartzCore                          0x00007fff9337850e _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 242
    29  QuartzCore                          0x00007fff93378164 _ZN2CA11Transaction6commitEv + 390
    30  QuartzCore                          0x00007fff93388f55 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 71
    31  CoreFoundation                      0x00007fff92dc0d87 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
    32  CoreFoundation                      0x00007fff92dc0ce0 __CFRunLoopDoObservers + 368
    33  CoreFoundation                      0x00007fff92db2f1a __CFRunLoopRun + 1178
    34  CoreFoundation                      0x00007fff92db2838 CFRunLoopRunSpecific + 296
    35  UIFoundation                        0x00007fff8fdfe744 -[NSHTMLReader _loadUsingWebKit] + 2097
    36  UIFoundation                        0x00007fff8fdffb55 -[NSHTMLReader attributedString] + 22
    37  UIFoundation                        0x00007fff8fe12cca _NSReadAttributedStringFromURLOrData + 10543
    38  UIFoundation                        0x00007fff8fe10306 -[NSAttributedString(NSAttributedStringUIFoundationAdditions) initWithData:options:documentAttributes:error:] + 115

怎么了,一切都在 beginEditing 和 endEditing 之间?

据我所知,这个问题没有解决办法。另一种可行的方法是将匹配项作为属性字符串存储在数组中,并使用 NSTableView 通过设置 textField.attributedStringValue 来显示匹配项(每次添加新匹配项时调用 reloadData);像这样的东西(其中 matchContent 是一个 NSMutableArray):

-(void)addMatch:(NSDictionary*)options{
 ...
 [matchContent addObject:text];
 [resultTableView reloadData];
}

- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
    return matchContent.count;
}

- (NSView *)tableView:(NSTableView *)tableView
   viewForTableColumn:(NSTableColumn *)tableColumn
                  row:(NSInteger)row {

    NSTableCellView *result = [tableView makeViewWithIdentifier:@"MyView" owner:self];
    result.textField.attributedStringValue  = [matchContent objectAtIndex:row];
    return result;
}

如果结果是多行的,您可能还需要使用属性字符串的 boundingRectWithSize 方法。

从堆栈跟踪(不完整)来看,运行 循环源似乎在不合时宜的时刻触发。

NSAttributedString 使用 WebKit 解析 HTML。 WebKit 有时 运行 是 运行 循环。对于一般情况,可能需要从网络中获取资源才能正确渲染。由于这需要时间,因此 运行 是 运行 循环以等待结果并同时处理其他事情。

另一个 运行 循环源似乎是核心动画源,用于执行某些动画的下一步(大概是滚动文本视图)。

您没有显示 beginEditingendEditing 之间的所有代码。我怀疑您已经从 HTML 构建了一个 NSAttributedString 或从这两个位置之间的 URL 获取了数据。这允许触发核心动画 运行 循环源。这要求文本视图绘制,要求其布局管理器布置文本。这发生在 beginEditing 之后但在 endEditing 之前,这是异常的原因。

因此,请尝试重新排序您的代码以在 beginEditing.

之前构造所有 NSAttributedString

并向 Apple 提交错误。在我看来,当 NSAttributeString 使用 WebKit 来呈现 HTML 时,它需要让 WebKit 使用私有 运行 循环模式,这样其他来源就无法触发。他们可能更喜欢不同的解决方案,但错误是真实存在的。