Runtime Swizzling方法使用补充材料

原文地址,点击打开

之前简单学习了Runtime中的移魂大法,swizzling,他可以将两个方法的imp指针进行对换,调用方法A,实际执行方法B,反之亦然。这个方法的使用可以看另一篇博客,很简单,就不在赘述。本片主要侧重于swizzling的一些技术细节以及对其理解,下面是对原文的整理,方便温故。

这里是一些关于 Method Swizzling的讨论:

  • Method swizzling is not atomic
  • Changes behavior of un-owned code
  • Possible naming conflicts
  • Swizzling changes the method's arguments
  • The order of swizzles matters
  • Difficult to understand (looks recursive)
  • Difficult to debug
依次分析如下

Method swizzling is not atomic


我所见过的使用method swizzling实现的方法在并发使用时基本都是安全的。95%的情况里这都不会是个问题。通常你替换一个方法的实现,是希望它在整个程序的生命周期里有效的。也就是说,你会把 method swizzling 修改方法实现的操作放在一个加号方法 +(void)load里,并在应用程序的一开始就调用执行。你将不会碰到并发问题。假如你在 +(void)initialize初始化方法中进行swizzle,那么……rumtime可能死于一个诡异的状态。

PS:swizzing方法不是线程安全的的。
swizzling方法只是交换了两个方法的IMP指针我们通常调用swizzing都是在类的category的+(void)load方法中,他在app加载是就已运行,不会牵扯到多线程的问题。如果写入initialize方法,则不确定。

Changes behavior of un-owned code


这是swizzling的一个问题。我们的目标是改变某些代码。swizzling方法是一件灰常灰常重要的事,当你不只是对一个NSButton类的实例进行了修改,而是程序中所有的NSButton实例。因此在swizzling时应该多加小心,但也不用总是去刻意避免。


想象一下,如果你重写了一个类的方法,而且没有调用父类的这个方法,这可能会引起问题。大多数情况下,父类方法期望会被调用(至少文档是这样说的)。如果你在swizzling实现中也这样做了,这会避免大部分问题。还是调用原始实现吧,如若不然,你会费很大力气去考虑代码的安全问题。

PS:swizzling方法是全局起作用的,如果不是想让绝大多数同类实现给方法,就不要轻易使用swizzling,好钢用在刀刃上,否则得不偿失,大量你不想让实现该方法的类中你需要加相关代码,也会浪费大量力气去考虑代码规范的事情。例如,你想检测程序,让其每个VC都会自动打印日志,则可以用该方法,简洁高效;

Possible naming conflicts


命名冲突贯穿整个Cocoa的问题. 我们常常在类名和类别方法名前加上前缀。不幸的是,命名冲突仍是个折磨。但是swizzling其实也不必过多考虑这个问题。我们只需要在原始方法命名前做小小的改动来命名就好,比如通常我们这样命名

    @interface NSView : NSObject  
    - (void)setFrame:(NSRect)frame;  
    @end     
    @implementation NSView (MyViewAdditions)  
    - (void)my_setFrame:(NSRect)frame {  
        // do custom work  
        [self my_setFrame:frame];  
    }  
    + (void)load {  
        [self swizzle:@selector(setFrame:) with:@selector(my_setFrame:)];  
    }  
    @end  

这段代码运行正确,但是如果my_setFrame: 在别处被定义了会发生什么呢?

这个问题不仅仅存在于swizzling,这里有一个替代的变通方法:

    @implementation NSView (MyViewAdditions)  
    static void MySetFrame(id self, SEL _cmd, NSRect frame);  
    static void (*SetFrameIMP)(id self, SEL _cmd, NSRect frame);  
    static void MySetFrame(id self, SEL _cmd, NSRect frame) {  
        // do custom work  
        SetFrameIMP(self, _cmd, frame);  
    }  
    + (void)load {  
        [self swizzle:@selector(setFrame:) with:(IMP)MySetFrame store:(IMP *)&SetFrameIMP];  
    }  
    @end  
这样确实能解决明明重复的问题,但是用了函数指针,看起来不那么OC了,

完美方案如下:

 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 

Swizzling changes the method's arguments



我认为这是最大的问题。想正常调用method swizzling 将会是个问题。

  1. [self my_setFrame:frame];  


直接调用my_setFrame: , runtime做的是

  1. objc_msgSend(self, @selector(my_setFrame:), frame);  

