如何覆盖/ swizzle在运行时的私人类的方法objective-c?

给我一点为什么我问这个问题的背景:基本上我想改变iOS的谷歌地图的myLocationButton的位置。 所以我首先获取像这样的实际button:

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

然后,我尝试改变它在屏幕上的位置使用NSLayoutConstraints(直接更改button的frame属性的值没有任何与谷歌地图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中运行它时,它会崩溃,出现这个着名的错误:

– [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] *由于未捕获exception'NSInternalInconsistencyException',原因:终止应用程序执行-layoutSubviews后仍然需要自动布局。 GMSUISettingsView的-layoutSubviews实现需要调用super。

问题是GMSUISettingsView不调用[super layoutSubviews] ..

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


UPDATE

所以根据反馈+多一点研究我做了以下几点:

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

另一种方式提出相同的问题:我如何保留对GMSUISettingsView's layoutSubviews的未GMSUISettingsView's layoutSubviews版本的引用,并在_autolayout_replacementLayoutSubviews使用它,这样我就不会陷入这个recursion调用问题。

想法?

这是做的..我不知道这是否算作一个实际的答案,因为我只是通过调用[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); } 

UPDATE

如果你想保留原始的实现,你可以得到它,并在blockImp中调用它。 然后调用[[aClass superclass] layoutSubviews]或者用函数指针调用它。 请注意,所有这些代码需要一些错误检查,并防止exception。

 //get method encoding const char * encoding = method_getTypeEncoding(class_getInstanceMethod(NSClassFromString(@"GMSUISettingsView"), @selector(layoutSubviews))); void(^oldImplementation)(id aClass, SEL aSelector) = imp_getBlock(method_getImplementation(class_getInstanceMethod(NSClassFromString(@"GMSUISettingsView"), @selector(layoutSubviews)))); id blockImp = ^void(id aClass, SEL aSelector) { //old imlpementation oldImplementation(aClass, aSelector); //calling [super layoutSubviews] IMP aImp = [[aClass superclass] methodForSelector:@selector(layoutSubviews)]; id (*func)(id, SEL) = (void *)aImp; func([aClass superclass], @selector(layoutSubviews)); }; IMP newImp = imp_implementationWithBlock(blockImp); class_replaceMethod(NSClassFromString(@"GMSUISettingsView"), @selector(layoutSubviews), newImp, encoding); 

原来的答案

我很抱歉,但我不完全明白,你想彻底重写'layoutsubviews'或有原始的实现,只是其中的一部分。 如果这是第一次,你可以使用class_replaceMethod而不是滚动 。 它会有性能点击! 像这样的事情应该做的伎俩:

 //get method encoding const char * encoding = method_getTypeEncoding(class_getInstanceMethod(NSClassFromString(@"GMSUISettingsView"), @selector(layoutSubviews))); id blockImp = ^void(id aClass, SEL aSelector) { //what you want to happen in layout subviews }; IMP newImp = imp_implementationWithBlock(blockImp); class_replaceMethod(NSClassFromString(@"GMSUISettingsView"), @selector(layoutSubviews), newImp, encoding); 

我没有在设备/模拟器上testing过,但是这样的东西应该适合你。 我假设你知道操纵私人类并不是一个好主意;)