IOS开发入门之Objective-C 方法交换实践(二) - 方法指针交换
安安 2018-03-22 来源 :网络 阅读 2103 评论 0

摘要:本篇IOS开发入门教程将为大家讲解Objective-C 方法交换实践,看完这篇文章会让你对IOS编程的知识点有更加清晰的理解和运用。

本篇IOS开发入门教程将为大家讲解Objective-C 方法交换实践,看完这篇文章会让你对IOS编程的知识点有更加清晰的理解和运用。

一. 基本函数

1. 

根据 sel 得到 class 的实例方法

2. 

Method class_getInstanceMethod(Class cls, SEL name)

3. 

4. 

根据 sel 得到 class 的函数指针

5. 

IMP class_getMethodImplementation(Class cls, SEL name)

6. 

7. 

给 class 添加方法

8. 

class_addMethod(Class cls, SEL name, IMP imp, const char * types)

9. 

10. 

替换 class 的 sel 对应的函数指针,返回值为 sel 对应的原函数指针

11. 

class_replaceMethod(Class cls, SEL name, IMP imp, const char * types)

12. 

13. 

交换两个 method

14. 

method_exchangeImplementations(Method m1, Method m2)

15. 

16. 

直接替换 method 的函数指针

17. 

method_setImplementation(Method method, IMP imp)

18. 

二. 主要问题

1. 原子性操作问题

解决方案一般是在 `+(void)load`方法中处理;也可以加锁;还可以在`+(void)initialize`中去做,但是一定要注意继承的问题。

2. 改变范围超出预期

比如你可能会只想修改一个实例的方法,但实际上你修改了所有的实例方法。比如你交换的方法真实的实现是在父类中的,你的修改会影响所有的父类派生出来的类。

例如,直接使用 `method_exchangeImplementations` 方法,考虑下这种情况

    @ B

    - (void)case1

    {

        NSLog(@"case 1 B");

    }

    @end

    

    @interface C: B

    

    @property (nonatomic, copy) NSString *x;

    

    @end

    

    @implementation C

    - (void)case2

    {

        NSLog(@"case2 C %@-%@",[self class],self.x);

    }

    

    @end

    

    - (void)someMethod {

        Method a1 = class_getInstanceMethod([C class], @selector(case1));

        Method a2 = class_getInstanceMethod([C class], @selector(case2));

        method_exchangeImplementations(a1, a2);

        

        B *b = [[B alloc] init];

        [b case1];

    }

会发生什么呢?会 crash ,因为 C 作为 B 的子类并没有实现 case1 方法,方法交换会把 B 的case1 替换成 C 的 case2,后面 [b case1]  其实会执行 void _.._case2(C * self, SEL _cmd) 这个函数,里面调用 x 属性,所以 crash。

为了避免这个错误,一般的做法有,先用 class_addMethod 判断能否添加将要替换的方法,如果可以添加,说明子类原先没有实现此方法,这个方法是在父类中实现的。具体可以看参考1。

RSSwizzle 和 jrswizzle 都避免了这个问题。

3. 可能有命名冲突

比如你交换的方法很可能在别的地方(比如类别里)已经有同样命名的存在了。此时的避免方法可以是直接去替换 Method 里的函数指针,保存原有的函数指针来调用:

        typedef IMP *IMPPointer;

        

        BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {

            IMP imp = NULL;

            Method method = class_getInstanceMethod(class, original);

            if (method) {

                const char *type = method_getTypeEncoding(method);

                imp = class_replaceMethod(class, original, replacement, type);

                if (!imp) {

                    imp = method_getImplementation(method);

                }

            }

            if (imp && store) { *store = imp; }

            return (imp != NULL);

        }

        

        @implementation NSObject (FRRuntimeAdditions)

        + (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {

            return class_swizzleMethodAndStore(self, original, replacement, store);

        }

        @end

4. 可能会使用不一样的方法参数

比如同样调用原来的函数时,`_cmd`已经不一样了,解决方案可以和上面一致。

5. 类簇类的swizzling

对于 Objective-C 中的一些类簇类,比如 NSNumber、NSArray和NSMutableArray 等,因为这些并不是一个具体的类,而是一个抽象类,如果直接在这些类的内部写个方法通过self class等方式来获取 Class 并做方法交换的话,因为并不能获得其真实的类名,所以会达不到想要的效果。

比如,我们可以通过以下代码来得到NSMutableArray的真实类型:

    object_getClass([[NSMutableArray alloc] init]);

    objc_getClass("__NSArrayM");

上面代码中__NSArrayM是NSMutableArray的真实类名;

6. 子类方法调用了 super 方法,并且都做了交换

比如下面的例子就会发生循环调用。

    @ A

    - (void)log {

        NSLog(@"i am a");

    }

    

    - (void)print {

        [self print];

    }

    @end

    

    @ B

    - (void)log {

        NSLog(@"i am b");

        [super log];

    }

    

    - (void)print {

        [self print];

    }

    @end

下面做一下方法交换,并执行子类的方法。

    - (void)test {

        Method a1 = class_getInstanceMethod([A class], @selector(log));

        Method a2 = class_getInstanceMethod([A class], @selector(print));

        method_exchangeImplementations(a1, a2);

    

        Method a3 = class_getInstanceMethod([B class], @selector(log));

        Method a4 = class_getInstanceMethod([B class], @selector(print));

        method_exchangeImplementations(a3, a4);

        

        B *b = [[B alloc] init];

        [b print];

    }

方法的调用流程(用imp来表示)

    B.log - A.print - B.log....

从而形成了循环的引用。

三. 方法交换的实现

1. 直接修改 Method 的函数指针

参考2中提到的,利用 (一、1)中的方法,额外提供一个变量来存储原始的函数指针,如果需要调用原始方法,就用这个变量来主动设置 sel 参数来防止原始函数用到了_cmd 的情况

2. jrswizzle

主要用到了 method_exchangeImplementations 方法,鲁棒性上做的工作就是先做了 class_addMethod 操作。简单是很简单,然而上面所说的大部分问题他都不能避免。

3. RSSwizzle

主要用到了 class_replaceMethod 方法,避免了子类的替换影响了父类。而且对方法交换加了锁,增强了线程安全。有更多的替换选项。并且,他通过block引入了两个方法互相调用或者子类父类同时交换导致的循环问题。上面的问题几乎都可以避免。
问题是:OSSpinLock 不被建议使用了。
官方文档说他解决了method_exchangeImplementations 的限制:

1. 只有在 +load 方法中才线程安全

2. 对没有重载的方法交换会遇到非期望的结果

3. 交换的方法不能依赖 _cmd 参数 (通过 RSSwizzleInfo结构,保存原始的 selector)

4. 命名冲突


希望这篇文章可以帮助到你。总之,同学们,你想要的职坐标IOS频道都能找到!

 

本文由 @安安 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程