|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
Model的改变最好通过Notification来传播之前吃过这样的亏最好不要用delegate模式)UIViewController在没有一个类的完成源码的情形下,想改动个中一个办法的完成,除承继它重写、和借助种别重名办法暴力争先以外,另有加倍天真的办法吗?在Objective-C编程中,怎样完成hook呢?题目有点年夜,企图分几篇来总结。
本文次要先容针对selector的hook,配角被题目剧透了————MethodSwizzling。
MethodSwizzling道理
在Objective-C中挪用一个办法,实际上是向一个对象发送动静,查找动静的独一根据是selector的名字。使用Objective-C的静态特征,能够完成在运转时掉包selector对应的办法完成,到达给办法挂钩的目标。
每一个类都有一个办法列表,寄存着selector的名字和办法完成的映照干系。IMP有点相似函数指针,指向详细的Method完成。
<br>
我们能够使用method_exchangeImplementations来互换2个办法中的IMP,
我们能够使用class_replaceMethod来修正类,
我们能够使用method_setImplementation来间接设置某个办法的IMP,
……
归根结柢,都是掉包了selector的IMP,以下图所示:
<br>
MethodSwizzling理论
举个例子好了,我想钩一下NSArray的lastObject办法,只需两个步骤。
第一步:给NSArray加一个我本人的lastObject
- #import"NSArray+Swizzle.h"
- @implementationNSArray(Swizzle)
- -(id)myLastObject
- {
- idret=[selfmyLastObject];
- NSLog(@"**********myLastObject***********");
- returnret;
- }
- @end
乍一看,这不递回了么?别健忘这是我们筹办互换IMP的selector,[selfmyLastObject]将会实行真的[selflastObject]。
第二步:互换IMP
- #import<objc/runtime.h>
- #import"NSArray+Swizzle.h"
- intmain(intargc,char*argv[])
- {
- @autoreleasepool{
- Methodori_Method=class_getInstanceMethod([NSArrayclass],@selector(lastObject));
- Methodmy_Method=class_getInstanceMethod([NSArrayclass],@selector(myLastObject));
- method_exchangeImplementations(ori_Method,my_Method);
- NSArray*array=@[@"0",@"1",@"2",@"3"];
- NSString*string=[arraylastObject];
- NSLog(@"TESTRESULT:%@",string);
- return0;
- }
- }
把持台输入Log:
- 2013-07-1816:26:12.585Hook[1740:c07]**********myLastObject***********
- 2013-07-1816:26:12.589Hook[1740:c07]TESTRESULT:3
了局很让人欣喜,是否是不由得想给UIWebView的loadRequest:加TODO了呢?
MethodSwizzling的封装
之前在github上找到的RNSwizzle,保举给人人,能够搜一下。
- //
- //RNSwizzle.m
- //MethodSwizzle
- #import"RNSwizzle.h"
- #import<objc/runtime.h>
- @implementationNSObject(RNSwizzle)
- +(IMP)swizzleSelector:(SEL)origSelector
- withIMP:(IMP)newIMP{
- Classclass=[selfclass];
- MethodorigMethod=class_getInstanceMethod(class,
- origSelector);
- IMPorigIMP=method_getImplementation(origMethod);
- if(!class_addMethod(self,origSelector,newIMP,
- method_getTypeEncoding(origMethod)))
- {
- method_setImplementation(origMethod,newIMP);
- }
- returnorigIMP;
- }
- @end
MethodSwizzling伤害不伤害
针对这个成绩,我在stackoverflow上看到了中意的谜底,这里翻译一下,总结纪录在本文中,以示分享:
利用MethodSwizzling编程就比如切菜时利用厉害的刀,一些人由于忧虑切到本人以是惧怕厉害的刀具,但是现实上,利用钝刀常常更简单失事,而利刀更加平安。
Methodswizzling能够匡助我们写出更好的,更高效的,易保护的代码。可是假如滥用它,也将会招致难以排查的bug。
背景
比如计划形式,假如我们摸清了一个形式的门道,利用该形式与否我们本人冷暖自知。单例形式就是一个很好的例子,它饱受争议可是很多人仍旧利用它。MethodSwizzling也是一样,一旦你真正了解它的上风和坏处,利用它与否你应当就有你本人的概念。
会商
这里是一些MethodSwizzling的圈套:
- Methodswizzlingisnotatomic
- Changesbehaviorofun-ownedcode
- Possiblenamingconflicts
- Swizzlingchangesthemethodsarguments
- Theorderofswizzlesmatters
- Difficulttounderstand(looksrecursive)
- Difficulttodebug
我将一一剖析这些点,促进对MethodSwizzling的了解的同时,并弄懂怎样应对。
Methodswizzlingisnotatomic
我所见过的利用methodswizzling完成的办法在并发利用时基础都是平安的。95%的情形里这都不会是个成绩。一般你交换一个办法的完成,是但愿它在全部程序的性命周期里无效的。也就是说,你会把methodswizzling修正办法完成的操纵放在一个加号办法+(void)load里,并在使用程序的一入手下手就挪用实行。你将不会碰着并提问题。假设你在+(void)initialize初始化办法中举行swizzle,那末……rumtime大概逝世于一个诡异的形态。
Changesbehaviorofun-ownedcode
这是swizzling的一个成绩。我们的方针是改动某些代码。swizzling办法是一件灰常灰常主要的事,当你不但是对一个NSButton类的实例举行了修正,而是程序中一切的NSButton实例。因而在swizzling时应当多加当心,但也不必老是往决心制止。
设想一下,假如你重写了一个类的办法,并且没有挪用父类的这个办法,这大概会引发成绩。年夜多半情形下,父类办法希冀会被挪用(最少文档是如许说的)。假如你在swizzling完成中也如许做了,这会制止年夜部分成绩。仍是挪用原始完成吧,如若否则,你会费很鼎力气往思索代码的平安成绩。
Possiblenamingconflicts
定名抵触贯串全部Cocoa的成绩.我们经常在类名和种别办法名前加上前缀。不幸的是,定名抵触还是个熬煎。可是swizzling实在也不用过量思索这个成绩。我们只必要在原始办法定名前做小小的修改来定名就好,好比一般我们如许定名:
- @interfaceNSView:NSObject
- -(void)setFrame:(NSRect)frame;
- @end
- @implementationNSView(MyViewAdditions)
- -(void)my_setFrame:(NSRect)frame{
- //docustomwork
- [selfmy_setFrame:frame];
- }
- +(void)load{
- [selfswizzle:@selector(setFrame:)with:@selector(my_setFrame:)];
- }
- @end
这段代码运转准确,可是假如my_setFrame:在别处被界说了会产生甚么呢?
这个成绩不单单存在于swizzling,这里有一个替换的变通办法:
- @implementationNSView(MyViewAdditions)
- staticvoidMySetFrame(idself,SEL_cmd,NSRectframe);
- staticvoid(*SetFrameIMP)(idself,SEL_cmd,NSRectframe);
- staticvoidMySetFrame(idself,SEL_cmd,NSRectframe){
- //docustomwork
- SetFrameIMP(self,_cmd,frame);
- }
- +(void)load{
- [selfswizzle:@selector(setFrame:)with:(IMP)MySetFramestore:(IMP*)&SetFrameIMP];
- }
- @end
看起来不那末Objectice-C了(用了函数指针),如许制止了selector的定名抵触。
最初给出一个较完善的swizzle办法的界说:
- typedefIMP*IMPPointer;
- BOOLclass_swizzleMethodAndStore(Classclass,SELoriginal,IMPreplacement,IMPPointerstore){
- IMPimp=NULL;
- Methodmethod=class_getInstanceMethod(class,original);
- if(method){
- constchar*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);
- }
- @implementationNSObject(FRRuntimeAdditions)
- +(BOOL)swizzle:(SEL)originalwith:(IMP)replacementstore:(IMPPointer)store{
- returnclass_swizzleMethodAndStore(self,original,replacement,store);
- }
- @end
Swizzlingchangesthemethodsarguments
我以为这是最年夜的成绩。想一般挪用methodswizzling将会是个成绩。
间接挪用my_setFrame:,runtime做的是
- objc_msgSend(self,@selector(my_setFrame:),frame);
runtime往寻觅my_setFrame:的办法完成,_cmd参数为my_setFrame:,可是现实上runtime找到的办法完成是原始的setFrame:的。
一个复杂的办理举措:利用下面先容的swizzling界说。
Theorderofswizzlesmatters
多个swizzle办法的实行按次也必要注重。假定setFrame:只界说在NSView中,想像一下依照上面的按次实行:
- [NSButtonswizzle:@selector(setFrame:)with:@selector(my_buttonSetFrame:)];
- [NSControlswizzle:@selector(setFrame:)with:@selector(my_controlSetFrame:)];
- [NSViewswizzle:@selector(setFrame:)with:@selector(my_viewSetFrame:)];
WhathappenswhenthemethodonNSButtonisswizzled?WellmostswizzlingwillensurethatitsnotreplacingtheimplementationofsetFrame:forallviews,soitwillpulluptheinstancemethod.Thiswillusetheexistingimplementationtore-definesetFrame:intheNSButtonclasssothatexchangingimplementationsdoesntaffectallviews.TheexistingimplementationistheonedefinedonNSView.ThesamethingwillhappenwhenswizzlingonNSControl(againusingtheNSViewimplementation).
WhenyoucallsetFrame:onabutton,itwillthereforecallyourswizzledmethod,andthenjumpstraighttothesetFrame:methodoriginallydefinedonNSView.TheNSControlandNSViewswizzledimplementationswillnotbecalled.
Butwhatiftheorderwere:
- [NSViewswizzle:@selector(setFrame:)with:@selector(my_viewSetFrame:)];
- [NSControlswizzle:@selector(setFrame:)with:@selector(my_controlSetFrame:)];
- [NSButtonswizzle:@selector(setFrame:)with:@selector(my_buttonSetFrame:)];
Sincetheviewswizzlingtakesplacefirst,thecontrolswizzlingwillbeabletopulluptherightmethod.Likewise,sincethecontrolswizzlingwasbeforethebuttonswizzling,thebuttonwillpullupthecontrolsswizzledimplementationofsetFrame:.Thisisabitconfusing,butthisisthecorrectorder.Howcanweensurethisorderofthings?
Again,justuseloadtoswizzlethings.Ifyouswizzleinloadandyouonlymakechangestotheclassbeingloaded,youllbesafe.Theloadmethodguaranteesthatthesuperclassloadmethodwillbecalledbeforeanysubclasses.Wellgettheexactrightorder!
这段贴了原文,硬翻译太拗口……总结一下就是:多个有承继干系的类的对象swizzle时,从子类对象入手下手。假如先swizzle父类对象,那末前面子类对象swizzle时就没法拿到真实的原始办法完成了。
(感激批评中qq373127202的提示,在此改正一下,非常感激)
多个有承继干系的类的对象swizzle时,先从父对象入手下手。如许才干包管子类办法拿到父类中的被swizzle的完成。在+(void)load中swizzle不会堕落,就是由于load类办法会默许从父类入手下手挪用。
Difficulttounderstand(looksrecursive)
(新办法的完成)看起来像递回,可是看看下面已给出的swizzling封装办法,利用起来就很易读懂.
这个成绩是已完整办理的了!
很多框架是用C写的学习iOS开发基础可以按照下面两个方面学基础(原料钢铁塑料) |
|