Sunday, September 4, 2016

Catch UIViewAlertForUnsatisfiableConstraints in production

Leave a Comment

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

My goal would be to add a global handler that would report such errors to a logging system.

2 Answers

Answers 1

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 

I test it on iOS Simulator 8/9/10, but I can't give any guarantee. 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.

Answers 2

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.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment