在生产中捕获 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 的理解正确,__UIConstraintBasedLayoutVisualizeMutuallyExclusiveConstraintsios 上将始终返回 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的主要内容,如果未能解决你的问题,请参考以下文章

Rails 4:为啥在生产中没有加载字体?

ActionCable - 无法在生产中升级到 WebSocket

砌体 jquery 不会在生产中呈现(Heroku)

在生产中禁用 graphiql

Tailwind 自定义背景图像在生产中不起作用

Sequelize:在生产中更改模型架构