如何覆盖/ 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过,但是这样的东西应该适合你。 我假设你知道操纵私人类并不是一个好主意;)