runtime去寻找my_setFrame:的方法实现, _cmd参数为 my_setFrame: ,但是事实上runtime找到的方法实现是原始的 setFrame: 的。

一个简单的解决办法:使用上面介绍的swizzling定义。




The order of swizzles matters



多个swizzle方法的执行顺序也需要注意。假设 setFrame: 只定义在NSView中,想像一下按照下面的顺序执行:
  1. [NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];  
  2. [NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];  
  3. [NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];  

What happens when the method on NSButton is swizzled? Well most swizzling will ensure that it's not replacing the implementation of setFrame: for all views, so it will pull up the instance method. This will use the existing implementation to re-define setFrame: in the NSButton class so that exchanging implementations doesn't affect all views. The existing implementation is the one defined on NSView. The same thing will happen when swizzling on NSControl (again using the NSView implementation).

When you call setFrame: on a button, it will therefore call your swizzled method, and then jump straight to the setFrame: method originally defined on NSView. The NSControl and NSView swizzled implementations will not be called.

But what if the order were:
  1. [NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];  
  2. [NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];  
  3. [NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];  

Since the view swizzling takes place first, the control swizzling will be able to pull up the right method. Likewise, since the control swizzling was before the button swizzling, the button will pull up the control's swizzled implementation of setFrame:. This is a bit confusing, but this is the correct order. How can we ensure this order of things?

Again, just use load to swizzle things. If you swizzle in load and you only make changes to the class being loaded, you'll be safe. The load method guarantees that the super class load method will be called before any subclasses. We'll get the exact right order!


这段贴了原文,硬翻译太拗口……总结一下就是: 多个有继承关系的类的对象swizzle时,从子类对象开始 。 如果先swizzle父类对象,那么后面子类对象swizzle时就无法拿到真正的原始方法实现了。 


(感谢评论中 qq373127202 的提醒,在此更正一下,十分感谢

多个有继承关系的类的对象swizzle时,先从父对象开始。 这样才能保证子类方法拿到父类中的被swizzle的实现。在+(void)load中swizzle不会出错,就是因为load类方法会默认从父类开始调用。



Difficult to understand (looks recursive)


(新方法的实现)看起来像递归,但是看看上面已经给出的 swizzling 封装方法, 使用起来就很易读懂.
这个问题是已完全解决的了!




Difficult to debug


debug时打出的backtrace,其中掺杂着被swizzle的方法名,一团糟啊!上面介绍的swizzle方案,使backtrace中打印出的方法名还是很清晰的。但仍然很难去debug,因为很难记住swizzling影响过什么。给你的代码写好文档(即使只有你一个人会看到)。养成一个好习惯,不会比调试多线程问题还难的。




结论


如果使用恰当,Method swizzling 还是很安全的.一个简单安全的方法是,仅在load中swizzle。 和许多其他东西一样,它也是有危险性的,但理解它了也就可以正确恰当的使用它了。






考虑柔性负荷的综合能源系统低碳经济优化调度【考虑碳交易机制】(Matlab代码实现)内容概要:本文围绕“考虑柔性负荷的综合能源系统低碳经济优化调度”展开,重点研究在碳交易机制下如何实现综合能源系统的低碳化与经济性协同优化。通过构建包含风电、光伏、储能、柔性负荷等多种能源形式的系统模型,结合碳交易成本与能源调度成本,提出优化调度策略,以降低碳排放并提升系统运行经济性。文中采用Matlab进行仿真代码实现,验证了所提模型在平衡能源供需、平抑可再生能源波动、引导柔性负荷参与调度等方面的有效性,为低碳能源系统的设计与运行提供了技术支撑。; 适合人群:具备一定电力系统、能源系统背景,熟悉Matlab编程,从事能源优化、低碳调度、综合能源系统等相关领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①研究碳交易机制对综合能源系统调度决策的影响;②实现柔性负荷在削峰填谷、促进可再生能源消纳中的作用;③掌握基于Matlab的能源系统建模与优化求解方法;④为实际综合能源项目提供低碳经济调度方案参考。; 阅读建议:建议读者结合Matlab代码深入理解模型构建与求解过程,重点关注目标函数设计、约束条件设置及碳交易成本的量化方式,可进一步扩展至多能互补、需求响应等场景进行二次开发与仿真验证。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值