iOS Runntime 动态添加类方法并调用-class_addMethod
上手开发iOS一段时间后,我发现并不能只着眼于完成需求,利用闲暇之余多研究其他的开发技巧,才能在有限时间内提升自己水平。当然,“其他开发技巧”这个命题对于任何一个开发领域都感觉不找边际,而对于我来说,尝试接触objc/runtime不失为是开始深入探索iOS开发的第一步。
刚了解runtime当然要从比较简单的api开始,今天就罗列整理一下class_addMethod的相关点:
首先从文档开始。
/** *Addsanewmethodtoaclasswithagivennameandimplementation. * *@paramclsTheclasstowhichtoaddamethod. *@paramnameAselectorthatspecifiesthenameofthemethodbeingadded. *@paramimpAfunctionwhichistheimplementationofthenewmethod.Thefunctionmusttakeatleasttwoarguments—selfand_cmd. *@paramtypesAnarrayofcharactersthatdescribethetypesoftheargumentstothemethod. * *@returnYESifthemethodwasaddedsuccessfully,otherwiseNO *(forexample,theclassalreadycontainsamethodimplementationwiththatname). * *@noteclass_addMethodwilladdanoverrideofasuperclass'simplementation, *butwillnotreplaceanexistingimplementationinthisclass. *Tochangeanexistingimplementation,usemethod_setImplementation. */ OBJC_EXPORTBOOLclass_addMethod(Classcls,SELname,IMPimp, constchar*types) __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
大意翻译一下,这个方法的作用是,给类添加一个新的方法和该方法的具体实现。分析一下这个方法需要的参数:
Classcls
cls参数表示需要添加新方法的类。
SELname
name参数表示selector的方法名称,可以根据喜好自己进行命名。
IMPimp
imp即implementation,表示由编译器生成的、指向实现方法的指针。也就是说,这个指针指向的方法就是我们要添加的方法。
constchar*types
最后一个参数*types表示我们要添加的方法的返回值和参数。
简要介绍了class_addMethod中所需要的参数以及作用之后,我们就可以开始利用这个方法进行添加我们所需要的方法啦!在使用之前,我们首先要明确Objective-C作为一种动态语言,它会将部分代码放置在运行时的过程中执行,而不是编译时,所以在执行代码时,不仅仅需要的是编译器,也同时需要一个运行时环境(Runtime),为了满足一些需求,苹果开源了RuntimeSource并提供了开放的api供开发者使用。
其次,我们需要知道在什么情况下需要调用class_addMethod这个方法。当项目中,需要继承某一个类(subclass),但是父类中并没有提供我需要的调用方法,而我又不清楚父类中某些方法的具体实现;或者,我需要为这个类写一个分类(category),在这个分类中,我可能需要替换/新增某个方法(注意:不推荐在分类中重写方法,而且也无法通过super来获取所谓父类的方法)。大致在这两种情况下,我们可以通过class_addMethod来实现我们想要的效果。
好了,说了这么多那么到底应该如何调用呢?如果不清楚使用方法,那么看说明书就是最好的方法。在Apple提供的文档中就有详细的使用方法(Objective-CRuntimeProgrammingGuide-DynamicMethodResolution),以下内容就以myCar这个类来详细说明一下具体的使用规则:
首先,既然要给某个类添加我们的方法,就应该继承或者给这个类写一个分类,这里我新建一个名为「myCar」的类,作为「Car」类的分类。
#import"Car+myCar.h" @implementationCar(myCar) @end
我们知道,在Objective-C中,正常的调用方法是通过消息机制(message)来实现的,那么如果类中没有找到发送的消息方法,系统就会进入找不到该方法的处理流程中,如果在这个流程中,我们加入我们所需要的新方法,就能实现运行过程中的动态添加了。这个流程或者说机制,就是Objective-C的MessageForwarding
这个机制中所涉及的方法主要有两个:
+(BOOL)resolveInstanceMethod:(SEL)sel +(BOOL)resolveClassMethod:(SEL)sel
两个方法的唯一区别在于需要添加的是静态方法还是实例方法。我们就拿前者来说,既然要添加方法,我们就在「myCar」类中实现它,代码如下:
#import"Car+myCar.h" voidstartEngine(idself,SEL_cmd){ NSLog(@"mycarstartstheengine"); } @implementationCar(myCar) @end
至此,我们实现了我们要添加的startEngine这个方法。这是一个C语言的函数,它至少包含了self和_cmd两个参数(self代表着函数本身,而_cmd则是一个SEL数据体,包含了具体的方法地址)。如果要在这个方法中新增参数呢?见如下代码:
#import"Car+myCar.h" voidstartEngine(idself,SEL_cmd,NSString*brand){ NSLog(@"my%@carstartstheengine",brand); } @implementationCar(myCar) @end
只要在那两个必须的参数之后添加所需要的参数和类型就可以了,返回值同样道理,只要把方法名之前的void修改成我们想要的返回类型就可以,这里我们不需要返回值。
接着,我们重载resolveInstanceMethod:这个函数:
#import"Car+myCar.h" #import<objc/runtime.h> voidstartEngine(idself,SEL_cmd,NSString*brand){ NSLog(@"my%@carstartstheengine",brand); } @implementationCar(myCar) +(BOOL)resolveInstanceMethod:(SEL)sel{ if(sel==@selector(drive)){ class_addMethod([selfclass],sel,(IMP)startEngine,"v@:@"); returnYES; } return[superresolveInstanceMethod:sel]; } @end
解释一下,这个函数在runtime环境下,如果没有找到该方法的实现的话就会执行。第一行判断的是传入的SEL名称是否匹配,接着调用class_addMethod方法,传入相应的参数。其中第三个参数传入的是我们添加的C语言函数的实现,也就是说,第三个参数的名称要和添加的具体函数名称一致。第四个参数指的是函数的返回值以及参数内容。
至于该类方法的返回值,在我测试的时候,无论这个BOOL值是多少,并不会影响我们的执行目标,一般返回YES即可。
如果觉得用C语言风格写新函数比较不适应,那么可以改写成以下的代码:
@implementationCar(myCar) +(BOOL)resolveInstanceMethod:(SEL)sel{ if(sel==@selector(drive)){ class_addMethod([selfclass],sel,class_getMethodImplementation(self,@selector(startEngine:)),"s@:@"); returnYES; } return[superresolveInstanceMethod:sel]; } -(void)startEngine:(NSString*)brand{ NSLog(@"my%@carstartstheengine",brand); } @end
其中class_getMethodImplementation意思就是获取SEL的具体实现的指针。
然后创建一个新的类「DynamicSelector」,在这个新类中我们实现对「Car」的动态添加方法。
#import"DynamicSelector.h" #import"Car+myCar.h" @implementationDynamicSelector -(void)dynamicAddMethod{ Car*c=[[Caralloc]init]; [cperformSelector:@selector(drive)withObject:@"bmw"]; } @end
注意,在这里就不能使用[selfmethod:]进行调用了,因为我们添加的方法是在运行时才执行,而编译器只负责编译时的方法检索,一旦对一个对象没有检索到它的drive方法,就会报错,所以这里我们使用performSelector:withObject:来进行调用,保存,运行。
2016-08-2610:50:17.207objc-runtime[76618:3031897]mybmwcarstartstheengine Programendedwithexitcode:0
打印结果符合我们期望实现的目标。如果需要返回值,方法类似。
以上所述是小编给大家介绍的iOSRunntime动态添加类方法并调用-class_addMethod,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!