【问题标题】:How to update an NSTextView while user is scrolling (without crashing)如何在用户滚动时更新 NSTextView(不崩溃)
【发布时间】:2015-01-18 22:29:42
【问题描述】:

我正在使用 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:] *** 在 textStorage 编辑时尝试布局。无效 导致 layoutManager 在 textStorage 编辑时进行布局 (即 textStorage 已发送一个 beginEditing 消息,但没有 匹配 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 之间,出了什么问题?

【问题讨论】:

  • BeginUpdate 和 EndUpdate 不是锁。它们仅用于优化多个更改。这可能是相关的:[NSTextStorage 对更新大小和频率的限制][1]。 [1]:stackoverflow.com/questions/9780032/…
  • 谢谢,虽然我认为已经在主队列上调度了。顺便说一句,这段代码在第一次编写时(在 Lion 中)曾经可靠地工作;现在在优胜美地它可靠地崩溃了。

标签: cocoa


【解决方案1】:

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

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

其他运行循环源之一似乎是核心动画源,用于在某些动画中执行下一步(大概是滚动文本视图)。

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

因此,请尝试重新排序您的代码以在 beginEditing 之前构造所有 NSAttributedStrings。

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

【讨论】:

  • 正确,我正在使用 initWithHTML 生成部分字符串(尽管没有 URL)。很好的解决方案!
【解决方案2】:

据我所知,没有办法解决这个问题。另一种可行的方法是将匹配作为属性字符串存储在数组中,并使用 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 方法返回表格视图的行高。

【讨论】:

    猜你喜欢
    • 2021-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-10
    • 2011-12-26
    • 1970-01-01
    相关资源
    最近更新 更多