Ios 如何在运行时objective-c中重写/swizzle私有类的方法?
让我们了解一下我为什么要问这个问题:基本上,我想在iOS上更改google地图的Ios 如何在运行时objective-c中重写/swizzle私有类的方法?,ios,objective-c,iphone,google-maps-sdk-ios,objective-c-runtime,Ios,Objective C,Iphone,Google Maps Sdk Ios,Objective C Runtime,让我们了解一下我为什么要问这个问题:基本上,我想在iOS上更改google地图的myLocationButton的位置。因此,我首先获取实际按钮,如下所示: @implementation GMSMapView (UIChanges) - (UIButton *)myLocationButton { UIButton *myLocationButton; for (myLocationButton in [settingView subviews]) {
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+无关:
其中,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
MakeActionButtonResponse]:810-MakeActionButtonResponse
2014-10-08 16:03:20.775 SmartTaxic[13009:60b]*中的断言失败
-[GMSUISettingsView layer的布局子层:],/SourceCache/UIKit_Sim/UIKit-2935.137/UIView.m:8794 2014-10-08
16:03:20.779智能出租车[13009:60b]*由于未捕获而终止应用程序
异常“NSInternalInconsistencyException”,原因:“自动布局”
执行-layoutSubviews后仍然需要。GMSUI设置视图
-layoutsubview的实现需要调用super
问题是GMSUISettingsView
没有调用[超级布局子视图]
我以前见过这种错误。。问题是,它发生在诸如UITableViewCell
之类的公共类上,而不是隐藏在google maps SDK for iOS内核中的这个私有类GMSUISettingsView
。如果是公开的。。我可以使用类似于answer的方法轻松地将方法layoutsubviews
放入其中。但这不是一种公开的方法。如何在运行时更改它的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
。。它实际上是在调用\u autolayout\u replacementlayoutsubview
。。然后递归调用GMSUISettingsView
layoutsubviews。。因此,我的应用程序进入无限递归循环。通过使用类别解决了该问题。。但是我不能在这种情况下b/cgmsuissettingsview
是一个私有类
问同样问题的另一种方法是:如何保留对GMSUISettingsView的layoutSubviews的未更改版本的引用,并在\u autolayout\u replacement layoutSubviews
中使用它,这样我就不会陷入这个递归调用问题
想法?更新
如果您想保留原始实现,可以获取它并在blockImp中调用它。然后在调用[[aClass superclass]layoutSubviews]或使用函数指针调用它之后。请注意,所有这些代码都需要一些错误检查和异常预防
//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);
原始答案
很抱歉,我没有完全理解您是要完全覆盖“LayoutSubView”,还是要使用原始实现并仅更改其中的一部分。如果这是第一次你可以用它来代替刷牙。它将有性能点击虽然!像这样的事情应该可以做到:
//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);
我没有在设备/模拟器上测试过,但类似的东西应该适合你。我想你知道操纵私有类并不是一个好主意;) 成功了。。我不确定这是否算是一个实际的答案,因为我只是通过调用[self layoutfneed]
而不是[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);
}
第一段代码有缺陷。它总是会返回一些东西,而这些东西并不总是UIButton
或nil
,这是我想象的预期语义。更不用说未初始化的变量和使用isMemberOfClass
而不是isKindOfClass
@Droppy作为参数。。让我们假设它没有bug,并且它可靠地返回我所期望的结果。如果您只需要将layoutSubviews
发送到objectssuper
类,那么您不能用objc\u msgsgsendsuper
而不是swizzling来执行此操作吗?@BradAllred告诉我更多信息?你能给我举一个使用objc_msgSendSuper的代码示例吗?这个方法在iOS运行库中似乎不可用。。只有在osx运行时如果操纵私有类不是一个好主意,那么请建议一种更好的方法来解决我上面所面临的问题,而不操纵私有类。如何确保blockImp
仍然像原始layoutSubviews
那样。。但也可以通过添加[super layoutsubviews]来执行此操作。上面的aSelector返回此aSelector SEL”(\xae\xff\xbf/\xa1\xfa\x03/4\x13\xe4\xfa\xa9\x01(“0xbffae08
)。因此调用旧实现(aClass,aSelector)
崩溃..嘿,看看我更新的问题..现在我陷入了一个递归的callUseobjc\u msgSend
发送@选择器(\u autolayout\u replacementlayoutsubview)
到self
(而不是调用[self-layoutfneed]
)这具有[self\u autolayout\u replacementlayoutsubview]的效果
您已将其标识为所需的行为。
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);
}