runtime(2)--method

二.方法

1.动态的给某个类添加一个方法。(Add Method)

Class c = [NSObject class];
IMP greetingIMP = imp_implementationWithBlock((id)^(id obj){
    return @"Hello, World!";
});
const char *greetingTypes = [[NSString stringWithFormat:@"%s%s%s", @encode(id), @encode(id), @encode(SEL)] UTF8String];
class_addMethod(c, @selector(greetingWithName:), greetingIMP, greetingTypes);

这有什么用呢?毕竟日常开发中,也不会这样给类增加方法。
有时候我们会遇到一些稀奇古怪的问题,比如:
[UIThreadSafeNode createPeripheral]: unrecognized selector sent to instance,复现也复现不了,不知道这个crash的原因,连这个类都没见过,那该怎么改?

//给未知类添加一个方法。
BOOL resolveUnrecognizedSelectorToClass(NSString *methodName, NSString *className) {
    
    Class cls = NSClassFromString(className);
    if (!cls) return NO;
    
    IMP imp = imp_implementationWithBlock((id)^(id obj){
        NSLog(@"Hello, World!");
        return nil;
    });
    const char *types = [[NSString stringWithFormat:@"%s%s%s", @encode(id), @encode(id), @encode(SEL)] UTF8String];
    BOOL result = class_addMethod(cls, NSSelectorFromString(methodName), imp, types);
    return result;
}

写个demo测试一下,创建一个Person类,且没有sayBye方法。

        BOOL isSuccess = resolveUnrecognizedSelectorToClass(@"sayBye", @"Person");
        if (isSuccess) [[NSClassFromString(@"Person") new] performSelector:@selector(sayBye)];

测试OK,没有crash。
同样的,[UIThreadSafeNode createPeripheral]: unrecognized selector sent to instance,也可以这样解决。

2.交换方法的实现 (Exchange Method)

//同一个对象交换方法的实现部分。eat和drink互换实现部分
Method eat = class_getInstanceMethod([Person class], sel_registerName("eat:"));
Method drink = class_getInstanceMethod([Person class], sel_registerName("drink:"));
method_exchangeImplementations(eat, drink);
        
//替换方法的实现部分。eat被替换成play
Method play = class_getInstanceMethod([Student class], sel_registerName("play:"));
class_replaceMethod([Person class], sel_registerName("eat:"), method_getImplementation(play), "v@:");
        
Person *per = [[Person alloc]init];
[per eat:@"超级汉堡"];
[per drink:@"焦糖咖啡"];

交换方法实现,有什么用呢?
比如,你接到一个需求,要app统计每个页面的使用次数。最常见的做法,就是在每一个viewWillAppear方法里,记录一下。每一个?
比如,你的小伙伴使用数组总是越界崩溃,那你是不是要处理一下?
下面针对数组越界,处理一下。

//崩溃日志
 -[__NSArrayI objectAtIndex:]: index 3 beyond bounds [0 .. 1]'
#import "NSArray+Index.h"
#import 

@implementation NSArray (Index)

+ (void)load {
    
    Class cls = NSClassFromString(@"__NSArrayI");
    Method oldMethod1 = class_getInstanceMethod(cls, @selector(objectAtIndex:));
    Method newMethod1 = class_getInstanceMethod(cls, @selector(newObjectAtIndex:));
    method_exchangeImplementations(oldMethod1, newMethod1);
    
    Method oldMethod2 = class_getInstanceMethod(cls, @selector(objectAtIndexedSubscript:));
    Method newMethod2 = class_getInstanceMethod(cls, @selector(newObjectAtIndexedSubscript:));
    method_exchangeImplementations(oldMethod2, newMethod2);
}

- (id)newObjectAtIndex:(NSUInteger)index {
    if (index >= self.count) {
        return nil;
    }
    return [self newObjectAtIndex:index];
}

- (id)newObjectAtIndexedSubscript:(NSUInteger)idx {
    if (idx >= self.count) {
        return nil;
    }
    return [self newObjectAtIndexedSubscript:idx];
}

@end

测试一下,OK。

    NSArray *lists = @[@"1", @"2"];
    NSLog(@"---%@", [lists objectAtIndex:3]); //---(null)
    NSLog(@"+++%@", lists[4]); //+++(null)

3.Method Swizzling

实际上就是addMethod,exchangeMethod等的统称,各个方法的结合可以写出的更严谨的代码。

#import "UIViewController+Hook.h"
#import 

@implementation UIViewController (Hook)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        
        SEL originSel = @selector(viewWillAppear:);
        SEL swizzSel = @selector(swizz_viewWillAppear:);
        Method originMethod = class_getInstanceMethod(class, originSel);
        Method swizzMethod = class_getInstanceMethod(class, swizzSel);

        //为什么要用addMethod,为什么不直接调用methodExchange呢??
        //因为:子类调用class_addMethod(method A)可以覆盖父类的A方法,返回成功;但是却无法覆盖自身的A方法,返回失败。
        BOOL didAdded = class_addMethod(class, originSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
        if (didAdded) {
            //如果addMethodA成功了,说明该类原本是没有A方法,新给该类添加了originSel及swizzMethod的实现。接下来就只需要把originMethod的实现替换给swizzSel即可。
            class_replaceMethod(class,swizzSel, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
        }else {
            //如果addMethodA失败了,说明该类原来就有A方法,那就只需要交换一下方法实现即可。
            method_exchangeImplementations(originMethod, swizzMethod);
        }
    });
}

- (void)swizz_viewWillAppear:(BOOL)animated {
    [self swizz_viewWillAppear:animated];
    NSLog(@"%@ -- appear ",NSStringFromClass([self class]));
}

@end

测试一下,在项目里新建多个UIViewController子类,每次viewWillAppear里都会输出NSLog。OK!!

你可能感兴趣的:(runtime(2)--method)