简介
在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现。
每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。
我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP,
我们可以利用 class_replaceMethod 来修改类,
我们可以利用 method_setImplementation 来直接设置某个方法的IMP,
通过上边的方法,可以把类的调度表(dispatch table)中选择器到最终函数间的映射关系 替换。就相当于把IMP 的只想替换了。
- swizzling 需要在 + (void)load{}中使用
- swizzling 需要保证只执行一次。 需要使用 dispatch_once;
- load 和initialize区别:load是只要类所在文件被引用就会被调用,而initialize是在类或者其子类的第一个方法被调用前调用。所以如果类没有被引用进项目,就不会有load调用;但即使类文件被引用进来,但是没有使用,那么initialize也不会被调用。
使用场景 (1)
需要统计事件,或者需要输出Log的时候,可以使用。比如在delloc中,输出log。告诉我们哪个类释放了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16+ (void)swizzWithClass:(Class)class originSel:(SEL)originSel newSel:(SEL)newSel{ Method originM = class_getInstanceMethod(class, originSel); Method newM = class_getInstanceMethod(class, newSel); IMP newImp = method_getImplementation(newM); BOOL addMethodSucess = class_addMethod(class, newSel, newImp, method_getTypeEncoding(newM)); if (addMethodSucess) { class_replaceMethod(class, originSel, newImp, method_getTypeEncoding(newM)); }else{ method_exchangeImplementations(originM, newM); } }
上边这段代码是 封装了,替换,可以直接copy 使用。下面我就进行。delloc的替换,这块,我要说明下,有个小问题。替换 方法的实现很多,这里如果你要替换delloc你,只能这么写。
1
2Method originalMethod = class_getClassMethod(self, @selector(dealloc));
dealloc 是系统关键字,上边的这种获取方式会报错。 使用封装的方法没事
代码如下
1
2
3
4
5
6
7
8+ (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [self swizzWithClass:[self class] originSel:NSSelectorFromString(@"dealloc") newSel:@selector(swizz_dealloc)]; });
}
需要保证只执行一次,多次,容易出现不可预知的错误。
1
2
3
4
5
6
7- (void)swizz_dealloc{ NSLog(@" ** %@ 释放了 %s",NSStringFromClass([self class]),__func__); [self swizz_dealloc]; }
这样你控制器的释放一目了然。 思路方法如上,你可以根据你自己的需求修改,来达到目的。
使用场景(2)
数组越界问题 ,空值 。导致程序崩溃的问题。
方法一 通过分类添加安全的索引方法 :
1
2
3
4
5
6
7
8
9@implementation UIView (safe) - (BOOL)containsObjectAtIndex:(NSInteger)index { return index >= 0 && index <self.count; } - (id)objectNilAtIndex:(NSInteger)index{ return [self containsObjectAtIndex:index] ? [self objectAtIndex:index] : nil; } @end
第二种方法。使用Method sizzling
1
2
3
4
5
6
7
8
9
10
11
12+ (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ @autoreleasepool { [objc_getClass("__NSArray0") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(emptyObjectIndex:)]; [objc_getClass("__NSArrayI") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(arrObjectIndex:)]; [objc_getClass("__NSArrayM") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(mutableObjectIndex:)]; [objc_getClass("__NSArrayM") swizzleMethod:@selector(insertObject:atIndex:) swizzledSelector:@selector(mutableInsertObject:atIndex:)]; } }); }
写在 NSArray的类目里边
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38- (id)emptyObjectIndex:(NSInteger)index{ return nil;} - (id)arrObjectIndex:(NSInteger)index{ if (index >= self.count || index < 0) { return nil; return [self arrObjectIndex:index]; } - (id)mutableObjectIndex:(NSInteger)index{ if (index >= self.count || index < 0) { return nil; } return [self mutableObjectIndex:index]; } - (void)mutableInsertObject:(id)object atIndex:(NSUInteger)index{ if (object) { [self mutableInsertObject:object atIndex:index]; } } @implementation NSDictionary (FlyElephant) + (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ @autoreleasepool { [objc_getClass("__NSDictionaryM") swizzleMethod:@selector(setObject:forKey:) swizzledSelector:@selector(mutableSetObject:forKey:)]; } }); } - (void)mutableSetObject:(id)obj forKey:(NSString *)key{ if (obj && key) { [self mutableSetObject:obj forKey:key]; } }@end
*这里NSArray0表示一般空数组,NSArrayI表示一般数组,__NSArrayM可变数组,NSArray和NSMutableArray相当于工厂,最终实现通过上面三个类进行实现的,有兴趣的可以自行了解一下类簇.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21- (void)swizzleMethod:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector{ Class class = [self class]; Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }
上边是 交换方法的封装,可以直接copy拿走直接使用。
通过Runtime进行对数组字典进行方法交换之后,有可能出现不明所以的崩溃 message sent to deallocated instance 0x1459e0600**
通过Runtime进行对数组字典进行方法交换之后,之前对键盘输入的场景没有完全弄清楚,完整过程应该是,文本框输入文字→Home键进入后台→点击App图标重新进入→崩溃,有的时候前两步就直接崩溃了,因此Runtime的这个文件需要通过mrc管理:
一般项目都是 ARC 模式,需要单独问Runtime的这个问题添加MRC支持,
Build Phases -> Compile Sources找到文件设置 -fno-objc-arc 标签。
如果项目是MRC 模式,则为 ARC 模式的代码文件加入 -fobjc-arc 标签.
同时可以为相应的代码块添加autoreleasepool:
1
2
3
4
5
6
7@autoreleasepool { if (index >= self.count || index < 0) { return nil; } return [self arrObjectIndex:index]; }
特别说明
*第一种方法。小团队使用, 人少,新员工 入职需要写说明文档。对苹果原生支持不够。
*第二种方法。 多人团队使用。新入职员工,无需说明文档。可以直接调用原生API 。对苹果支持 度高。可扩展性高
使用场景 (3)
还有一种错误,是unrecognized selector sent to class会导致APP崩溃。 要想APP 健壮。需要处理、
1
2
3
4
5
6
7
8
9
10
11
12- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { NSMethodSignature *signature = [super methodSignatureForSelector:sel]; if (!signature) { NSLog(@"*******%@ - %@ ***un reconize selector %s!",NSStringFromClass([self class]),NSStringFromSelector(sel),__func__); signature = [NSMethodSignature signatureWithObjCTypes:@encode(void)]; } return signature; } - (void)forwardInvocation:(NSInvocation *)invocation { - }
这里知识设计 。动态方法决议(Dynamic Method Resolution)。和消息转发机制(Message Forwarding)。 有兴趣的同学可以去看看。
最后
以上就是无语芒果最近收集整理的关于method-swizzling 详解 和使用的全部内容,更多相关method-swizzling内容请搜索靠谱客的其他文章。
发表评论 取消回复