Catch UIViewAlertForUnsatisfiableConstraints in production

前端 未结 2 812
孤独总比滥情好
孤独总比滥情好 2021-02-19 01:25

Is it possible to catch autolayout constraint ambiguities in production – the equivalent of a UIViewAlertForUnsatisfiableConstraints breakpoint but for production a

2条回答
  •  面向向阳花
    2021-02-19 01:58

    The symbol UIViewAlertForUnsatisfiableConstraints actually is a function :

    _UIViewAlertForUnsatisfiableConstraints(NSLayoutConstraint* unsatisfiableConstraint, NSArray* allConstraints).

    It is private, so you can't replace it.

    But it is called from private method -[UIView engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:], which can be swizzled. This method has approximately this content:

    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);
        }
      }
    }
    

    If I understand correctly from this article, __UIConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints will always return NO on iOS, so all you need to do is to check private bool property called _isUnsatisfiableConstraintsLoggingSuspended and call original method then.

    This is result code example:

    #import 
    
    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*)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
    

    It works on iOS 8.2/9/10 (it doesn't work in iOS 8.1, so be careful), but I can't give any guarantee. Also, it catches constraint problems in system components, such as keyboard/video player/etc. This code is fragile (it can lead to crash on any system version update, parameters change, etc) and I will not recommend to use it in production (guess that it will not pass even automated review process). You have the last word, but you are warned.

    However I think that you can use it in builds for internal/external testers to fix bugs in autolayout before production.

    Noticed that you are using swift: you can add this code to your swift project with bridging header file.

提交回复
热议问题