在iOS11上使用自带悬浮窗工具调试UI

背景介绍

iOS系统从9.0之后就加入了悬浮窗调试小工具来帮助开发者调试UI,很遗憾的是,这个是一个非公开的功能,苹果没有公开它的头文件。(私有API传送门)当然私有API没有阻挡住我们使用这么酷炫的小工具。如何使用可以看看前段时间笔者写过一片文章《iOS自带悬浮窗调试工具使用详解》。可是好景不长,在iOS11中这个小工具没法用了。最近想用这个系统自带的悬浮窗工具来调试UI,毕竟是接入成本最小UI调试工具,于是看到了国外大神的这篇文章《Swizzling in iOS 11 with UIDebuggingInformationOverlay》。

原因

国外大神的文章很长,详细介绍了他是如何让悬浮窗调试工具重现在iOS11上的。文章具体内容这里就不展开了,感兴趣的可以去看看他的文章。文章主要内容:

iOS9 & 10 上 -[UIDebuggingInformationOverlay init] 和 [UIDebuggingInformationOverlay prepareDebuggingOverlay] 是能正常工作的。在iOS11上,上面这两个方法被苹果做了限制,只有苹果内部设备才可以正常使用。对这两个方法逆向后的代码如下:

@implementation UIDebuggingInformationOverlay

- (instancetype)init {

  static BOOL overlayEnabled = NO;

  static dispatch_once_t onceToken;

  dispatch_once(&onceToken, ^{

    overlayEnabled = UIDebuggingOverlayIsEnabled();

  });

  if (!overlayEnabled) { 

    return nil;

  }

  if (self = [super init]) {

    [self _setWindowControlsStatusBarOrientation:NO];

  }

  return self;

}

+ (void)prepareDebuggingOverlay {

  if (_UIGetDebuggingOverlayEnabled()) {

    id handler = [UIDebuggingInformationOverlayInvokeGestureHandler mainHandler];

    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:handler action:@selector(_handleActivationGesture:)];

    [tapGesture setNumberOfTouchesRequired:2];

    [tapGesture setNumberOfTapsRequired:1];

    [tapGesture setDelegate:handler];

    

    UIView *statusBarWindow = [UIApp statusBarWindow];

    [statusBarWindow addGestureRecognizer:tapGesture];

  }

}

@end

可以很清晰的看到,苹果用UIDebuggingOverlayIsEnabled() 对UIDebuggingInformationOverlay的初始化方法做了检测,如果不是内部设备就返回nil,同时对prepareDebuggingOverlay方法也做了检测。

破解

既然我们都知道了方法内容,我们绕过这两个检查方法不就OK了?对的,使用Methond Swizzling 替换这两个OC的方法就好了。

国外大神也给出了一个解决方案,替换上面的两个OC方法,但是其中prepareDebuggingOverlay中添加了汇编代码,并且给出的汇编代码只支持x86_64的cpu。笔者在这个基础上重写了prepareDebuggingOverlay,发现也可以work。代码如下:

@interface UIWindow (PrivateMethods)

- (void)_setWindowControlsStatusBarOrientation:(BOOL)orientation;

@end

@interface FakeWindowClass : UIWindow

@end

@implementation FakeWindowClass

- (instancetype)initSwizzled {

    self = [super init];

    if (self) {

        [self _setWindowControlsStatusBarOrientation:NO];

    }

    return self;

}

@end

@implementation NSObject (UIDebuggingInformationOverlayEnable)

+ (void)load {

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        Class cls = NSClassFromString(@"UIDebuggingInformationOverlay");

        [FakeWindowClass swizzleSelector:@selector(init) newSelector:@selector(initSwizzled) forClass:cls isClassMethod:NO];

        [self swizzleSelector:@selector(prepareDebuggingOverlay) newSelector:@selector(prepareDebuggingOverlaySwizzled) forClass:cls isClassMethod:YES];

    });

}

+ (void)swizzleSelector:(SEL)originalSelector newSelector:(SEL)swizzledSelector forClass:(Class)class isClassMethod:(BOOL)isClassMethod {

    Method originalMethod = NULL;

    Method swizzledMethod = NULL;

    

    if (isClassMethod) {

        originalMethod = class_getClassMethod(class, originalSelector);

        swizzledMethod = class_getClassMethod([self class], swizzledSelector);

    } else {

        originalMethod = class_getInstanceMethod(class, originalSelector);

        swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);

    }

    method_exchangeImplementations(originalMethod, swizzledMethod);

}

+ (void)prepareDebuggingOverlaySwizzled {

    id overlayClass = NSClassFromString(@"UIDebuggingInformationOverlayInvokeGestureHandler");

    id handler = [overlayClass performSelector:NSSelectorFromString(@"mainHandler")];

  

    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:handler action:@selector(_handleActivationGesture:)];

    tapGesture.numberOfTouchesRequired = 2;

    tapGesture.numberOfTapsRequired = 1;

    tapGesture.delegate = handler;

  

    UIView *statusBarWindow = [[UIApplication sharedApplication] valueForKey:@"statusBarWindow"];

    [statusBarWindow addGestureRecognizer:tapGesture];

}

@end

结尾

将上面的代码放在一个文件里,引入到我们的项目中就可以在iOS11上使用苹果自带的悬浮窗UI调试工具了。这里上传了这个文件UIDebuggingTool,方便大家。笔者只测试了iOS11.0.1,欢迎大家帮忙测试下其他系统的情况并修改这个小工具。

如果UIDebuggingTool好用的话,给加个星咯~~

文章转载请注明出处:wellphone.me

在iOS11上使用自带悬浮窗工具调试UI