更新 Touch Bar 转义键时如何防止崩溃?
How can I prevent this crash when updating the Touch Bar's escape key?
我在最新的 MacBook Pro 发布时向我的应用程序添加了 Touch Bar 支持。后来我做了各种小改进,包括在有意义的地方自定义转义键。发布该更新后,当应用程序尝试更新转义键时,我开始收到崩溃报告。
这是一个例子:
Exception Type: SIGBUS
Exception Codes: BUS_ADRERR at 0x7fff54a05ff8
Crashed Thread: 0
Application Specific Information:
Selector name found in current argument registers: objectForKey:
Thread 0 Crashed:
0 Foundation 0x00007fffacbaea98 -[NSConcreteMapTable objectForKey:] + 21
1 Foundation 0x00007fffacbfc019 -[NSISEngine outgoingRowHeadForRemovingConstraintWithMarker:] + 214
2 Foundation 0x00007fffacbfbb4c -[NSISEngine removeConstraintWithMarker:] + 479
3 Foundation 0x00007fffacbf76a6 -[NSISEngine _flushPendingRemovals] + 615
4 Foundation 0x00007fffacbf4b06 -[NSISEngine withBehaviors:performModifications:] + 197
5 AppKit 0x00007fffa8c83760 -[NSView(NSConstraintBasedLayout) _withAutomaticEngineOptimizationDisabled:] + 69
6 AppKit 0x00007fffa8d129dd -[NSView(NSConstraintBasedLayout) removeConstraints:] + 276
7 AppKit 0x00007fffa8c88f9e -[NSView(NSConstraintBasedLayout) _constraints_snipDangliesWithForce:] + 595
8 AppKit 0x00007fffa8c82d9e -[NSView _setSuperview:] + 1076
9 AppKit 0x00007fffa8c88945 -[NSView removeFromSuperview] + 446
10 AppKit 0x00007fffa9593eef -[NSTouchBarEscapeKeyViewController setTouchBarItem:] + 145
11 AppKit 0x00007fffa9176bf5 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
12 AppKit 0x00007fffa9176bf5 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
13 AppKit 0x00007fffa9176bf5 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
…
509 AppKit 0x00007fffa9176bf5 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
510 AppKit 0x00007fffa9176bf5 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
511 AppKit 0x00007fffa9176bf5 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
为简洁起见,我删除了大约 500 行——所有这些都是对 [NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
的调用
这是另一个简短的报告。这是我最常见的崩溃。它实际上调用了我自己的代码,尽管我怀疑它调用的代码不是真正的问题:
Exception Type: SIGBUS
Exception Codes: BUS_ADRERR at 0x7fff5b8c2f74
Crashed Thread: 0
Thread 0 Crashed:
0 CoreText 0x00007fffc87d2e8d _ZNK3OTL7GCommon9NthLookupEj + 41
1 CoreText 0x00007fffc87d658b _ZNK3OTL4GPOS12ApplyLookupsER8TRunGlueiRNS_12GlyphLookupsE + 155
2 CoreText 0x00007fffc87d5f53 _ZN26TOpenTypePositioningEngine12PositionRunsER9SyncStateR13KerningStatus + 839
3 CoreText 0x00007fffc8823920 _ZN14TKerningEngine14PositionGlyphsER8TRunGlue11ShapingTypePK10__CFString + 168
4 CoreText 0x00007fffc87ddcf7 CTFontTransformGlyphs + 463
5 UIFoundation 0x00007fffd9d2a795 __NSStringDrawingEngine + 7348
6 UIFoundation 0x00007fffd9d315ea -[NSAttributedString(NSExtendedStringDrawing) boundingRectWithSize:options:context:] + 605
7 UIFoundation 0x00007fffd9d31efd -[NSAttributedString(NSExtendedStringDrawing) boundingRectWithSize:options:] + 32
8 AppKit 0x00007fffc4e711cb -[NSAttributedString(NSStringDrawingExtension) _sizeWithSize:] + 55
9 AppKit 0x00007fffc4e710ff -[NSButtonCell(NSButtonCellPrivate) _titleSizeWithSize:] + 97
10 AppKit 0x00007fffc4e70eab -[NSButtonCell(NSButtonCellPrivate) _alignedTitleRectWithRect:] + 235
11 AppKit 0x00007fffc4e25162 -[NSButtonCell cellSizeForBounds:] + 918
12 AppKit 0x00007fffc4da2a21 -[NSCell cellSize] + 68
13 AppKit 0x00007fffc4da295a -[NSControl sizeToFit] + 53
14 AppKit 0x00007fffc520392c +[NSButton(NSButtonConvenience) _buttonWithTitle:image:target:action:] + 421
15 AppKit 0x00007fffc5203a05 +[NSButton(NSButtonConvenience) buttonWithTitle:target:action:] + 199
16 Deliveries 0x0000000103b4c655 +[JUNTouchBar cancelButtonItemWithIdentifier:] (JUNTouchBar.m:75)
17 Deliveries 0x0000000103b67e9b -[JUNEditWindowController touchBar:makeItemForIdentifier:] (JUNEditWindowController.m:183)
18 AppKit 0x00007fffc57d7d2a __32-[NSTouchBar itemForIdentifier:]_block_invoke + 34
19 AppKit 0x00007fffc4e71bd0 +[NSAppearance _performWithCurrentAppearance:usingBlock:] + 79
20 AppKit 0x00007fffc57d7b9b -[NSTouchBar itemForIdentifier:] + 1158
21 AppKit 0x00007fffc57d868e -[NSTouchBar(NSEscapeKeyReplacementOld) escapeKeyReplacementItem] + 51
22 AppKit 0x00007fffc5250e80 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 238
23 AppKit 0x00007fffc5250f49 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 439
24 AppKit 0x00007fffc5250f49 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 439
25 AppKit 0x00007fffc5250f49 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 439
…
509 AppKit 0x00007fffc5250f49 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 439
510 AppKit 0x00007fffc5250f49 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 439
511 AppKit 0x00007fffc5250f49 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 439
我的touchBar:makeItemForIdentifier:
方法就是这样:
- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier {
if ([identifier isEqualToString:JUNTouchBarItemIdentifierCancel]) {
NSCustomTouchBarItem *item = [JUNTouchBar cancelButtonItemWithIdentifier:identifier];
return item;
}
return nil;
}
这里是 cancelButtonItemWithIdentifier:
,也很简单:
+ (NSCustomTouchBarItem *)cancelButtonItemWithIdentifier:(NSString *)identifier {
NSString *title = NSLocalizedString(@"Cancel", nil);
NSButton *button = [NSButton buttonWithTitle:title target:nil action:@selector(cancelOperation:)];
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:80.0];
constraint.priority = 950;
constraint.active = YES;
NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
item.view = button;
return item;
}
当然我不能自己重现这个崩溃。它发生在 macOS 10.12.2 到 10.12.5 的 beta 版本中。许多报告都来自我正在自己测试的同一个 13" 模型。在他们的崩溃报告中包含评论的少数人大多说它是在打开新 window 的操作之后立即发生的(因此改变转义键)——有人说它发生在关闭 window 的操作之后。有人提到在崩溃前挂起 10 秒——考虑到对同一方法的 500 次调用,这是有道理的。有几个人有不止一次提到过,但是他们当然没有留下任何联系方式,所以我无法跟进他们。
我知道我可以通过删除我的自定义转义键来解决崩溃问题,但我不想这样做。关于如何处理这个问题还有其他想法吗?
更新: 自从最初发布这个之后,我更新了应用程序以删除约束,所以我创建按钮的代码现在尽可能简单:
+ (NSCustomTouchBarItem *)cancelButtonItemWithIdentifier:(NSString *)identifier {
NSString *title = JUNLocalizedString(@"Cancel", nil);
NSButton *button = [NSButton buttonWithTitle:title target:nil action:@selector(cancelOperation:)];
NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
item.view = button;
return item;
}
不幸的是,我仍然收到新版本的崩溃报告,具有相同的堆栈跟踪。所以我认为问题不在于我提供的按钮。
抱歉不是答案,但评论太长了。
我为经历过崩溃的用户构建了一个调试版本,它调整了 _updateEscapeKeyItem 方法,试图查明崩溃源,由于堆栈底部被无限吹走,崩溃源不可见递归。
我猜他们彻底改变了 10.13 的实现(他们提到了一些关于 "KVO running amok" 的事情),除非他们有可靠的线索,否则可能不会调查 10.12 的崩溃。
这是您可以插入的快速而肮脏的代码(主要是从 How to swizzle a method of a private class 借来的),它停止了无限递归。生成的崩溃报告可能更有用。
@interface NSObject (swizzle_touchbar_stuff)
@end
static NSInteger updateEscapeKeyItem = 0;
@implementation NSObject (swizzle_touchbar_stuff)
+ (void)load
{
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
Method original, swizzled;
original = class_getInstanceMethod(objc_getClass("NSApplicationFunctionRowController"), NSSelectorFromString(@"_updateEscapeKeyItem"));
swizzled = class_getInstanceMethod(self, @selector(sparkle_updateEscapeKeyItem));
method_exchangeImplementations(original, swizzled);
});
}
- (void)sparkle_updateEscapeKeyItem
{
updateEscapeKeyItem++;
NSLog(@"sparkle_updateEscapeKeyItem %ld", updateEscapeKeyItem);
assert(updateEscapeKeyItem < 10);
[(NSObject *)self sparkle_updateEscapeKeyItem];
updateEscapeKeyItem--;
}
@end
我在最新的 MacBook Pro 发布时向我的应用程序添加了 Touch Bar 支持。后来我做了各种小改进,包括在有意义的地方自定义转义键。发布该更新后,当应用程序尝试更新转义键时,我开始收到崩溃报告。
这是一个例子:
Exception Type: SIGBUS
Exception Codes: BUS_ADRERR at 0x7fff54a05ff8
Crashed Thread: 0
Application Specific Information:
Selector name found in current argument registers: objectForKey:
Thread 0 Crashed:
0 Foundation 0x00007fffacbaea98 -[NSConcreteMapTable objectForKey:] + 21
1 Foundation 0x00007fffacbfc019 -[NSISEngine outgoingRowHeadForRemovingConstraintWithMarker:] + 214
2 Foundation 0x00007fffacbfbb4c -[NSISEngine removeConstraintWithMarker:] + 479
3 Foundation 0x00007fffacbf76a6 -[NSISEngine _flushPendingRemovals] + 615
4 Foundation 0x00007fffacbf4b06 -[NSISEngine withBehaviors:performModifications:] + 197
5 AppKit 0x00007fffa8c83760 -[NSView(NSConstraintBasedLayout) _withAutomaticEngineOptimizationDisabled:] + 69
6 AppKit 0x00007fffa8d129dd -[NSView(NSConstraintBasedLayout) removeConstraints:] + 276
7 AppKit 0x00007fffa8c88f9e -[NSView(NSConstraintBasedLayout) _constraints_snipDangliesWithForce:] + 595
8 AppKit 0x00007fffa8c82d9e -[NSView _setSuperview:] + 1076
9 AppKit 0x00007fffa8c88945 -[NSView removeFromSuperview] + 446
10 AppKit 0x00007fffa9593eef -[NSTouchBarEscapeKeyViewController setTouchBarItem:] + 145
11 AppKit 0x00007fffa9176bf5 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
12 AppKit 0x00007fffa9176bf5 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
13 AppKit 0x00007fffa9176bf5 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
…
509 AppKit 0x00007fffa9176bf5 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
510 AppKit 0x00007fffa9176bf5 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
511 AppKit 0x00007fffa9176bf5 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
为简洁起见,我删除了大约 500 行——所有这些都是对 [NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
这是另一个简短的报告。这是我最常见的崩溃。它实际上调用了我自己的代码,尽管我怀疑它调用的代码不是真正的问题:
Exception Type: SIGBUS
Exception Codes: BUS_ADRERR at 0x7fff5b8c2f74
Crashed Thread: 0
Thread 0 Crashed:
0 CoreText 0x00007fffc87d2e8d _ZNK3OTL7GCommon9NthLookupEj + 41
1 CoreText 0x00007fffc87d658b _ZNK3OTL4GPOS12ApplyLookupsER8TRunGlueiRNS_12GlyphLookupsE + 155
2 CoreText 0x00007fffc87d5f53 _ZN26TOpenTypePositioningEngine12PositionRunsER9SyncStateR13KerningStatus + 839
3 CoreText 0x00007fffc8823920 _ZN14TKerningEngine14PositionGlyphsER8TRunGlue11ShapingTypePK10__CFString + 168
4 CoreText 0x00007fffc87ddcf7 CTFontTransformGlyphs + 463
5 UIFoundation 0x00007fffd9d2a795 __NSStringDrawingEngine + 7348
6 UIFoundation 0x00007fffd9d315ea -[NSAttributedString(NSExtendedStringDrawing) boundingRectWithSize:options:context:] + 605
7 UIFoundation 0x00007fffd9d31efd -[NSAttributedString(NSExtendedStringDrawing) boundingRectWithSize:options:] + 32
8 AppKit 0x00007fffc4e711cb -[NSAttributedString(NSStringDrawingExtension) _sizeWithSize:] + 55
9 AppKit 0x00007fffc4e710ff -[NSButtonCell(NSButtonCellPrivate) _titleSizeWithSize:] + 97
10 AppKit 0x00007fffc4e70eab -[NSButtonCell(NSButtonCellPrivate) _alignedTitleRectWithRect:] + 235
11 AppKit 0x00007fffc4e25162 -[NSButtonCell cellSizeForBounds:] + 918
12 AppKit 0x00007fffc4da2a21 -[NSCell cellSize] + 68
13 AppKit 0x00007fffc4da295a -[NSControl sizeToFit] + 53
14 AppKit 0x00007fffc520392c +[NSButton(NSButtonConvenience) _buttonWithTitle:image:target:action:] + 421
15 AppKit 0x00007fffc5203a05 +[NSButton(NSButtonConvenience) buttonWithTitle:target:action:] + 199
16 Deliveries 0x0000000103b4c655 +[JUNTouchBar cancelButtonItemWithIdentifier:] (JUNTouchBar.m:75)
17 Deliveries 0x0000000103b67e9b -[JUNEditWindowController touchBar:makeItemForIdentifier:] (JUNEditWindowController.m:183)
18 AppKit 0x00007fffc57d7d2a __32-[NSTouchBar itemForIdentifier:]_block_invoke + 34
19 AppKit 0x00007fffc4e71bd0 +[NSAppearance _performWithCurrentAppearance:usingBlock:] + 79
20 AppKit 0x00007fffc57d7b9b -[NSTouchBar itemForIdentifier:] + 1158
21 AppKit 0x00007fffc57d868e -[NSTouchBar(NSEscapeKeyReplacementOld) escapeKeyReplacementItem] + 51
22 AppKit 0x00007fffc5250e80 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 238
23 AppKit 0x00007fffc5250f49 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 439
24 AppKit 0x00007fffc5250f49 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 439
25 AppKit 0x00007fffc5250f49 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 439
…
509 AppKit 0x00007fffc5250f49 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 439
510 AppKit 0x00007fffc5250f49 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 439
511 AppKit 0x00007fffc5250f49 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 439
我的touchBar:makeItemForIdentifier:
方法就是这样:
- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier {
if ([identifier isEqualToString:JUNTouchBarItemIdentifierCancel]) {
NSCustomTouchBarItem *item = [JUNTouchBar cancelButtonItemWithIdentifier:identifier];
return item;
}
return nil;
}
这里是 cancelButtonItemWithIdentifier:
,也很简单:
+ (NSCustomTouchBarItem *)cancelButtonItemWithIdentifier:(NSString *)identifier {
NSString *title = NSLocalizedString(@"Cancel", nil);
NSButton *button = [NSButton buttonWithTitle:title target:nil action:@selector(cancelOperation:)];
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:80.0];
constraint.priority = 950;
constraint.active = YES;
NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
item.view = button;
return item;
}
当然我不能自己重现这个崩溃。它发生在 macOS 10.12.2 到 10.12.5 的 beta 版本中。许多报告都来自我正在自己测试的同一个 13" 模型。在他们的崩溃报告中包含评论的少数人大多说它是在打开新 window 的操作之后立即发生的(因此改变转义键)——有人说它发生在关闭 window 的操作之后。有人提到在崩溃前挂起 10 秒——考虑到对同一方法的 500 次调用,这是有道理的。有几个人有不止一次提到过,但是他们当然没有留下任何联系方式,所以我无法跟进他们。
我知道我可以通过删除我的自定义转义键来解决崩溃问题,但我不想这样做。关于如何处理这个问题还有其他想法吗?
更新: 自从最初发布这个之后,我更新了应用程序以删除约束,所以我创建按钮的代码现在尽可能简单:
+ (NSCustomTouchBarItem *)cancelButtonItemWithIdentifier:(NSString *)identifier {
NSString *title = JUNLocalizedString(@"Cancel", nil);
NSButton *button = [NSButton buttonWithTitle:title target:nil action:@selector(cancelOperation:)];
NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
item.view = button;
return item;
}
不幸的是,我仍然收到新版本的崩溃报告,具有相同的堆栈跟踪。所以我认为问题不在于我提供的按钮。
抱歉不是答案,但评论太长了。
我为经历过崩溃的用户构建了一个调试版本,它调整了 _updateEscapeKeyItem 方法,试图查明崩溃源,由于堆栈底部被无限吹走,崩溃源不可见递归。
我猜他们彻底改变了 10.13 的实现(他们提到了一些关于 "KVO running amok" 的事情),除非他们有可靠的线索,否则可能不会调查 10.12 的崩溃。
这是您可以插入的快速而肮脏的代码(主要是从 How to swizzle a method of a private class 借来的),它停止了无限递归。生成的崩溃报告可能更有用。
@interface NSObject (swizzle_touchbar_stuff)
@end
static NSInteger updateEscapeKeyItem = 0;
@implementation NSObject (swizzle_touchbar_stuff)
+ (void)load
{
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
Method original, swizzled;
original = class_getInstanceMethod(objc_getClass("NSApplicationFunctionRowController"), NSSelectorFromString(@"_updateEscapeKeyItem"));
swizzled = class_getInstanceMethod(self, @selector(sparkle_updateEscapeKeyItem));
method_exchangeImplementations(original, swizzled);
});
}
- (void)sparkle_updateEscapeKeyItem
{
updateEscapeKeyItem++;
NSLog(@"sparkle_updateEscapeKeyItem %ld", updateEscapeKeyItem);
assert(updateEscapeKeyItem < 10);
[(NSObject *)self sparkle_updateEscapeKeyItem];
updateEscapeKeyItem--;
}
@end