Reading Code ONE

2018-03-06  本文已影响10人  iLeooooo

引入:读JSONKit ~

static void lq_swizzleInstanceMethod(Class fromClass, Class toClass, SEL selector);
static void lq_swizzleClassMethod(Class fromClass, Class toClass, SEL selector);  
#pragma mark -
#pragma mark ObjC Voodoo
// These two functions are used to perform some ObjC swizzling voodoo to implement our mutable collection classes.
static void lq_swizzleInstanceMethod(Class fromClass, Class toClass, SEL selector) {
  fromClass = class_isMetaClass(fromClass) ? objc_getClass(class_getName(fromClass)) : fromClass;
  toClass   = class_isMetaClass(toClass)   ? objc_getClass(class_getName(toClass))   : toClass;
  class_replaceMethod(fromClass, selector, method_getImplementation(class_getInstanceMethod(toClass, selector)), method_getTypeEncoding(class_getInstanceMethod(fromClass, selector)));
}

static void lq_swizzleClassMethod(Class fromClass, Class toClass, SEL selector) {
  fromClass = class_isMetaClass(fromClass) ? fromClass : objc_getMetaClass(class_getName(fromClass));
  toClass   = class_isMetaClass(toClass)   ? toClass   : objc_getMetaClass(class_getName(toClass));
  class_replaceMethod(fromClass, selector, method_getImplementation(class_getClassMethod(toClass, selector)), method_getTypeEncoding(class_getClassMethod(fromClass, selector)));
}

ONE:

//获取实例方法
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name) ;
//类方法
class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)

method_exchangeImplementations使用方法示例:
一、UIControl扩展,拦截点击事件

#import "UIControl+LQHelper.h"
#import <objc/message.h>

@implementation UIControl (LQHelper)

+ (void)load {

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self changeMethod];
    });

}

/**
 *创建VC的时候先交换sendAction:to:forEvent:方法,在sendAction:to:forEvent:方法里面统计点击事件
 */
+ (void)changeMethod {

    Class class = [self class];
    SEL originalSelector = @selector(sendAction:to:forEvent:);
    SEL swizzledSelector = @selector(lq_sendAction:to:forEvent:);

    /**
     class_getInstanceMethod  获取实例方法
     class_getClassMethod     获取类方法
     */
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

    method_exchangeImplementations(originalMethod, swizzledMethod);
}

- (void)lq_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {

    NSLog(@"---%@---%@---%@", NSStringFromSelector(action), target, event);

    for (UITouch *touch in event.allTouches) {
    
        UIView *view = touch.view;
        NSLog(@"tag:%ld", view.tag);
    }

    [self lq_sendAction:action to:target forEvent:event];
}

@end

二、UITableView扩展,拦截tableview点击事件

#import "UITableView+LQHelper.h"
#import <objc/message.h>

@implementation UITableView (LQHelper)

+ (void)load {

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self changeDelegate];
    });
}

+ (void)changeDelegate {

    Class class = [self class];
    SEL originalSelector = @selector(setDelegate:);
    SEL swizzledSelector = @selector(lq_setDelegate:);

    /**
     class_getInstanceMethod  获取实例方法
     class_getClassMethod     获取类方法
     */
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

    method_exchangeImplementations(originalMethod, swizzledMethod);

}

- (void)lq_setDelegate:(id <UITableViewDelegate>)delegate {

    [self lq_setDelegate:delegate];

    Class class = [delegate class];

    if (class_addMethod(class, NSSelectorFromString(@"lq_didSelectRowAtIndexPath"), (IMP)lq_didSelectRowAtIndexPath, "v@:@@")) {
        /**
         class_getInstanceMethod  获取实例方法
         class_getClassMethod     获取类方法
         */
        Method originalMethod = class_getInstanceMethod(class, NSSelectorFromString(@"lq_didSelectRowAtIndexPath"));
        Method swizzledMethod = class_getInstanceMethod(class, @selector(tableView:didSelectRowAtIndexPath:));
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

void lq_didSelectRowAtIndexPath(id self, SEL _cmd, id tableView, id indexPath) {

    //do something - 添加统计事件
    NSLog(@"这是统计UITableView的点击事件,哈哈哈");

    SEL selector = NSSelectorFromString(@"lq_didSelectRowAtIndexPath");
    ((void(*)(id, SEL, id, id))objc_msgSend)(self, selector, tableView, indexPath);
}

@end

适用于:1. 页面点击事件统计(页面统计通过拦截viewWillDisappear:和viewWillAppear)

三、class_addMethodclass_replaceMethodmethod_getImplementationobject_getClass

  1. 使用category,通过Runtime实现用自己的函数调换掉原生函数
  2. oc的message forwarding
  3. 使用Runtime为类添加原来没有的方法
  4. 为什么category里不重写方法

通过运用class_addMethodclass_replaceMethod来调换掉系统库里的方法

#import "NSObject+Swizzle.h"  
#import <objc/message.h>

@implementation NSObject (Swizzle)  

+ (BOOL)swizzledMethod:(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. 传入两个参数,原方法选择子,新方法选择子,并通过class_getInstanceMethod拿到对应的Method
  2. class_addMethod是相对于实现来的说的,将本来不存在于被操作的Class里的newMethod的实现添加在被操作的Class里,并使用origSel作为其选择子(注意参数中的self为被操作的Class,不要忘了这里是类方法).
  3. class_replaceMethod,addMethod成功完成后,从参数可以看出,目的是换掉method_getImplaementation(roiginMethod)的选择子,将原方法的实现的SEL换成新方法的SEL:aftSel,ok目的达成了。想一想,现在通过旧方法SEL来调用,就会实现新方法的IMP,通过新方法的SEL来调用,就会实现旧方法的IMP
#import "NSString+LQHelper.h"
#import "NSObject+Swizzle.h"
#import <objc/message.h>

@implementation NSString (LQHelper)

+ (void)load {

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class cla = object_getClass(self);
        [cla swizzledMethod:@selector(resolveInstanceMethod:) withMethod:@selector(lqResolveInstanceMethod:)];
    });
}

+ (BOOL)lqResolveInstanceMethod:(SEL)sel {

    if (![self lqResolveInstanceMethod: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(@"动态加入的IMP");
}
@end
  1. 首先这里要提下resolveInstanceMethod:,不了解的朋友可以去补一下oc的message forwarding,就是当运行时对象调用了一个找不到的方法的时候系统会去寻找的机制,这个方法是第一步去到的地方,我们可以在这里面runtime添加方法,首先我们得劫持了这个方法,做我们自己的事,通过上面category里封装好的swizzledMethod:withMethod:
  2. category没有办法去代替子类,它不能像子类一样通过super去调用父类的方法实现。如果category中重写覆盖了当前类中的某个方法,那么这个当前类中的原始方法实现,将永远不会被执行,这在某些方法里是致命的(一个特例+(void)load,它会在当前方法里执行完再去category里执行) ,如果两个category重写了同一个方法,我们无法控制哪个优先级更高,一直以来还是提倡通过继承去重写方法。
  3. class_addMethod参数的意义,按顺序是:类--选择子--实现--方法的返回值和参数资料。v代表返回值void,@代表id类型对象,:代表选择子。为什么呢,其实每一个oc方法都有两个隐式的参数(id self, SEL _cmd),也可以说是由C语言函数再加着两个参数组成一个oc方法。

内容来自网上摘抄,简单整理,如果错误,欢迎指正,谢谢~
持续更新

上一篇 下一篇

猜你喜欢

热点阅读