Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/objective-c/23.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 如何在运行时objective-c中重写/swizzle私有类的方法?_Ios_Objective C_Iphone_Google Maps Sdk Ios_Objective C Runtime - Fatal编程技术网

Ios 如何在运行时objective-c中重写/swizzle私有类的方法?

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]) {

让我们了解一下我为什么要问这个问题:基本上,我想在iOS上更改google地图的
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/c
gmsuissettingsview
是一个私有类

问同样问题的另一种方法是:如何保留对
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
发送到objects
super
类,那么您不能用
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)
崩溃..嘿,看看我更新的问题..现在我陷入了一个递归的callUse
objc\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);
}