如何在用户滚动时更新 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 有时 运行 是 运行 循环。对于一般情况,可能需要从网络中获取资源才能正确渲染。由于这需要时间,因此 运行 是 运行 循环以等待结果并同时处理其他事情。
另一个 运行 循环源似乎是核心动画源,用于执行某些动画的下一步(大概是滚动文本视图)。
您没有显示 beginEditing
和 endEditing
之间的所有代码。我怀疑您已经从 HTML 构建了一个 NSAttributedString
或从这两个位置之间的 URL 获取了数据。这允许触发核心动画 运行 循环源。这要求文本视图绘制,要求其布局管理器布置文本。这发生在 beginEditing
之后但在 endEditing
之前,这是异常的原因。
因此,请尝试重新排序您的代码以在 beginEditing
.
之前构造所有 NSAttributedString
并向 Apple 提交错误。在我看来,当 NSAttributeString
使用 WebKit 来呈现 HTML 时,它需要让 WebKit 使用私有 运行 循环模式,这样其他来源就无法触发。他们可能更喜欢不同的解决方案,但错误是真实存在的。
我正在使用 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 有时 运行 是 运行 循环。对于一般情况,可能需要从网络中获取资源才能正确渲染。由于这需要时间,因此 运行 是 运行 循环以等待结果并同时处理其他事情。
另一个 运行 循环源似乎是核心动画源,用于执行某些动画的下一步(大概是滚动文本视图)。
您没有显示 beginEditing
和 endEditing
之间的所有代码。我怀疑您已经从 HTML 构建了一个 NSAttributedString
或从这两个位置之间的 URL 获取了数据。这允许触发核心动画 运行 循环源。这要求文本视图绘制,要求其布局管理器布置文本。这发生在 beginEditing
之后但在 endEditing
之前,这是异常的原因。
因此,请尝试重新排序您的代码以在 beginEditing
.
NSAttributedString
并向 Apple 提交错误。在我看来,当 NSAttributeString
使用 WebKit 来呈现 HTML 时,它需要让 WebKit 使用私有 运行 循环模式,这样其他来源就无法触发。他们可能更喜欢不同的解决方案,但错误是真实存在的。