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 <objc/message.h>

@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 <objc/message.h>

@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!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Overview The ccxt library is a collection of available cr...
    郭蝈儿蝈儿阅读 3,826评论 0 1
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,824评论 0 9
  • 引言: 如果说想要通过学习柳比歇夫的时间统计法,并希望如同作者笔下的主人翁一样活得更加丰满完整的话,其实就是一两个...
    甘泽欣阅读 505评论 0 0
  • 火车在缓缓蠕动 希望它的轮子松松垮垮 我望向窗外 一片雾尝遍了这座城市的疲倦 鸡鸣传遍了整座城 太阳却还未将这座城...
    纵浪阅读 131评论 4 1
  • 09104---胡练练 酷学多纳出品:Big and Small. 大和小 Big and Small 大和...
    胡练练阅读 2,033评论 2 1