NSObjectSafe icon indicating copy to clipboard operation
NSObjectSafe copied to clipboard

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

Open whihail opened this issue 6 years ago • 14 comments

+ (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);

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

whihail avatar Jun 08 '18 00:06 whihail

@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`

jasenhuang avatar Jun 08 '18 05:06 jasenhuang

多谢

whihail avatar Jun 08 '18 07:06 whihail

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

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

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

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

yulingtianxia avatar Dec 25 '18 04:12 yulingtianxia

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

jasenhuang avatar Dec 25 '18 17:12 jasenhuang

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

yulingtianxia avatar Dec 26 '18 02:12 yulingtianxia

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

whihail avatar Dec 26 '18 02:12 whihail

@yulingtianxia execuse me?

jasenhuang avatar Dec 26 '18 02:12 jasenhuang

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

yulingtianxia avatar Dec 26 '18 02:12 yulingtianxia

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

whihail avatar Dec 26 '18 02:12 whihail

看了下 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 avatar Dec 26 '18 03:12 yulingtianxia

@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。

whihail avatar Jun 03 '19 03:06 whihail

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

yulingtianxia avatar Jun 03 '19 03:06 yulingtianxia

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

whihail avatar Jun 03 '19 03:06 whihail

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

yulingtianxia avatar Jun 03 '19 03:06 yulingtianxia