如何在运行中的objective-c中覆盖/swizzle一个私有类的方法?[英] how to override/swizzle a method of a private class in runtime objective-c?

本文是小编为大家收集整理的关于如何在运行中的objective-c中覆盖/swizzle一个私有类的方法?的处理方法,想解了如何在运行中的objective-c中覆盖/swizzle一个私有类的方法?的问题怎么解决?如何在运行中的objective-c中覆盖/swizzle一个私有类的方法?问题的解决办法?那么可以参考本文帮助大家快速定位并解决问题。

问题描述

为了说明我问这个问题的原因:基本上我想更改 iOS 上谷歌地图的 myLocationButton 的位置.所以我首先像这样获取实际的按钮:

@implementation GMSMapView (UIChanges)

- (UIButton *)myLocationButton
{
    UIButton *myLocationButton;
    for (myLocationButton in [settingView subviews])
    {
        if ([myLocationButton isMemberOfClass:[UIButton class]])
            break;
    }
    return myLocationButton;
}

然后我尝试使用 NSLayoutConstraints 更改它在屏幕中的位置(直接更改按钮的 frame 属性的值对 google maps SDK 1.8+ 没有任何作用):

UIButton *myLocationButton = [mapView_ myLocationButton];
[myLocationButton setTranslatesAutoresizingMaskIntoConstraints:NO];
[myLocationButton constraintRightEqualTo:[myLocationButton superview] constant:0];
[myLocationButton constraintTopEqualTo:[myLocationButton superview] constant:50];

其中 constraintRightEqualTo 在类别中定义为:

- (void)constraintRightEqualTo:(UIView *)toView constant:(CGFloat)constant
{
    NSLayoutConstraint *cn = [NSLayoutConstraint constraintWithItem:self
                                                          attribute:NSLayoutAttributeRight

                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:toView
                                                          attribute:NSLayoutAttributeRight
                                                         multiplier:1 constant:constant];

    [toView addConstraint:cn];
}

到目前为止这么好?好的.

现在这在 iOS8 中工作得很好.但是当我在 iOS 7 中运行它时,它会因这个著名的错误而崩溃:

<块引用>

-[TPMURequestStatusNotificationManagermakeActionButtonResponsive]:810 - makeActionButtonResponsive2014-10-08 16:03:20.775 SmartTaxi[13009:60b] * 中的断言失败-[GMSUISettingsView layoutSublayersOfLayer:],/SourceCache/UIKit_Sim/UIKit-2935.137/UIView.m:8794 2014-10-0816:03:20.779 SmartTaxi[13009:60b] * 由于未捕获而终止应用程序异常'NSInternalInconsistencyException',原因:'自动布局执行 -layoutSubviews 后仍然需要.GMUISettingsView 的-layoutSubviews 的实现需要调用 super.'

问题是 GMSUISettingsView 没有调用 [super layoutSubviews]..

我以前见过这种错误.问题是它发生在诸如 UITableViewCell 之类的公共类上,而不是隐藏在谷歌地图 SDK 内部的私有类 GMSUISettingsViewIOS.如果它是公开的.. 我可以很容易地 swizzled 方法 layoutsubviews 在其中使用类似于 this 答案的方法.但这不是公共方法.如何在运行时更改它的 layoutsubviews 的定义来解决这个问题?(也欢迎使用更简单的方法实现相同目标的建议)

<小时>

更新

所以根据反馈 + 更多研究,我做了以下工作:

@interface AttackerClass : UIView @end
@implementation AttackerClass

- (void)_autolayout_replacementLayoutSubviews
{
    struct objc_super superTarget;
    superTarget.receiver = self;
    superTarget.super_class= class_getSuperclass(object_getClass(self));

    objc_msgSendSuper(&superTarget, @selector(layoutSubviews));
    NSLog(@":: calling send super")
    // PROBLEM: recursive call.. how do I call the *original* 
    // GMSUISettingsView implementation of layoutSubivews here?
    // replacing this with _autolayout_replacementLayoutSubviews will
    // throw an error b/c GMSUISettingsView doesn't have that method defined
    objc_msgSend(self, @selector(layoutSubviews)); 
    objc_msgSendSuper(&superTarget, @selector(layoutSubviews));
}            
@end


Method attackerMethod = class_getInstanceMethod([AttackerClass class], @selector(_autolayout_replacementLayoutSubviews));
Method victimMethod = class_getInstanceMethod(NSClassFromString(@"GMSUISettingsView"), @selector(layoutSubviews));

method_exchangeImplementations(victimMethod, attackerMethod);

这种方法的问题是,任何时候 GMSUISettingsView 调用 layoutSubviews.. 它实际上是调用 _autolayout_replacementLayoutSubviews.. 然后递归调用 GMSUISettingsView layoutsubviews.. 所以我的应用程序进入无限递归循环.this answer 通过使用类别解决了这个问题.. 但在这种情况下我不能 b/c GMSUISettingsView 是私人课程..

问同样问题的另一种方式:如何保留对未更改版本 GMSUISettingsView's layoutSubviews 的引用并在 _autolayout_replacementLayoutSubviews 中使用它,这样我就不会陷入这种递归调用问题.

想法?

推荐答案

做到了.. 我不确定这是否算作实际答案,因为我只是通过简单地调用 [self layoutIfNeeded] 而不是 [self layoutSubviews]

void _autolayout_replacementLayoutSubviews(id self, SEL _cmd)
{
    // calling super
    struct objc_super superTarget;
    superTarget.receiver = self;
    superTarget.super_class= class_getSuperclass(object_getClass(self));
    objc_msgSendSuper(&superTarget, @selector(layoutSubviews));

    // to get around calling layoutSubviews and having
    // a recursive call
    [self layoutIfNeeded];

    objc_msgSendSuper(&superTarget, @selector(layoutSubviews));
}

- (void)replaceGMSUISettingsViewImplementation
{
    class_addMethod(NSClassFromString(@"GMSUISettingsView"), @selector(_autolayout_replacementLayoutSubviews), (IMP)_autolayout_replacementLayoutSubviews, "v@:");

    Method existing = class_getInstanceMethod(NSClassFromString(@"GMSUISettingsView"), @selector(layoutSubviews));
    Method new = class_getInstanceMethod(NSClassFromString(@"GMSUISettingsView"), @selector(_autolayout_replacementLayoutSubviews));

    method_exchangeImplementations(existing, new);
}

本文地址:https://www.itbaoku.cn/post/924545.html