Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

为啥交换 class method 和交换 instance method 的实现有差异? #14

Open
whihail opened this issue Jun 8, 2018 · 14 comments
Open

Comments

@whihail
Copy link

whihail commented Jun 8, 2018

+ (void)swizzleClassMethod:(SEL)origSelector withMethod:(SEL)newSelector
{
    Class cls = [self class];
    
    Method originalMethod = class_getClassMethod(cls, origSelector);
    Method swizzledMethod = class_getClassMethod(cls, newSelector);
    
    Class metacls = objc_getMetaClass(NSStringFromClass(cls).UTF8String);
    if (class_addMethod(metacls,
                        origSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod)) ) {
        /* swizzing super class method, added if not exist */
        class_replaceMethod(metacls,
                            newSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
        
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}
- (void)swizzleInstanceMethod:(SEL)origSelector withMethod:(SEL)newSelector
{
    Class cls = [self class];
    /* if current class not exist selector, then get super*/
    Method originalMethod = class_getInstanceMethod(cls, origSelector);
    Method swizzledMethod = class_getInstanceMethod(cls, newSelector);
    /* add selector if not exist, implement append with method */
    if (class_addMethod(cls,
                        origSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod)) ) {
        /* replace class instance method, added if selector not exist */
        /* for class cluster , it always add new selector here */
        class_replaceMethod(cls,
                            newSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
        
    } else {
        /* swizzleMethod maybe belong to super */
        class_replaceMethod(cls,
                            newSelector,
                            class_replaceMethod(cls,
                                                origSelector,
                                                method_getImplementation(swizzledMethod),
                                                method_getTypeEncoding(swizzledMethod)),
                            method_getTypeEncoding(originalMethod));
    }
}

为什么交换实例方法的时候需要

class_replaceMethod(cls,
                            newSelector,
                            class_replaceMethod(cls,
                                                origSelector,
                                                method_getImplementation(swizzledMethod),
                                                method_getTypeEncoding(swizzledMethod)),
                            method_getTypeEncoding(originalMethod));

而交换类方法是只需要

method_exchangeImplementations(originalMethod, swizzledMethod);

看了许久,希望能得到帮助和理解

@jasenhuang
Copy link
Owner

jasenhuang commented Jun 8, 2018

@whihail
实际上 交换实例方法 的写法才是正确的,交换类方法是改漏了。
考虑到类继承关系,我们hook的方法有可能在不同子类和父类都有实现,method_exchangeImplementations 会直接修改修改到父类的IMP,多次swizzleInstanceMethod会乱。
可以更新一下 ,参数下 NSObjectSafeTests.m 的测试用例

`@interface Base : NSObject

  • (void)print:(NSString*)msg;
    @EnD
    @implementation Base
  • (void)print:(NSString*)msg
    {
    NSLog(@"Base obj %@ print say:%@", NSStringFromClass(self.class), msg);
    }
  • (void)hookPrint:(NSString*)msg {
    NSLog(@"hook obj %@ print say:%@", NSStringFromClass(self.class), msg);
    }
  • (void)printClass:(NSString*)msg
    {
    NSLog(@"Base class %@ print say:%@", NSStringFromClass(self.class), msg);
    }
  • (void)hookPrintClass:(NSString*)msg {
    NSLog(@"hook class %@ print say:%@", NSStringFromClass(self.class), msg);
    }
    @EnD

@interface A : Base
@EnD
@implementation A

  • (void)print:(NSString*)msg {
    NSLog(@"A obj print say:%@", msg);
    }
  • (void)printClass:(NSString*)msg {
    NSLog(@"A class print say:%@", msg);
    }

@EnD

@interface B : Base
@EnD
@implementation B
@end`

@whihail
Copy link
Author

whihail commented Jun 8, 2018

多谢

@yulingtianxia
Copy link

原因在于 class_getInstanceMethod 和 class_getClassMethod 方法吧,当子类没有自己实现方法而是继承父类的时候,这俩方法拿到的是父类的 Method 对象。

而有趣的是当 class_addMethod 方法返回 NO 的时候,说明子类自己已经实现了方法,就不存在修改父类 IMP 的情况了。无需多此一举。

类也是对象,类方法和实例方法本质上是同样的。这种区分毫无必要。

PS:我的文章下面有评论引用了这个 issue,我觉得会误导人,所以过来评论了下。http://yulingtianxia.com/blog/2017/04/17/Objective-C-Method-Swizzling/

@jasenhuang
Copy link
Owner

@yulingtianxia 实际上你和我说的是同个意思,而且我在上面的回复已经说明了,并没有区分必要。

@yulingtianxia
Copy link

“实际上 交换实例方法 的写法才是正确的,交换类方法是改漏了”
这句话明显有问题呀。可能issue里贴的是修改后的代码?

@whihail
Copy link
Author

whihail commented Dec 26, 2018

issue里贴的是修改前的代码,其实你和我们说的都是同个意思。

@jasenhuang
Copy link
Owner

@yulingtianxia execuse me?

@yulingtianxia
Copy link

我的理解是 else 分支里两种写法都 OK,不存在 『实际上 交换实例方法 的写法才是正确的,交换类方法是改漏了。考虑到类继承关系,我们hook的方法有可能在不同子类和父类都有实现,method_exchangeImplementations 会直接修改修改到父类的IMP,多次swizzleInstanceMethod会乱。』这句话所描述的问题。

@whihail
Copy link
Author

whihail commented Dec 26, 2018

@yulingtianxia 你的理解错了,具体可以参考一下 NSObjectSafeTests.m 的测试用例。

@yulingtianxia
Copy link

看了下 NSObjectSafeTests.m 的两个 wrongSwizzleXXXMethod 方法,用的都是 method_exchangeImplementations。通过方法名字暗示实现是错的么?

所以想请教下下面这段话里为啥说交换类方法改漏了,交换实例方法才是正确的。(PS:交换实例方法用的是 class_replaceMethod ,NSObjectSafeTests.m 里用的写法是交换类方法的写法。)

实际上 交换实例方法 的写法才是正确的,交换类方法是改漏了。
考虑到类继承关系,我们hook的方法有可能在不同子类和父类都有实现,>method_exchangeImplementations 会直接修改修改到父类的IMP,多次swizzleInstanceMethod会乱。
可以更新一下 ,参数下 NSObjectSafeTests.m 的测试用例

顺便说下:

  1. 无论是hook实例方法还是类方法,本质上是一样的,可以统一成一个方法。
  2. else 分支里是不会修改到父类的 IMP 的,所以两种写法都 OK
  3. 修改到父类 IMP 的根源不是 method_exchangeImplementations ,而是前面 class_getInstanceMethod 和 class_getClassMethod 的调用可能会获取到父类 Method。

@whihail
Copy link
Author

whihail commented Jun 3, 2019

@yulingtianxia 不好意思,最近几个月挺忙,github逛得少,没来得及回复。
我的意思是作者之前的代码中交换类和实例的方式不一样,交换类方法是因为作者忘记改好所以有误,其实类和实例这两者在交换方法方面没有任何区别,现在的代码都已经改成了正确的写法。
至于 NSObjectSafeTests.m 的两个 wrongSwizzleXXXMethod 方法,用的都是method_exchangeImplementations,是因为这是错误的写法,测试用例的作用就是需要把错误暴露出来,以此证明错误的存在,而不是想通过方法名暗示实现是错的。

看了下 NSObjectSafeTests.m 的两个 wrongSwizzleXXXMethod 方法,用的都是 method_exchangeImplementations。通过方法名字暗示实现是错的么?

所以想请教下下面这段话里为啥说交换类方法改漏了,交换实例方法才是正确的。(PS:交换实例方法用的是 class_replaceMethod ,NSObjectSafeTests.m 里用的写法是交换类方法的写法。)

实际上 交换实例方法 的写法才是正确的,交换类方法是改漏了。
考虑到类继承关系,我们hook的方法有可能在不同子类和父类都有实现,>method_exchangeImplementations 会直接修改修改到父类的IMP,多次swizzleInstanceMethod会乱。
可以更新一下 ,参数下 NSObjectSafeTests.m 的测试用例

顺便说下:

  1. 无论是hook实例方法还是类方法,本质上是一样的,可以统一成一个方法。
  2. else 分支里是不会修改到父类的 IMP 的,所以两种写法都 OK
  3. 修改到父类 IMP 的根源不是 method_exchangeImplementations ,而是前面 class_getInstanceMethod 和 class_getClassMethod 的调用可能会获取到父类 Method。

@yulingtianxia
Copy link

@whihail 我觉得我强调多少遍都没有用了,都不仔细看啊,那就这样吧。

@whihail
Copy link
Author

whihail commented Jun 3, 2019

@yulingtianxia 其实我和作者也强调多次了,你做事想问题有些偏激,没有耐心。

@yulingtianxia
Copy link

@whihail 好吧,就事论事,莫人身攻击。因为第三点的内容我纠正好几次了,但是都被忽略了。只能先这样了,可能理解不同吧。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants