如何在运行时objective-c中覆盖/ swizzle私有类的方法? [英] how to override/swizzle a method of a private class in runtime objective-c?
问题描述
给出一些我为什么要问这个问题的背景:基本上我想改变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屋!