二.方法
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!!