我是靠谱客的博主 无语芒果,这篇文章主要介绍method-swizzling 详解 和使用,现在分享给大家,希望可以做个参考。

简介

在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
2
Method 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内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(76)

评论列表共有 0 条评论

立即
投稿
返回
顶部