说说objcRuntime的一些妙用(class_addMethod,class_replaceMe


前言: 
陈列一下今天要讲的知识点:classaddMethod,classreplaceMethod,methodgetImplementation,objectgetClass数组

涉及到的知识 
--使用category,经过Runtime实现用本身的函数调换掉原生函数 
--oc的message forwarding 
--使用Runtime为类添加原来没有的方法 
--为何category里不重写方法函数

注明: 
本文章内技术参考固然来自四面八方,来自不一样时期,小弟只是作个总结,有很差的地方欢迎你们指导spa

  先从一个场景问题带出吧,毕业设计的时候小弟作ipad应用,到后面才决定加上旋转屏适配,看着100多个文件20多个页面差点没把血吐出来,哈哈每一个controller去修改方法是不可能的了,由于强迫症也不想多创个父类,好吧决定一次过替换掉这些controller里的viewWillAppear: 和 willAnimateRotationToInterfaceOrientation:duration:,换成本身的。 



先看一个category 经过运用classaddMethodclassreplaceMethod来调换掉系统库里的方法设计

#import "NSObject+Swizzle.h"@implementation NSObject (Swizzle)+ (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)aftSel {

    Method originMethod = class_getInstanceMethod(self, origSel);
    Method newMethod = class_getInstanceMethod(self, aftSel);    if(originMethod && newMethod) {//必须两个Method都要拿到
        if(class_addMethod(self, origSel, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {            //实现成功添加后
            class_replaceMethod(self, aftSel, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
        }        return YES;
    }    return NO;
}@end

1.传入两个参数,原方法选择子,新方法选择子,并经过classgetInstanceMethod()拿到对应的Method 

2.class
addMethod,是相对于实现来的说的,将原本不存在于被操做的Class里的newMethod的实现添加在被操做的Class里,并使用origSel做为其选择子(注意参数中的self为被操做的Class,不要忘了这里是类方法). 

3.classreplaceMethod,addMethod成功完成后,从参数能够看出,目的是换掉methodgetImplaementation(roiginMethod)的选择子,将原方法的实现的SEL换成新方法的SEL:aftSel,ok目的达成了。想想,如今经过旧方法SEL来调用,就会实现新方法的IMP,经过新方法的SEL来调用,就会实现旧方法的IMP,好了理一理思路继续往下。 



此次就用NSString作载体来演示吧:code

#import "MyString.h"#import "NSObject+Swizzle.h"@implementation MyString+ (void)load {    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{
        Class clazz = object_getClass((id)self);
        [clazz swizzleMethod:@selector(resolveInstanceMethod:) withMethod:@selector(myResolveInstanceMethod:)];
    });
}

+ (BOOL)myResolveInstanceMethod:(SEL)sel {    if(! [self myResolveInstanceMethod:sel]) {        NSString *selString = NSStringFromSelector(sel);        if([selString isEqualToString:@"countAll"] || [selString isEqualToString:@"pushViewController"]) {
            class_addMethod(self, sel, class_getMethodImplementation(self, @selector(dynamicMethodIMP)), "v@:");            return YES;
        }else {            return NO;
        }
    }    return YES;
}

- (void)dynamicMethodIMP {    NSLog(@"我是动态加入的函数");
}@end

1.首先这里要提下resolveInstanceMethod:,不了解的朋友能够去补一下oc的message forwarding,就是当运行时对象调用了一个找不到的方法的时候系统会去寻找的机制,这个方法是第一步去到的地方,咱们能够在这里面runtime添加方法,是的,首先咱们得劫持了这个方法,作咱们本身的事,经过刚才category里封装好的swizzleMethod:withMethod: -------这个时候有朋友有疑问了,咱们能够重写这个方法来作本身的事情啊,其实并不能够,在category里重写现有方法会有警告#Category is implementing a method which will also be implemented by its primary class,这种作法是不提倡的! ------------category没有办法去代替子类,它不能像子类同样经过super去调用父类的方法实现。若是category中重写覆盖了当前类中的某个方法,那么这个当前类中的原始方法实现,将永远不会被执行,这在某些方法里是致命的(这里提一下一个特例+(void)load,它会在当前方法里执行完再去category里执行). ------------若是两个category重写了同一个方法,咱们没法控制哪一个优先级更高,一直以来仍是提倡经过继承去重写方法 

2.objectgetClass拿到当前MyString的Class,调用刚才category里封装好的swizzleMethod:withMethod:,用咱们本身的myResolveInstanceMethod:去替换原生的,好了,如今若是咱们在运行时调用了一个不存在的方法,系统会去调用咱们的myResolveInstanceMethod:,是的不用怀疑。 

3.如今看看myResolveInstanceMethod:里面又调用了一次myResolveInstanceMethod:,有的朋友会觉得是递归其实并非,系统去调用原生的方法,会跑到咱们本身的方法实现,是由于咱们以前的swizzle操做没问题,而不要忘记了,咱们本身的方法selector对应的实现,已经换成了原生方法的实现,ok。。if(! [self myResolveInstanceMethod:sel])是调用原生方法的实现,去检测一次传入的方法是否存在,若是仍是没有,则作class
addMethod操做为此类添加对应的方法,return YES,该方法被系统调用,OK,达到目的。 

class_addMethod参数的意义orm

class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)class_addMethod(self, sel, class_getMethodImplementation(self, @selector(dynamicMethodIMP)), "v@:");

按顺序是,类--选择子--实现--方法的返回值和参数资料。 
v表明返回值void,@表明id类型对象,:表明选择子。 
why? 其实每个oc方法都有两个隐式的参数(id self, SEL _cmd),也能够说是由C语言函数再加着两个参数组成一个oc方法。对象

最后看看咱们的工做的收获:继承

NSLog(@"begin test");  
//------------------------------------------------

    MyString *string = [[MyString alloc] init];
    [string performSelector:@selector(countAll)];
    [string performSelector:@selector(pushViewController)];
<pre name="code" class="objc">  
//------------------------------------------------
    NSLog(@"finish test");

-----Log:2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] begin test  
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是动态加入的函数  
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是动态加入的函数  
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是动态加入的函数  
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] finish test
相关文章
相关标签/搜索