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

查看:92
本文介绍了如何在运行时objective-c中覆盖/ swizzle私有类的方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

给出一些我为什么要问这个问题的背景:基本上我想改变iOS上谷歌地图的 myLocationButton 的位置。所以我首先按如下方式获取实际按钮:

To give a bit of context of why I'm asking this: basically I would like to change the location of the myLocationButton of the google map on iOS. So I first fetch the actual button like so:

@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+没有任何关系:

Then I try to change it's position in the screen using NSLayoutConstraints (directly changing values of the frame property of the button does nothing with 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];
}

到目前为止还不错?好的。

so far so good? ok.

现在这在iOS8中运行得很好..但是当我在iOS 7中运行时,它崩溃了这个着名的错误:

Now this works just fine in iOS8.. however when I run this in iOS 7 it crashes with this famous error:


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

-[TPMURequestStatusNotificationManager makeActionButtonResponsive]:810 - makeActionButtonResponsive 2014-10-08 16:03:20.775 SmartTaxi[13009:60b] * Assertion failure in -[GMSUISettingsView layoutSublayersOfLayer:], /SourceCache/UIKit_Sim/UIKit-2935.137/UIView.m:8794 2014-10-08 16:03:20.779 SmartTaxi[13009:60b] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. GMSUISettingsView's implementation of -layoutSubviews needs to call super.'

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

the problem is that GMSUISettingsView is not calling [super layoutSubviews]..

我见过这种错误之前..事情是它发生在公共类,如 UITableViewCell ,而不是这个私人 GMSUISettingsView 这隐藏在谷歌地图SDK for iOS的内容中。如果它是公开的..我可以轻松调动方法 layoutsubviews 使用类似于这个答案的方法。但这不是一种公共方法。我怎样才能在运行时更改它的 layoutsubviews 的定义来解决这个问题? (也欢迎使用更简单方法实现相同目标的建议)

I've seen this kind of error before.. the thing is that it happens with public classes such as UITableViewCell, as opposed to this private one GMSUISettingsView which is hidden in the guts of the google maps SDK for iOS. Had it been public.. i could have easily swizzled the method layoutsubviews inside of it using an approach similar to this answer. But it's not a public method. How can I in runtime change the definition of it's layoutsubviews to get around this problem? (suggestions to accomplish the same goal using simpler methods are also welcome)

更新

所以基于反馈+更多的研究,我做了以下几点:

so based on feedback + a bit more research I did the following:

@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 ..所以我的应用程序进入无限递归循环。 这个答案通过使用类别解决了这个问题..但我不能在这种情况下使用b / c GMSUISettingsView 是一个私有类..

The problem with this approach is that anytime GMSUISettingsView calls layoutSubviews.. it is in effect calling _autolayout_replacementLayoutSubviews.. which then recursively calls GMSUISettingsView layoutsubviews.. so my app goes into an infinite recursive loop. this answer solved that problem by using a category.. but I can't in this case b/c GMSUISettingsView is a private class..

另一种提出相同问题的方式:我如何保留对未更改版本的引用? GMSUISettingsView的layoutSubviews 并在 _autolayout_replacementLayoutSubviews 中使用它,这样我就不会遇到这种递归调用问题。

another way of asking the same question: how can i retain a reference to the unaltered version of GMSUISettingsView's layoutSubviews and use it in _autolayout_replacementLayoutSubviews so that I don't fall into this recursive call problem.

想法?

推荐答案

这样做了..我不确定这是不是算作一个真正的答案,因为我只是通过简单地调用 [self layoutIfNeeded] 而不是 [self layoutSubviews]

this did it.. i'm not sure if this counts as an actual answer since i just got around the problem by simply calling [self layoutIfNeeded] instead of [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);
}

这篇关于如何在运行时objective-c中覆盖/ swizzle私有类的方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