Catch UIViewAlertForUnsatisfiableConstraints in production

前端 未结 2 808
孤独总比滥情好
孤独总比滥情好 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:54

    The short answer is, this is a private API and you shouldn't be messing with it in production code…

    …at least not without knowing the related dangers:

    A) Apple will reject your app if you try to override SPIs like this in a product submitted to the app store. And if it slips through for some reason, they will catch it at some later date, and that is generally worse.

    B) Method swizzling, like @Roman mentions in his answer, often brings with it some likelihood of you destabilizing whatever you're working on further (or in the future). I still worry when I user a third party library that someone is doing something brittle like this under the hood.

    With those warnings out there, go ahead, override private methods and swizzle them to your hearts content. Just please don't ship that code.

    0 讨论(0)
  • 2021-02-19 01:58

    The symbol UIViewAlertForUnsatisfiableConstraints actually is a function :

    _UIViewAlertForUnsatisfiableConstraints(NSLayoutConstraint* unsatisfiableConstraint, NSArray<NSLayoutConstraint*>* 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 <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
    

    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.

    0 讨论(0)
提交回复
热议问题