在生产中捕获 UIViewAlertForUnsatisfiableConstraints
Posted
技术标签:
【中文标题】在生产中捕获 UIViewAlertForUnsatisfiableConstraints【英文标题】:Catch UIViewAlertForUnsatisfiableConstraints in production 【发布时间】:2016-08-26 14:30:52 【问题描述】:是否有可能在生产环境中捕获自动布局约束的歧义——相当于UIViewAlertForUnsatisfiableConstraints
断点,但对于生产应用程序?
我的目标是添加一个将此类错误报告给日志系统的全局处理程序。
【问题讨论】:
这是个好问题。据我了解,符号断点允许您在特定符号、方法或选择器上中断。我尝试外部化一个全局 C 函数UIViewAlertForUnsatisfiableConstraints()
并查看它是否是 UIView
上的实例或类方法,但到目前为止我一无所获。
【参考方案1】:
符号UIViewAlertForUnsatisfiableConstraints
实际上是一个函数:
_UIViewAlertForUnsatisfiableConstraints(NSLayoutConstraint* unsatisfiableConstraint, NSArray<NSLayoutConstraint*>* allConstraints)
.
它是私有的,所以你不能替换它。
但它是从私有方法-[UIView engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:]
调用的,可以调配。这个方法大致有这样的内容:
void -[UIView engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:]
if ([self _isUnsatisfiableConstraintsLoggingSuspended])
[self _recordConstraintBrokenWhileUnsatisfiableConstraintsLoggingSuspended:$arg4]; // add constraint to some pool
else
if (__UIConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints)
// print something in os_log
else
_UIViewAlertForUnsatisfiableConstraints($arg4, $arg5);
如果我对this article 的理解正确,__UIConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints
在 ios 上将始终返回 NO,因此您需要做的就是检查名为 _isUnsatisfiableConstraintsLoggingSuspended
的私有 bool 属性,然后调用原始方法。
这是结果代码示例:
#import <objc/runtime.h>
void SwizzleInstanceMethod(Class classToSwizzle, SEL origSEL, Class myClass, SEL newSEL)
Method methodToSwizzle = class_getInstanceMethod(classToSwizzle, origSEL);
Method myMethod = class_getInstanceMethod(myClass, newSEL);
class_replaceMethod(classToSwizzle, newSEL, method_getImplementation(methodToSwizzle), method_getTypeEncoding(methodToSwizzle));
class_replaceMethod(classToSwizzle, origSEL, method_getImplementation(myMethod), method_getTypeEncoding(myMethod));
@interface InterceptUnsatisfiableConstraints : NSObject
@end
@implementation InterceptUnsatisfiableConstraints
+ (void)load
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
SEL willBreakConstantSel = NSSelectorFromString(@"engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:");
SwizzleInstanceMethod([UIView class], willBreakConstantSel, [self class], @selector(pr_engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:));
);
- (void)pr_engine:(id)engine willBreakConstraint:(NSLayoutConstraint*)constraint dueToMutuallyExclusiveConstraints:(NSArray<NSLayoutConstraint*>*)layoutConstraints
BOOL constrainsLoggingSuspended = [[self valueForKey:@"_isUnsatisfiableConstraintsLoggingSuspended"] boolValue];
if (!constrainsLoggingSuspended)
NSLog(@"_UIViewAlertForUnsatisfiableConstraints would be called on next line, log this event");
[self pr_engine:engine willBreakConstraint:constraint dueToMutuallyExclusiveConstraints:layoutConstraints];
@end
它适用于 iOS 8.2/9/10(它不适用于 iOS 8.1,所以要小心),但我不能提供任何保证。 此外,它还捕获系统组件中的约束问题,例如键盘/视频播放器/等。 这段代码很脆弱(它可能导致任何系统版本更新、参数更改等崩溃),我不建议在生产中使用它(猜测它甚至不会通过自动审查过程)。你有最后一句话,但你被警告了。
但是我认为您可以在内部/外部测试人员的构建中使用它来修复生产前自动布局中的错误。
注意到您正在使用 swift:您可以使用桥接头文件将此代码添加到您的 swift 项目中。
【讨论】:
酷!您是如何找到_UIViewAlertForUnsatisfiableConstraints
的方法声明的?
@JAL 我做了一点逆向工程,并花了一些时间调试我的测试项目。
你能详细说明你是如何对 UIKit 进行逆向工程的吗?我想更多地了解它的内部结构,并且在任何私有头文件中都没有看到这个 C 函数。您是否创建了一个约束被破坏的应用程序并反汇编了二进制文件?
@JAL 当然。简而言之,我使用反汇编程序Hopper 来揭示目标函数内部发生的情况。它没有为您提供真正的对象类型(因为在编译时所有对象都转换为id
),但它为您提供了许多参数。然后您可以在lldb
中显示参数值(使用$arg1
、$arg2
等)。您可以在 lldb
/ Hopper
文档或相关文章中了解有关无源调试的更多信息。
@JAL 是的,我创建了一个测试项目,它在按钮按下时添加了损坏的约束,并在函数中添加了断点以显示堆栈跟踪(通常它会让你对这个问题产生一些想法)。【参考方案2】:
简短的回答是,这是一个私有 API,您不应该在生产代码中使用它……
……至少在不知道相关危险的情况下:
A) 如果您尝试在提交到应用商店的产品中覆盖此类 SPI,Apple 将拒绝您的应用。如果它由于某种原因漏掉了,他们会在以后的某个时间抓住它,而且通常情况会更糟。
B) 方法调配,就像@Roman 在他的回答中提到的那样,通常会带来一些可能使您破坏您正在进一步(或将来)进行的任何工作的稳定性。当我使用第三方库时,我仍然担心有人在幕后做这种脆弱的事情。
有了这些警告,继续,覆盖私有方法并将它们调配到您的内心深处。只是请不要发布该代码。
【讨论】:
以上是关于在生产中捕获 UIViewAlertForUnsatisfiableConstraints的主要内容,如果未能解决你的问题,请参考以下文章