Skip to content

Commit

Permalink
Updating the method we swizzle to detect autolayout constraint failur…
Browse files Browse the repository at this point in the history
…es. (#65)

* Updating the method we swizzle to detect autolayout constraint failures.

- It seems like Apple has changed the implementation of NSEngine and it no longer implements
'handleUnsatisfiableRow:usingInfeasibilityHandlingBehavior:prospectiveRowHead:mutuallyExclusiveConstraints:'
(verified by calling 'instancesRespondToSelector' on the class)
- As a result the swizzle we use to detect failing constraints no longer works.
- Change to use UIView.engine:willBreakConstraint:dueToMutuallyExclusiveConstraints: instead - Apple uses this method
to trigger Xcode's constraint error breakpoint - so this should be a lot more realiable in detection in failing constraints
- Added a 'responds to selector' check - this should help us identify when the implementation changes again in the future.

* Updating the assert statement to be more accurate
  • Loading branch information
indraShan authored Sep 28, 2023
1 parent 28a5f60 commit 1a6a3eb
Showing 1 changed file with 22 additions and 12 deletions.
34 changes: 22 additions & 12 deletions LayoutTestBase/Autolayout/LYTAutolayoutFailureIntercepter.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,31 @@
#import <objc/runtime.h>


#define NSStringISEngineClassName @"NSISEngine"
#define CStringISEngineClassName "NSISEngine"
#define NSStringViewClassName @"UIView"
#define CStringViewClassName "UIView"

@implementation LYTAutolayoutFailureIntercepter

+ (void)interceptAutolayoutFailuresWithBlock:(void(^)(void))block {
Class c = NSClassFromString(NSStringISEngineClassName);
NSAssert(c != nil, @"This class no longer exists which mean this method no longer works. This means that Apple has changed their implementation of Auto Layout and this code needs to be updated. Please file a bug with this information.");
[c interceptAutolayoutFailuresWithBlock:block];
Class class = NSClassFromString(NSStringViewClassName);
[self validateAutolayoutInterceptionForClass:class];
[class interceptAutolayoutFailuresWithBlock:block];
}

+ (void)stopInterceptingAutolayoutFailures {
Class c = NSClassFromString(NSStringISEngineClassName);
NSAssert(c != nil, @"This class no longer exists which mean this method no longer works. This means that Apple has changed their implementation of Auto Layout and this code needs to be updated. Please file a bug with this information.");
[c stopInterceptingAutolayoutFailures];
Class class = NSClassFromString(NSStringViewClassName);
[self validateAutolayoutInterceptionForClass:class];
[class stopInterceptingAutolayoutFailures];
}

+ (void)validateAutolayoutInterceptionForClass:(Class)class {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (class && [class instancesRespondToSelector:@selector(engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:)]) {
return;
}
#pragma clang diagnostic pop
NSAssert(false, @"This class no longer exists or it no longer implements the method we swizzle. This means that Apple has changed their implementation of Auto Layout and this code needs to be updated. Please file a bug with this information.");
}

@end
Expand Down Expand Up @@ -66,19 +76,19 @@ + (void)swizzleAutolayoutMethod {
// This will either turn on or turn off the feature
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
Method original = class_getInstanceMethod(objc_getClass(CStringISEngineClassName), @selector(handleUnsatisfiableRow:usingInfeasibilityHandlingBehavior:prospectiveRowHead:mutuallyExclusiveConstraints:));
Method original = class_getInstanceMethod(objc_getClass(CStringViewClassName), @selector(engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:));
#pragma clang diagnostic pop

Method swizzled = class_getInstanceMethod(self, @selector(swizzle_handleUnsatisfiableRow:usingInfeasibilityHandlingBehavior:prospectiveRowHead:mutuallyExclusiveConstraints:));
Method swizzled = class_getInstanceMethod(self, @selector(swizzle_engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:));
method_exchangeImplementations(original, swizzled);
}

- (id)swizzle_handleUnsatisfiableRow:(void *)row usingInfeasibilityHandlingBehavior:(void *)behavior prospectiveRowHead:(void *)prospectiveRowHead mutuallyExclusiveConstraints:(void *)constraints {
- (id)swizzle_engine:(void *)engine willBreakConstraint:(void *)constraints dueToMutuallyExclusiveConstraints:(void *)mutuallyExclusiveConstraints {
savedBlock();

// After running our block, we call the original implementation
// This looks like an infinite loop, but it isn't because we switched the implementations. So now, this goes to the original method.
return [self swizzle_handleUnsatisfiableRow:row usingInfeasibilityHandlingBehavior:behavior prospectiveRowHead:prospectiveRowHead mutuallyExclusiveConstraints:constraints];
return [self swizzle_engine:engine willBreakConstraint:constraints dueToMutuallyExclusiveConstraints:mutuallyExclusiveConstraints];
}

@end

0 comments on commit 1a6a3eb

Please sign in to comment.