how to override/swizzle a method of a private class in runtime objective-c?

后端 未结 2 805
温柔的废话
温柔的废话 2021-01-07 04:37

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 fet

相关标签:
2条回答
  • 2021-01-07 05:33

    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);
    }
    
    0 讨论(0)
  • 2021-01-07 05:37

    UPDATE

    If you want to preserve original implementation you can get it and call it inside the blockImp. Then after that call [[aClass superclass] layoutSubviews] or call it with function pointer. Note that all this code need some error check and exception prevents.

    //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);
    

    ORIGINAL ANSWER

    I'm sorry but I didn't fully understand do you want to completely override 'layoutsubviews' or to have original implementation and change just part of it. If it's the first you could use class_replaceMethod instead of swizzling. It will have performance hits though! Something like this should do the trick:

    //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);
    

    I've not tested this on device/simulator but something like this should work for you. I assume you know it's not a really bright idea to manipulate private classes ;)

    0 讨论(0)
提交回复
热议问题